]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/media-views.js
WordPress 4.4.1
[autoinstalls/wordpress.git] / wp-includes / js / media-views.js
1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2 /**
3  * wp.media.controller.CollectionAdd
4  *
5  * A state for adding attachments to a collection (e.g. video playlist).
6  *
7  * @class
8  * @augments wp.media.controller.Library
9  * @augments wp.media.controller.State
10  * @augments Backbone.Model
11  *
12  * @param {object}                     [attributes]                         The attributes hash passed to the state.
13  * @param {string}                     [attributes.id=library]      Unique identifier.
14  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
15  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
16  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
17  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
18  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
19  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
20  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
21  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
22  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
23  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
24  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
25  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
26  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
27  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
28  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
29  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
30  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
31  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
32  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
33  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
34  */
35 var Selection = wp.media.model.Selection,
36         Library = wp.media.controller.Library,
37         CollectionAdd;
38
39 CollectionAdd = Library.extend({
40         defaults: _.defaults( {
41                 // Selection defaults. @see media.model.Selection
42                 multiple:      'add',
43                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
44                 filterable:    'uploaded',
45
46                 priority:      100,
47                 syncSelection: false
48         }, Library.prototype.defaults ),
49
50         /**
51          * @since 3.9.0
52          */
53         initialize: function() {
54                 var collectionType = this.get('collectionType');
55
56                 if ( 'video' === this.get( 'type' ) ) {
57                         collectionType = 'video-' + collectionType;
58                 }
59
60                 this.set( 'id', collectionType + '-library' );
61                 this.set( 'toolbar', collectionType + '-add' );
62                 this.set( 'menu', collectionType );
63
64                 // If we haven't been provided a `library`, create a `Selection`.
65                 if ( ! this.get('library') ) {
66                         this.set( 'library', wp.media.query({ type: this.get('type') }) );
67                 }
68                 Library.prototype.initialize.apply( this, arguments );
69         },
70
71         /**
72          * @since 3.9.0
73          */
74         activate: function() {
75                 var library = this.get('library'),
76                         editLibrary = this.get('editLibrary'),
77                         edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
78
79                 if ( editLibrary && editLibrary !== edit ) {
80                         library.unobserve( editLibrary );
81                 }
82
83                 // Accepts attachments that exist in the original library and
84                 // that do not exist in gallery's library.
85                 library.validator = function( attachment ) {
86                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
87                 };
88
89                 // Reset the library to ensure that all attachments are re-added
90                 // to the collection. Do so silently, as calling `observe` will
91                 // trigger the `reset` event.
92                 library.reset( library.mirroring.models, { silent: true });
93                 library.observe( edit );
94                 this.set('editLibrary', edit);
95
96                 Library.prototype.activate.apply( this, arguments );
97         }
98 });
99
100 module.exports = CollectionAdd;
101
102 },{}],2:[function(require,module,exports){
103 /**
104  * wp.media.controller.CollectionEdit
105  *
106  * A state for editing a collection, which is used by audio and video playlists,
107  * and can be used for other collections.
108  *
109  * @class
110  * @augments wp.media.controller.Library
111  * @augments wp.media.controller.State
112  * @augments Backbone.Model
113  *
114  * @param {object}                     [attributes]                      The attributes hash passed to the state.
115  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
116  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
117  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
118  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
119  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
120  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
121  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
122  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
123  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
124  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
125  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
126  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
127  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
128  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
129  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
130  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
131  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
132  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
133  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
134  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
135  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
136  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
137  */
138 var Library = wp.media.controller.Library,
139         l10n = wp.media.view.l10n,
140         $ = jQuery,
141         CollectionEdit;
142
143 CollectionEdit = Library.extend({
144         defaults: {
145                 multiple:         false,
146                 sortable:         true,
147                 date:             false,
148                 searchable:       false,
149                 content:          'browse',
150                 describe:         true,
151                 dragInfo:         true,
152                 idealColumnWidth: 170,
153                 editing:          false,
154                 priority:         60,
155                 SettingsView:     false,
156                 syncSelection:    false
157         },
158
159         /**
160          * @since 3.9.0
161          */
162         initialize: function() {
163                 var collectionType = this.get('collectionType');
164
165                 if ( 'video' === this.get( 'type' ) ) {
166                         collectionType = 'video-' + collectionType;
167                 }
168
169                 this.set( 'id', collectionType + '-edit' );
170                 this.set( 'toolbar', collectionType + '-edit' );
171
172                 // If we haven't been provided a `library`, create a `Selection`.
173                 if ( ! this.get('library') ) {
174                         this.set( 'library', new wp.media.model.Selection() );
175                 }
176                 // The single `Attachment` view to be used in the `Attachments` view.
177                 if ( ! this.get('AttachmentView') ) {
178                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
179                 }
180                 Library.prototype.initialize.apply( this, arguments );
181         },
182
183         /**
184          * @since 3.9.0
185          */
186         activate: function() {
187                 var library = this.get('library');
188
189                 // Limit the library to images only.
190                 library.props.set( 'type', this.get( 'type' ) );
191
192                 // Watch for uploaded attachments.
193                 this.get('library').observe( wp.Uploader.queue );
194
195                 this.frame.on( 'content:render:browse', this.renderSettings, this );
196
197                 Library.prototype.activate.apply( this, arguments );
198         },
199
200         /**
201          * @since 3.9.0
202          */
203         deactivate: function() {
204                 // Stop watching for uploaded attachments.
205                 this.get('library').unobserve( wp.Uploader.queue );
206
207                 this.frame.off( 'content:render:browse', this.renderSettings, this );
208
209                 Library.prototype.deactivate.apply( this, arguments );
210         },
211
212         /**
213          * Render the collection embed settings view in the browser sidebar.
214          *
215          * @todo This is against the pattern elsewhere in media. Typically the frame
216          *       is responsible for adding region mode callbacks. Explain.
217          *
218          * @since 3.9.0
219          *
220          * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
221          */
222         renderSettings: function( attachmentsBrowserView ) {
223                 var library = this.get('library'),
224                         collectionType = this.get('collectionType'),
225                         dragInfoText = this.get('dragInfoText'),
226                         SettingsView = this.get('SettingsView'),
227                         obj = {};
228
229                 if ( ! library || ! attachmentsBrowserView ) {
230                         return;
231                 }
232
233                 library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
234
235                 obj[ collectionType ] = new SettingsView({
236                         controller: this,
237                         model:      library[ collectionType ],
238                         priority:   40
239                 });
240
241                 attachmentsBrowserView.sidebar.set( obj );
242
243                 if ( dragInfoText ) {
244                         attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
245                                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
246                                 priority: -40
247                         }) );
248                 }
249
250                 // Add the 'Reverse order' button to the toolbar.
251                 attachmentsBrowserView.toolbar.set( 'reverse', {
252                         text:     l10n.reverseOrder,
253                         priority: 80,
254
255                         click: function() {
256                                 library.reset( library.toArray().reverse() );
257                         }
258                 });
259         }
260 });
261
262 module.exports = CollectionEdit;
263
264 },{}],3:[function(require,module,exports){
265 /**
266  * wp.media.controller.Cropper
267  *
268  * A state for cropping an image.
269  *
270  * @class
271  * @augments wp.media.controller.State
272  * @augments Backbone.Model
273  */
274 var l10n = wp.media.view.l10n,
275         Cropper;
276
277 Cropper = wp.media.controller.State.extend({
278         defaults: {
279                 id:          'cropper',
280                 title:       l10n.cropImage,
281                 // Region mode defaults.
282                 toolbar:     'crop',
283                 content:     'crop',
284                 router:      false,
285
286                 canSkipCrop: false
287         },
288
289         activate: function() {
290                 this.frame.on( 'content:create:crop', this.createCropContent, this );
291                 this.frame.on( 'close', this.removeCropper, this );
292                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
293         },
294
295         deactivate: function() {
296                 this.frame.toolbar.mode('browse');
297         },
298
299         createCropContent: function() {
300                 this.cropperView = new wp.media.view.Cropper({
301                         controller: this,
302                         attachment: this.get('selection').first()
303                 });
304                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
305                 this.frame.content.set(this.cropperView);
306
307         },
308         removeCropper: function() {
309                 this.imgSelect.cancelSelection();
310                 this.imgSelect.setOptions({remove: true});
311                 this.imgSelect.update();
312                 this.cropperView.remove();
313         },
314         createCropToolbar: function() {
315                 var canSkipCrop, toolbarOptions;
316
317                 canSkipCrop = this.get('canSkipCrop') || false;
318
319                 toolbarOptions = {
320                         controller: this.frame,
321                         items: {
322                                 insert: {
323                                         style:    'primary',
324                                         text:     l10n.cropImage,
325                                         priority: 80,
326                                         requires: { library: false, selection: false },
327
328                                         click: function() {
329                                                 var controller = this.controller,
330                                                         selection;
331
332                                                 selection = controller.state().get('selection').first();
333                                                 selection.set({cropDetails: controller.state().imgSelect.getSelection()});
334
335                                                 this.$el.text(l10n.cropping);
336                                                 this.$el.attr('disabled', true);
337
338                                                 controller.state().doCrop( selection ).done( function( croppedImage ) {
339                                                         controller.trigger('cropped', croppedImage );
340                                                         controller.close();
341                                                 }).fail( function() {
342                                                         controller.trigger('content:error:crop');
343                                                 });
344                                         }
345                                 }
346                         }
347                 };
348
349                 if ( canSkipCrop ) {
350                         _.extend( toolbarOptions.items, {
351                                 skip: {
352                                         style:      'secondary',
353                                         text:       l10n.skipCropping,
354                                         priority:   70,
355                                         requires:   { library: false, selection: false },
356                                         click:      function() {
357                                                 var selection = this.controller.state().get('selection').first();
358                                                 this.controller.state().cropperView.remove();
359                                                 this.controller.trigger('skippedcrop', selection);
360                                                 this.controller.close();
361                                         }
362                                 }
363                         });
364                 }
365
366                 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
367         },
368
369         doCrop: function( attachment ) {
370                 return wp.ajax.post( 'custom-header-crop', {
371                         nonce: attachment.get('nonces').edit,
372                         id: attachment.get('id'),
373                         cropDetails: attachment.get('cropDetails')
374                 } );
375         }
376 });
377
378 module.exports = Cropper;
379
380 },{}],4:[function(require,module,exports){
381 /**
382  * wp.media.controller.CustomizeImageCropper
383  *
384  * A state for cropping an image.
385  *
386  * @class
387  * @augments wp.media.controller.Cropper
388  * @augments wp.media.controller.State
389  * @augments Backbone.Model
390  */
391 var Controller = wp.media.controller,
392         CustomizeImageCropper;
393
394 CustomizeImageCropper = Controller.Cropper.extend({
395         doCrop: function( attachment ) {
396                 var cropDetails = attachment.get( 'cropDetails' ),
397                         control = this.get( 'control' );
398
399                 cropDetails.dst_width  = control.params.width;
400                 cropDetails.dst_height = control.params.height;
401
402                 return wp.ajax.post( 'crop-image', {
403                         wp_customize: 'on',
404                         nonce: attachment.get( 'nonces' ).edit,
405                         id: attachment.get( 'id' ),
406                         context: control.id,
407                         cropDetails: cropDetails
408                 } );
409         }
410 });
411
412 module.exports = CustomizeImageCropper;
413
414 },{}],5:[function(require,module,exports){
415 /**
416  * wp.media.controller.EditImage
417  *
418  * A state for editing (cropping, etc.) an image.
419  *
420  * @class
421  * @augments wp.media.controller.State
422  * @augments Backbone.Model
423  *
424  * @param {object}                    attributes                      The attributes hash passed to the state.
425  * @param {wp.media.model.Attachment} attributes.model                The attachment.
426  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
427  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
428  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
429  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
430  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
431  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
432  */
433 var l10n = wp.media.view.l10n,
434         EditImage;
435
436 EditImage = wp.media.controller.State.extend({
437         defaults: {
438                 id:      'edit-image',
439                 title:   l10n.editImage,
440                 menu:    false,
441                 toolbar: 'edit-image',
442                 content: 'edit-image',
443                 url:     ''
444         },
445
446         /**
447          * @since 3.9.0
448          */
449         activate: function() {
450                 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
451         },
452
453         /**
454          * @since 3.9.0
455          */
456         deactivate: function() {
457                 this.stopListening( this.frame );
458         },
459
460         /**
461          * @since 3.9.0
462          */
463         toolbar: function() {
464                 var frame = this.frame,
465                         lastState = frame.lastState(),
466                         previous = lastState && lastState.id;
467
468                 frame.toolbar.set( new wp.media.view.Toolbar({
469                         controller: frame,
470                         items: {
471                                 back: {
472                                         style: 'primary',
473                                         text:     l10n.back,
474                                         priority: 20,
475                                         click:    function() {
476                                                 if ( previous ) {
477                                                         frame.setState( previous );
478                                                 } else {
479                                                         frame.close();
480                                                 }
481                                         }
482                                 }
483                         }
484                 }) );
485         }
486 });
487
488 module.exports = EditImage;
489
490 },{}],6:[function(require,module,exports){
491 /**
492  * wp.media.controller.Embed
493  *
494  * A state for embedding media from a URL.
495  *
496  * @class
497  * @augments wp.media.controller.State
498  * @augments Backbone.Model
499  *
500  * @param {object} attributes                         The attributes hash passed to the state.
501  * @param {string} [attributes.id=embed]              Unique identifier.
502  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
503  * @param {string} [attributes.content=embed]         Initial mode for the content region.
504  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
505  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
506  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
507  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
508  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
509  * @param {string} [attributes.url]                   The embed URL.
510  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
511  */
512 var l10n = wp.media.view.l10n,
513         $ = Backbone.$,
514         Embed;
515
516 Embed = wp.media.controller.State.extend({
517         defaults: {
518                 id:       'embed',
519                 title:    l10n.insertFromUrlTitle,
520                 content:  'embed',
521                 menu:     'default',
522                 toolbar:  'main-embed',
523                 priority: 120,
524                 type:     'link',
525                 url:      '',
526                 metadata: {}
527         },
528
529         // The amount of time used when debouncing the scan.
530         sensitivity: 400,
531
532         initialize: function(options) {
533                 this.metadata = options.metadata;
534                 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
535                 this.props = new Backbone.Model( this.metadata || { url: '' });
536                 this.props.on( 'change:url', this.debouncedScan, this );
537                 this.props.on( 'change:url', this.refresh, this );
538                 this.on( 'scan', this.scanImage, this );
539         },
540
541         /**
542          * Trigger a scan of the embedded URL's content for metadata required to embed.
543          *
544          * @fires wp.media.controller.Embed#scan
545          */
546         scan: function() {
547                 var scanners,
548                         embed = this,
549                         attributes = {
550                                 type: 'link',
551                                 scanners: []
552                         };
553
554                 // Scan is triggered with the list of `attributes` to set on the
555                 // state, useful for the 'type' attribute and 'scanners' attribute,
556                 // an array of promise objects for asynchronous scan operations.
557                 if ( this.props.get('url') ) {
558                         this.trigger( 'scan', attributes );
559                 }
560
561                 if ( attributes.scanners.length ) {
562                         scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
563                         scanners.always( function() {
564                                 if ( embed.get('scanners') === scanners ) {
565                                         embed.set( 'loading', false );
566                                 }
567                         });
568                 } else {
569                         attributes.scanners = null;
570                 }
571
572                 attributes.loading = !! attributes.scanners;
573                 this.set( attributes );
574         },
575         /**
576          * Try scanning the embed as an image to discover its dimensions.
577          *
578          * @param {Object} attributes
579          */
580         scanImage: function( attributes ) {
581                 var frame = this.frame,
582                         state = this,
583                         url = this.props.get('url'),
584                         image = new Image(),
585                         deferred = $.Deferred();
586
587                 attributes.scanners.push( deferred.promise() );
588
589                 // Try to load the image and find its width/height.
590                 image.onload = function() {
591                         deferred.resolve();
592
593                         if ( state !== frame.state() || url !== state.props.get('url') ) {
594                                 return;
595                         }
596
597                         state.set({
598                                 type: 'image'
599                         });
600
601                         state.props.set({
602                                 width:  image.width,
603                                 height: image.height
604                         });
605                 };
606
607                 image.onerror = deferred.reject;
608                 image.src = url;
609         },
610
611         refresh: function() {
612                 this.frame.toolbar.get().refresh();
613         },
614
615         reset: function() {
616                 this.props.clear().set({ url: '' });
617
618                 if ( this.active ) {
619                         this.refresh();
620                 }
621         }
622 });
623
624 module.exports = Embed;
625
626 },{}],7:[function(require,module,exports){
627 /**
628  * wp.media.controller.FeaturedImage
629  *
630  * A state for selecting a featured image for a post.
631  *
632  * @class
633  * @augments wp.media.controller.Library
634  * @augments wp.media.controller.State
635  * @augments Backbone.Model
636  *
637  * @param {object}                     [attributes]                          The attributes hash passed to the state.
638  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
639  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
640  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
641  *                                                                           If one is not supplied, a collection of all images will be created.
642  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
643  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
644  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
645  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
646  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
647  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
648  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
649  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
650  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
651  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
652  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
653  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
654  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
655  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
656  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
657  */
658 var Attachment = wp.media.model.Attachment,
659         Library = wp.media.controller.Library,
660         l10n = wp.media.view.l10n,
661         FeaturedImage;
662
663 FeaturedImage = Library.extend({
664         defaults: _.defaults({
665                 id:            'featured-image',
666                 title:         l10n.setFeaturedImageTitle,
667                 multiple:      false,
668                 filterable:    'uploaded',
669                 toolbar:       'featured-image',
670                 priority:      60,
671                 syncSelection: true
672         }, Library.prototype.defaults ),
673
674         /**
675          * @since 3.5.0
676          */
677         initialize: function() {
678                 var library, comparator;
679
680                 // If we haven't been provided a `library`, create a `Selection`.
681                 if ( ! this.get('library') ) {
682                         this.set( 'library', wp.media.query({ type: 'image' }) );
683                 }
684
685                 Library.prototype.initialize.apply( this, arguments );
686
687                 library    = this.get('library');
688                 comparator = library.comparator;
689
690                 // Overload the library's comparator to push items that are not in
691                 // the mirrored query to the front of the aggregate collection.
692                 library.comparator = function( a, b ) {
693                         var aInQuery = !! this.mirroring.get( a.cid ),
694                                 bInQuery = !! this.mirroring.get( b.cid );
695
696                         if ( ! aInQuery && bInQuery ) {
697                                 return -1;
698                         } else if ( aInQuery && ! bInQuery ) {
699                                 return 1;
700                         } else {
701                                 return comparator.apply( this, arguments );
702                         }
703                 };
704
705                 // Add all items in the selection to the library, so any featured
706                 // images that are not initially loaded still appear.
707                 library.observe( this.get('selection') );
708         },
709
710         /**
711          * @since 3.5.0
712          */
713         activate: function() {
714                 this.updateSelection();
715                 this.frame.on( 'open', this.updateSelection, this );
716
717                 Library.prototype.activate.apply( this, arguments );
718         },
719
720         /**
721          * @since 3.5.0
722          */
723         deactivate: function() {
724                 this.frame.off( 'open', this.updateSelection, this );
725
726                 Library.prototype.deactivate.apply( this, arguments );
727         },
728
729         /**
730          * @since 3.5.0
731          */
732         updateSelection: function() {
733                 var selection = this.get('selection'),
734                         id = wp.media.view.settings.post.featuredImageId,
735                         attachment;
736
737                 if ( '' !== id && -1 !== id ) {
738                         attachment = Attachment.get( id );
739                         attachment.fetch();
740                 }
741
742                 selection.reset( attachment ? [ attachment ] : [] );
743         }
744 });
745
746 module.exports = FeaturedImage;
747
748 },{}],8:[function(require,module,exports){
749 /**
750  * wp.media.controller.GalleryAdd
751  *
752  * A state for selecting more images to add to a gallery.
753  *
754  * @class
755  * @augments wp.media.controller.Library
756  * @augments wp.media.controller.State
757  * @augments Backbone.Model
758  *
759  * @param {object}                     [attributes]                         The attributes hash passed to the state.
760  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
761  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
762  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
763  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
764  *                                                                          If one is not supplied, a collection of all images will be created.
765  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
766  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
767  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
768  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
769  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
770  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
771  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
772  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
773  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
774  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
775  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
776  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
777  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
778  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
779  */
780 var Selection = wp.media.model.Selection,
781         Library = wp.media.controller.Library,
782         l10n = wp.media.view.l10n,
783         GalleryAdd;
784
785 GalleryAdd = Library.extend({
786         defaults: _.defaults({
787                 id:            'gallery-library',
788                 title:         l10n.addToGalleryTitle,
789                 multiple:      'add',
790                 filterable:    'uploaded',
791                 menu:          'gallery',
792                 toolbar:       'gallery-add',
793                 priority:      100,
794                 syncSelection: false
795         }, Library.prototype.defaults ),
796
797         /**
798          * @since 3.5.0
799          */
800         initialize: function() {
801                 // If a library wasn't supplied, create a library of images.
802                 if ( ! this.get('library') ) {
803                         this.set( 'library', wp.media.query({ type: 'image' }) );
804                 }
805
806                 Library.prototype.initialize.apply( this, arguments );
807         },
808
809         /**
810          * @since 3.5.0
811          */
812         activate: function() {
813                 var library = this.get('library'),
814                         edit    = this.frame.state('gallery-edit').get('library');
815
816                 if ( this.editLibrary && this.editLibrary !== edit ) {
817                         library.unobserve( this.editLibrary );
818                 }
819
820                 // Accepts attachments that exist in the original library and
821                 // that do not exist in gallery's library.
822                 library.validator = function( attachment ) {
823                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
824                 };
825
826                 // Reset the library to ensure that all attachments are re-added
827                 // to the collection. Do so silently, as calling `observe` will
828                 // trigger the `reset` event.
829                 library.reset( library.mirroring.models, { silent: true });
830                 library.observe( edit );
831                 this.editLibrary = edit;
832
833                 Library.prototype.activate.apply( this, arguments );
834         }
835 });
836
837 module.exports = GalleryAdd;
838
839 },{}],9:[function(require,module,exports){
840 /**
841  * wp.media.controller.GalleryEdit
842  *
843  * A state for editing a gallery's images and settings.
844  *
845  * @class
846  * @augments wp.media.controller.Library
847  * @augments wp.media.controller.State
848  * @augments Backbone.Model
849  *
850  * @param {object}                     [attributes]                       The attributes hash passed to the state.
851  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
852  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
853  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
854  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
855  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
856  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
857  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
858  * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
859  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
860  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
861  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
862  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
863  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
864  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
865  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
866  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
867  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
868  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
869  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
870  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
871  */
872 var Library = wp.media.controller.Library,
873         l10n = wp.media.view.l10n,
874         GalleryEdit;
875
876 GalleryEdit = Library.extend({
877         defaults: {
878                 id:               'gallery-edit',
879                 title:            l10n.editGalleryTitle,
880                 multiple:         false,
881                 searchable:       false,
882                 sortable:         true,
883                 date:             false,
884                 display:          false,
885                 content:          'browse',
886                 toolbar:          'gallery-edit',
887                 describe:         true,
888                 displaySettings:  true,
889                 dragInfo:         true,
890                 idealColumnWidth: 170,
891                 editing:          false,
892                 priority:         60,
893                 syncSelection:    false
894         },
895
896         /**
897          * @since 3.5.0
898          */
899         initialize: function() {
900                 // If we haven't been provided a `library`, create a `Selection`.
901                 if ( ! this.get('library') ) {
902                         this.set( 'library', new wp.media.model.Selection() );
903                 }
904
905                 // The single `Attachment` view to be used in the `Attachments` view.
906                 if ( ! this.get('AttachmentView') ) {
907                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
908                 }
909
910                 Library.prototype.initialize.apply( this, arguments );
911         },
912
913         /**
914          * @since 3.5.0
915          */
916         activate: function() {
917                 var library = this.get('library');
918
919                 // Limit the library to images only.
920                 library.props.set( 'type', 'image' );
921
922                 // Watch for uploaded attachments.
923                 this.get('library').observe( wp.Uploader.queue );
924
925                 this.frame.on( 'content:render:browse', this.gallerySettings, this );
926
927                 Library.prototype.activate.apply( this, arguments );
928         },
929
930         /**
931          * @since 3.5.0
932          */
933         deactivate: function() {
934                 // Stop watching for uploaded attachments.
935                 this.get('library').unobserve( wp.Uploader.queue );
936
937                 this.frame.off( 'content:render:browse', this.gallerySettings, this );
938
939                 Library.prototype.deactivate.apply( this, arguments );
940         },
941
942         /**
943          * @since 3.5.0
944          *
945          * @param browser
946          */
947         gallerySettings: function( browser ) {
948                 if ( ! this.get('displaySettings') ) {
949                         return;
950                 }
951
952                 var library = this.get('library');
953
954                 if ( ! library || ! browser ) {
955                         return;
956                 }
957
958                 library.gallery = library.gallery || new Backbone.Model();
959
960                 browser.sidebar.set({
961                         gallery: new wp.media.view.Settings.Gallery({
962                                 controller: this,
963                                 model:      library.gallery,
964                                 priority:   40
965                         })
966                 });
967
968                 browser.toolbar.set( 'reverse', {
969                         text:     l10n.reverseOrder,
970                         priority: 80,
971
972                         click: function() {
973                                 library.reset( library.toArray().reverse() );
974                         }
975                 });
976         }
977 });
978
979 module.exports = GalleryEdit;
980
981 },{}],10:[function(require,module,exports){
982 /**
983  * wp.media.controller.ImageDetails
984  *
985  * A state for editing the attachment display settings of an image that's been
986  * inserted into the editor.
987  *
988  * @class
989  * @augments wp.media.controller.State
990  * @augments Backbone.Model
991  *
992  * @param {object}                    [attributes]                       The attributes hash passed to the state.
993  * @param {string}                    [attributes.id=image-details]      Unique identifier.
994  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
995  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
996  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
997  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
998  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
999  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
1000  * @param {boolean}                   [attributes.editing=false]         Unused.
1001  * @param {int}                       [attributes.priority=60]           Unused.
1002  *
1003  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
1004  *       however this may not do anything.
1005  */
1006 var State = wp.media.controller.State,
1007         Library = wp.media.controller.Library,
1008         l10n = wp.media.view.l10n,
1009         ImageDetails;
1010
1011 ImageDetails = State.extend({
1012         defaults: _.defaults({
1013                 id:       'image-details',
1014                 title:    l10n.imageDetailsTitle,
1015                 content:  'image-details',
1016                 menu:     false,
1017                 router:   false,
1018                 toolbar:  'image-details',
1019                 editing:  false,
1020                 priority: 60
1021         }, Library.prototype.defaults ),
1022
1023         /**
1024          * @since 3.9.0
1025          *
1026          * @param options Attributes
1027          */
1028         initialize: function( options ) {
1029                 this.image = options.image;
1030                 State.prototype.initialize.apply( this, arguments );
1031         },
1032
1033         /**
1034          * @since 3.9.0
1035          */
1036         activate: function() {
1037                 this.frame.modal.$el.addClass('image-details');
1038         }
1039 });
1040
1041 module.exports = ImageDetails;
1042
1043 },{}],11:[function(require,module,exports){
1044 /**
1045  * wp.media.controller.Library
1046  *
1047  * A state for choosing an attachment or group of attachments from the media library.
1048  *
1049  * @class
1050  * @augments wp.media.controller.State
1051  * @augments Backbone.Model
1052  * @mixes media.selectionSync
1053  *
1054  * @param {object}                          [attributes]                         The attributes hash passed to the state.
1055  * @param {string}                          [attributes.id=library]              Unique identifier.
1056  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
1057  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
1058  *                                                                               If one is not supplied, a collection of all attachments will be created.
1059  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
1060  *                                                                               If the 'selection' attribute is a plain JS object,
1061  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
1062  *                                                                               Otherwise, it will copy the library's `props` model.
1063  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
1064  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
1065  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
1066  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
1067  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
1068  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
1069  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
1070  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
1071  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
1072  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1073  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1074  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1075  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1076  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1077  */
1078 var l10n = wp.media.view.l10n,
1079         getUserSetting = window.getUserSetting,
1080         setUserSetting = window.setUserSetting,
1081         Library;
1082
1083 Library = wp.media.controller.State.extend({
1084         defaults: {
1085                 id:                 'library',
1086                 title:              l10n.mediaLibraryTitle,
1087                 multiple:           false,
1088                 content:            'upload',
1089                 menu:               'default',
1090                 router:             'browse',
1091                 toolbar:            'select',
1092                 searchable:         true,
1093                 filterable:         false,
1094                 sortable:           true,
1095                 autoSelect:         true,
1096                 describe:           false,
1097                 contentUserSetting: true,
1098                 syncSelection:      true
1099         },
1100
1101         /**
1102          * If a library isn't provided, query all media items.
1103          * If a selection instance isn't provided, create one.
1104          *
1105          * @since 3.5.0
1106          */
1107         initialize: function() {
1108                 var selection = this.get('selection'),
1109                         props;
1110
1111                 if ( ! this.get('library') ) {
1112                         this.set( 'library', wp.media.query() );
1113                 }
1114
1115                 if ( ! ( selection instanceof wp.media.model.Selection ) ) {
1116                         props = selection;
1117
1118                         if ( ! props ) {
1119                                 props = this.get('library').props.toJSON();
1120                                 props = _.omit( props, 'orderby', 'query' );
1121                         }
1122
1123                         this.set( 'selection', new wp.media.model.Selection( null, {
1124                                 multiple: this.get('multiple'),
1125                                 props: props
1126                         }) );
1127                 }
1128
1129                 this.resetDisplays();
1130         },
1131
1132         /**
1133          * @since 3.5.0
1134          */
1135         activate: function() {
1136                 this.syncSelection();
1137
1138                 wp.Uploader.queue.on( 'add', this.uploading, this );
1139
1140                 this.get('selection').on( 'add remove reset', this.refreshContent, this );
1141
1142                 if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
1143                         this.frame.on( 'content:activate', this.saveContentMode, this );
1144                         this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
1145                 }
1146         },
1147
1148         /**
1149          * @since 3.5.0
1150          */
1151         deactivate: function() {
1152                 this.recordSelection();
1153
1154                 this.frame.off( 'content:activate', this.saveContentMode, this );
1155
1156                 // Unbind all event handlers that use this state as the context
1157                 // from the selection.
1158                 this.get('selection').off( null, null, this );
1159
1160                 wp.Uploader.queue.off( null, null, this );
1161         },
1162
1163         /**
1164          * Reset the library to its initial state.
1165          *
1166          * @since 3.5.0
1167          */
1168         reset: function() {
1169                 this.get('selection').reset();
1170                 this.resetDisplays();
1171                 this.refreshContent();
1172         },
1173
1174         /**
1175          * Reset the attachment display settings defaults to the site options.
1176          *
1177          * If site options don't define them, fall back to a persistent user setting.
1178          *
1179          * @since 3.5.0
1180          */
1181         resetDisplays: function() {
1182                 var defaultProps = wp.media.view.settings.defaultProps;
1183                 this._displays = [];
1184                 this._defaultDisplaySettings = {
1185                         align: getUserSetting( 'align', defaultProps.align ) || 'none',
1186                         size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
1187                         link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
1188                 };
1189         },
1190
1191         /**
1192          * Create a model to represent display settings (alignment, etc.) for an attachment.
1193          *
1194          * @since 3.5.0
1195          *
1196          * @param {wp.media.model.Attachment} attachment
1197          * @returns {Backbone.Model}
1198          */
1199         display: function( attachment ) {
1200                 var displays = this._displays;
1201
1202                 if ( ! displays[ attachment.cid ] ) {
1203                         displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
1204                 }
1205                 return displays[ attachment.cid ];
1206         },
1207
1208         /**
1209          * Given an attachment, create attachment display settings properties.
1210          *
1211          * @since 3.6.0
1212          *
1213          * @param {wp.media.model.Attachment} attachment
1214          * @returns {Object}
1215          */
1216         defaultDisplaySettings: function( attachment ) {
1217                 var settings = _.clone( this._defaultDisplaySettings );
1218
1219                 if ( settings.canEmbed = this.canEmbed( attachment ) ) {
1220                         settings.link = 'embed';
1221                 } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
1222                         settings.link = 'file';
1223                 }
1224
1225                 return settings;
1226         },
1227
1228         /**
1229          * Whether an attachment is image.
1230          *
1231          * @since 4.4.1
1232          *
1233          * @param {wp.media.model.Attachment} attachment
1234          * @returns {Boolean}
1235          */
1236         isImageAttachment: function( attachment ) {
1237                 // If uploading, we know the filename but not the mime type.
1238                 if ( attachment.get('uploading') ) {
1239                         return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
1240                 }
1241
1242                 return attachment.get('type') === 'image';
1243         },
1244
1245         /**
1246          * Whether an attachment can be embedded (audio or video).
1247          *
1248          * @since 3.6.0
1249          *
1250          * @param {wp.media.model.Attachment} attachment
1251          * @returns {Boolean}
1252          */
1253         canEmbed: function( attachment ) {
1254                 // If uploading, we know the filename but not the mime type.
1255                 if ( ! attachment.get('uploading') ) {
1256                         var type = attachment.get('type');
1257                         if ( type !== 'audio' && type !== 'video' ) {
1258                                 return false;
1259                         }
1260                 }
1261
1262                 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
1263         },
1264
1265
1266         /**
1267          * If the state is active, no items are selected, and the current
1268          * content mode is not an option in the state's router (provided
1269          * the state has a router), reset the content mode to the default.
1270          *
1271          * @since 3.5.0
1272          */
1273         refreshContent: function() {
1274                 var selection = this.get('selection'),
1275                         frame = this.frame,
1276                         router = frame.router.get(),
1277                         mode = frame.content.mode();
1278
1279                 if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
1280                         this.frame.content.render( this.get('content') );
1281                 }
1282         },
1283
1284         /**
1285          * Callback handler when an attachment is uploaded.
1286          *
1287          * Switch to the Media Library if uploaded from the 'Upload Files' tab.
1288          *
1289          * Adds any uploading attachments to the selection.
1290          *
1291          * If the state only supports one attachment to be selected and multiple
1292          * attachments are uploaded, the last attachment in the upload queue will
1293          * be selected.
1294          *
1295          * @since 3.5.0
1296          *
1297          * @param {wp.media.model.Attachment} attachment
1298          */
1299         uploading: function( attachment ) {
1300                 var content = this.frame.content;
1301
1302                 if ( 'upload' === content.mode() ) {
1303                         this.frame.content.mode('browse');
1304                 }
1305
1306                 if ( this.get( 'autoSelect' ) ) {
1307                         this.get('selection').add( attachment );
1308                         this.frame.trigger( 'library:selection:add' );
1309                 }
1310         },
1311
1312         /**
1313          * Persist the mode of the content region as a user setting.
1314          *
1315          * @since 3.5.0
1316          */
1317         saveContentMode: function() {
1318                 if ( 'browse' !== this.get('router') ) {
1319                         return;
1320                 }
1321
1322                 var mode = this.frame.content.mode(),
1323                         view = this.frame.router.get();
1324
1325                 if ( view && view.get( mode ) ) {
1326                         setUserSetting( 'libraryContent', mode );
1327                 }
1328         }
1329 });
1330
1331 // Make selectionSync available on any Media Library state.
1332 _.extend( Library.prototype, wp.media.selectionSync );
1333
1334 module.exports = Library;
1335
1336 },{}],12:[function(require,module,exports){
1337 /**
1338  * wp.media.controller.MediaLibrary
1339  *
1340  * @class
1341  * @augments wp.media.controller.Library
1342  * @augments wp.media.controller.State
1343  * @augments Backbone.Model
1344  */
1345 var Library = wp.media.controller.Library,
1346         MediaLibrary;
1347
1348 MediaLibrary = Library.extend({
1349         defaults: _.defaults({
1350                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
1351                 filterable:      'uploaded',
1352
1353                 displaySettings: false,
1354                 priority:        80,
1355                 syncSelection:   false
1356         }, Library.prototype.defaults ),
1357
1358         /**
1359          * @since 3.9.0
1360          *
1361          * @param options
1362          */
1363         initialize: function( options ) {
1364                 this.media = options.media;
1365                 this.type = options.type;
1366                 this.set( 'library', wp.media.query({ type: this.type }) );
1367
1368                 Library.prototype.initialize.apply( this, arguments );
1369         },
1370
1371         /**
1372          * @since 3.9.0
1373          */
1374         activate: function() {
1375                 // @todo this should use this.frame.
1376                 if ( wp.media.frame.lastMime ) {
1377                         this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
1378                         delete wp.media.frame.lastMime;
1379                 }
1380                 Library.prototype.activate.apply( this, arguments );
1381         }
1382 });
1383
1384 module.exports = MediaLibrary;
1385
1386 },{}],13:[function(require,module,exports){
1387 /**
1388  * wp.media.controller.Region
1389  *
1390  * A region is a persistent application layout area.
1391  *
1392  * A region assumes one mode at any time, and can be switched to another.
1393  *
1394  * When mode changes, events are triggered on the region's parent view.
1395  * The parent view will listen to specific events and fill the region with an
1396  * appropriate view depending on mode. For example, a frame listens for the
1397  * 'browse' mode t be activated on the 'content' view and then fills the region
1398  * with an AttachmentsBrowser view.
1399  *
1400  * @class
1401  *
1402  * @param {object}        options          Options hash for the region.
1403  * @param {string}        options.id       Unique identifier for the region.
1404  * @param {Backbone.View} options.view     A parent view the region exists within.
1405  * @param {string}        options.selector jQuery selector for the region within the parent view.
1406  */
1407 var Region = function( options ) {
1408         _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
1409 };
1410
1411 // Use Backbone's self-propagating `extend` inheritance method.
1412 Region.extend = Backbone.Model.extend;
1413
1414 _.extend( Region.prototype, {
1415         /**
1416          * Activate a mode.
1417          *
1418          * @since 3.5.0
1419          *
1420          * @param {string} mode
1421          *
1422          * @fires this.view#{this.id}:activate:{this._mode}
1423          * @fires this.view#{this.id}:activate
1424          * @fires this.view#{this.id}:deactivate:{this._mode}
1425          * @fires this.view#{this.id}:deactivate
1426          *
1427          * @returns {wp.media.controller.Region} Returns itself to allow chaining.
1428          */
1429         mode: function( mode ) {
1430                 if ( ! mode ) {
1431                         return this._mode;
1432                 }
1433                 // Bail if we're trying to change to the current mode.
1434                 if ( mode === this._mode ) {
1435                         return this;
1436                 }
1437
1438                 /**
1439                  * Region mode deactivation event.
1440                  *
1441                  * @event this.view#{this.id}:deactivate:{this._mode}
1442                  * @event this.view#{this.id}:deactivate
1443                  */
1444                 this.trigger('deactivate');
1445
1446                 this._mode = mode;
1447                 this.render( mode );
1448
1449                 /**
1450                  * Region mode activation event.
1451                  *
1452                  * @event this.view#{this.id}:activate:{this._mode}
1453                  * @event this.view#{this.id}:activate
1454                  */
1455                 this.trigger('activate');
1456                 return this;
1457         },
1458         /**
1459          * Render a mode.
1460          *
1461          * @since 3.5.0
1462          *
1463          * @param {string} mode
1464          *
1465          * @fires this.view#{this.id}:create:{this._mode}
1466          * @fires this.view#{this.id}:create
1467          * @fires this.view#{this.id}:render:{this._mode}
1468          * @fires this.view#{this.id}:render
1469          *
1470          * @returns {wp.media.controller.Region} Returns itself to allow chaining
1471          */
1472         render: function( mode ) {
1473                 // If the mode isn't active, activate it.
1474                 if ( mode && mode !== this._mode ) {
1475                         return this.mode( mode );
1476                 }
1477
1478                 var set = { view: null },
1479                         view;
1480
1481                 /**
1482                  * Create region view event.
1483                  *
1484                  * Region view creation takes place in an event callback on the frame.
1485                  *
1486                  * @event this.view#{this.id}:create:{this._mode}
1487                  * @event this.view#{this.id}:create
1488                  */
1489                 this.trigger( 'create', set );
1490                 view = set.view;
1491
1492                 /**
1493                  * Render region view event.
1494                  *
1495                  * Region view creation takes place in an event callback on the frame.
1496                  *
1497                  * @event this.view#{this.id}:create:{this._mode}
1498                  * @event this.view#{this.id}:create
1499                  */
1500                 this.trigger( 'render', view );
1501                 if ( view ) {
1502                         this.set( view );
1503                 }
1504                 return this;
1505         },
1506
1507         /**
1508          * Get the region's view.
1509          *
1510          * @since 3.5.0
1511          *
1512          * @returns {wp.media.View}
1513          */
1514         get: function() {
1515                 return this.view.views.first( this.selector );
1516         },
1517
1518         /**
1519          * Set the region's view as a subview of the frame.
1520          *
1521          * @since 3.5.0
1522          *
1523          * @param {Array|Object} views
1524          * @param {Object} [options={}]
1525          * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
1526          */
1527         set: function( views, options ) {
1528                 if ( options ) {
1529                         options.add = false;
1530                 }
1531                 return this.view.views.set( this.selector, views, options );
1532         },
1533
1534         /**
1535          * Trigger regional view events on the frame.
1536          *
1537          * @since 3.5.0
1538          *
1539          * @param {string} event
1540          * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
1541          */
1542         trigger: function( event ) {
1543                 var base, args;
1544
1545                 if ( ! this._mode ) {
1546                         return;
1547                 }
1548
1549                 args = _.toArray( arguments );
1550                 base = this.id + ':' + event;
1551
1552                 // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
1553                 args[0] = base + ':' + this._mode;
1554                 this.view.trigger.apply( this.view, args );
1555
1556                 // Trigger `{this.id}:{event}` event on the frame.
1557                 args[0] = base;
1558                 this.view.trigger.apply( this.view, args );
1559                 return this;
1560         }
1561 });
1562
1563 module.exports = Region;
1564
1565 },{}],14:[function(require,module,exports){
1566 /**
1567  * wp.media.controller.ReplaceImage
1568  *
1569  * A state for replacing an image.
1570  *
1571  * @class
1572  * @augments wp.media.controller.Library
1573  * @augments wp.media.controller.State
1574  * @augments Backbone.Model
1575  *
1576  * @param {object}                     [attributes]                         The attributes hash passed to the state.
1577  * @param {string}                     [attributes.id=replace-image]        Unique identifier.
1578  * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
1579  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
1580  *                                                                          If one is not supplied, a collection of all images will be created.
1581  * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
1582  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
1583  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
1584  * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
1585  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
1586  * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
1587  * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
1588  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
1589  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
1590  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
1591  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1592  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1593  * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1594  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1595  * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1596  */
1597 var Library = wp.media.controller.Library,
1598         l10n = wp.media.view.l10n,
1599         ReplaceImage;
1600
1601 ReplaceImage = Library.extend({
1602         defaults: _.defaults({
1603                 id:            'replace-image',
1604                 title:         l10n.replaceImageTitle,
1605                 multiple:      false,
1606                 filterable:    'uploaded',
1607                 toolbar:       'replace',
1608                 menu:          false,
1609                 priority:      60,
1610                 syncSelection: true
1611         }, Library.prototype.defaults ),
1612
1613         /**
1614          * @since 3.9.0
1615          *
1616          * @param options
1617          */
1618         initialize: function( options ) {
1619                 var library, comparator;
1620
1621                 this.image = options.image;
1622                 // If we haven't been provided a `library`, create a `Selection`.
1623                 if ( ! this.get('library') ) {
1624                         this.set( 'library', wp.media.query({ type: 'image' }) );
1625                 }
1626
1627                 Library.prototype.initialize.apply( this, arguments );
1628
1629                 library    = this.get('library');
1630                 comparator = library.comparator;
1631
1632                 // Overload the library's comparator to push items that are not in
1633                 // the mirrored query to the front of the aggregate collection.
1634                 library.comparator = function( a, b ) {
1635                         var aInQuery = !! this.mirroring.get( a.cid ),
1636                                 bInQuery = !! this.mirroring.get( b.cid );
1637
1638                         if ( ! aInQuery && bInQuery ) {
1639                                 return -1;
1640                         } else if ( aInQuery && ! bInQuery ) {
1641                                 return 1;
1642                         } else {
1643                                 return comparator.apply( this, arguments );
1644                         }
1645                 };
1646
1647                 // Add all items in the selection to the library, so any featured
1648                 // images that are not initially loaded still appear.
1649                 library.observe( this.get('selection') );
1650         },
1651
1652         /**
1653          * @since 3.9.0
1654          */
1655         activate: function() {
1656                 this.updateSelection();
1657                 Library.prototype.activate.apply( this, arguments );
1658         },
1659
1660         /**
1661          * @since 3.9.0
1662          */
1663         updateSelection: function() {
1664                 var selection = this.get('selection'),
1665                         attachment = this.image.attachment;
1666
1667                 selection.reset( attachment ? [ attachment ] : [] );
1668         }
1669 });
1670
1671 module.exports = ReplaceImage;
1672
1673 },{}],15:[function(require,module,exports){
1674 /**
1675  * wp.media.controller.SiteIconCropper
1676  *
1677  * A state for cropping a Site Icon.
1678  *
1679  * @class
1680  * @augments wp.media.controller.Cropper
1681  * @augments wp.media.controller.State
1682  * @augments Backbone.Model
1683  */
1684 var Controller = wp.media.controller,
1685         SiteIconCropper;
1686
1687 SiteIconCropper = Controller.Cropper.extend({
1688         activate: function() {
1689                 this.frame.on( 'content:create:crop', this.createCropContent, this );
1690                 this.frame.on( 'close', this.removeCropper, this );
1691                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
1692         },
1693
1694         createCropContent: function() {
1695                 this.cropperView = new wp.media.view.SiteIconCropper({
1696                         controller: this,
1697                         attachment: this.get('selection').first()
1698                 });
1699                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
1700                 this.frame.content.set(this.cropperView);
1701
1702         },
1703
1704         doCrop: function( attachment ) {
1705                 var cropDetails = attachment.get( 'cropDetails' ),
1706                         control = this.get( 'control' );
1707
1708                 cropDetails.dst_width  = control.params.width;
1709                 cropDetails.dst_height = control.params.height;
1710
1711                 return wp.ajax.post( 'crop-image', {
1712                         nonce: attachment.get( 'nonces' ).edit,
1713                         id: attachment.get( 'id' ),
1714                         context: 'site-icon',
1715                         cropDetails: cropDetails
1716                 } );
1717         }
1718 });
1719
1720 module.exports = SiteIconCropper;
1721
1722 },{}],16:[function(require,module,exports){
1723 /**
1724  * wp.media.controller.StateMachine
1725  *
1726  * A state machine keeps track of state. It is in one state at a time,
1727  * and can change from one state to another.
1728  *
1729  * States are stored as models in a Backbone collection.
1730  *
1731  * @since 3.5.0
1732  *
1733  * @class
1734  * @augments Backbone.Model
1735  * @mixin
1736  * @mixes Backbone.Events
1737  *
1738  * @param {Array} states
1739  */
1740 var StateMachine = function( states ) {
1741         // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
1742         this.states = new Backbone.Collection( states );
1743 };
1744
1745 // Use Backbone's self-propagating `extend` inheritance method.
1746 StateMachine.extend = Backbone.Model.extend;
1747
1748 _.extend( StateMachine.prototype, Backbone.Events, {
1749         /**
1750          * Fetch a state.
1751          *
1752          * If no `id` is provided, returns the active state.
1753          *
1754          * Implicitly creates states.
1755          *
1756          * Ensure that the `states` collection exists so the `StateMachine`
1757          *   can be used as a mixin.
1758          *
1759          * @since 3.5.0
1760          *
1761          * @param {string} id
1762          * @returns {wp.media.controller.State} Returns a State model
1763          *   from the StateMachine collection
1764          */
1765         state: function( id ) {
1766                 this.states = this.states || new Backbone.Collection();
1767
1768                 // Default to the active state.
1769                 id = id || this._state;
1770
1771                 if ( id && ! this.states.get( id ) ) {
1772                         this.states.add({ id: id });
1773                 }
1774                 return this.states.get( id );
1775         },
1776
1777         /**
1778          * Sets the active state.
1779          *
1780          * Bail if we're trying to select the current state, if we haven't
1781          * created the `states` collection, or are trying to select a state
1782          * that does not exist.
1783          *
1784          * @since 3.5.0
1785          *
1786          * @param {string} id
1787          *
1788          * @fires wp.media.controller.State#deactivate
1789          * @fires wp.media.controller.State#activate
1790          *
1791          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
1792          */
1793         setState: function( id ) {
1794                 var previous = this.state();
1795
1796                 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
1797                         return this;
1798                 }
1799
1800                 if ( previous ) {
1801                         previous.trigger('deactivate');
1802                         this._lastState = previous.id;
1803                 }
1804
1805                 this._state = id;
1806                 this.state().trigger('activate');
1807
1808                 return this;
1809         },
1810
1811         /**
1812          * Returns the previous active state.
1813          *
1814          * Call the `state()` method with no parameters to retrieve the current
1815          * active state.
1816          *
1817          * @since 3.5.0
1818          *
1819          * @returns {wp.media.controller.State} Returns a State model
1820          *    from the StateMachine collection
1821          */
1822         lastState: function() {
1823                 if ( this._lastState ) {
1824                         return this.state( this._lastState );
1825                 }
1826         }
1827 });
1828
1829 // Map all event binding and triggering on a StateMachine to its `states` collection.
1830 _.each([ 'on', 'off', 'trigger' ], function( method ) {
1831         /**
1832          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
1833          */
1834         StateMachine.prototype[ method ] = function() {
1835                 // Ensure that the `states` collection exists so the `StateMachine`
1836                 // can be used as a mixin.
1837                 this.states = this.states || new Backbone.Collection();
1838                 // Forward the method to the `states` collection.
1839                 this.states[ method ].apply( this.states, arguments );
1840                 return this;
1841         };
1842 });
1843
1844 module.exports = StateMachine;
1845
1846 },{}],17:[function(require,module,exports){
1847 /**
1848  * wp.media.controller.State
1849  *
1850  * A state is a step in a workflow that when set will trigger the controllers
1851  * for the regions to be updated as specified in the frame.
1852  *
1853  * A state has an event-driven lifecycle:
1854  *
1855  *     'ready'      triggers when a state is added to a state machine's collection.
1856  *     'activate'   triggers when a state is activated by a state machine.
1857  *     'deactivate' triggers when a state is deactivated by a state machine.
1858  *     'reset'      is not triggered automatically. It should be invoked by the
1859  *                  proper controller to reset the state to its default.
1860  *
1861  * @class
1862  * @augments Backbone.Model
1863  */
1864 var State = Backbone.Model.extend({
1865         /**
1866          * Constructor.
1867          *
1868          * @since 3.5.0
1869          */
1870         constructor: function() {
1871                 this.on( 'activate', this._preActivate, this );
1872                 this.on( 'activate', this.activate, this );
1873                 this.on( 'activate', this._postActivate, this );
1874                 this.on( 'deactivate', this._deactivate, this );
1875                 this.on( 'deactivate', this.deactivate, this );
1876                 this.on( 'reset', this.reset, this );
1877                 this.on( 'ready', this._ready, this );
1878                 this.on( 'ready', this.ready, this );
1879                 /**
1880                  * Call parent constructor with passed arguments
1881                  */
1882                 Backbone.Model.apply( this, arguments );
1883                 this.on( 'change:menu', this._updateMenu, this );
1884         },
1885         /**
1886          * Ready event callback.
1887          *
1888          * @abstract
1889          * @since 3.5.0
1890          */
1891         ready: function() {},
1892
1893         /**
1894          * Activate event callback.
1895          *
1896          * @abstract
1897          * @since 3.5.0
1898          */
1899         activate: function() {},
1900
1901         /**
1902          * Deactivate event callback.
1903          *
1904          * @abstract
1905          * @since 3.5.0
1906          */
1907         deactivate: function() {},
1908
1909         /**
1910          * Reset event callback.
1911          *
1912          * @abstract
1913          * @since 3.5.0
1914          */
1915         reset: function() {},
1916
1917         /**
1918          * @access private
1919          * @since 3.5.0
1920          */
1921         _ready: function() {
1922                 this._updateMenu();
1923         },
1924
1925         /**
1926          * @access private
1927          * @since 3.5.0
1928         */
1929         _preActivate: function() {
1930                 this.active = true;
1931         },
1932
1933         /**
1934          * @access private
1935          * @since 3.5.0
1936          */
1937         _postActivate: function() {
1938                 this.on( 'change:menu', this._menu, this );
1939                 this.on( 'change:titleMode', this._title, this );
1940                 this.on( 'change:content', this._content, this );
1941                 this.on( 'change:toolbar', this._toolbar, this );
1942
1943                 this.frame.on( 'title:render:default', this._renderTitle, this );
1944
1945                 this._title();
1946                 this._menu();
1947                 this._toolbar();
1948                 this._content();
1949                 this._router();
1950         },
1951
1952         /**
1953          * @access private
1954          * @since 3.5.0
1955          */
1956         _deactivate: function() {
1957                 this.active = false;
1958
1959                 this.frame.off( 'title:render:default', this._renderTitle, this );
1960
1961                 this.off( 'change:menu', this._menu, this );
1962                 this.off( 'change:titleMode', this._title, this );
1963                 this.off( 'change:content', this._content, this );
1964                 this.off( 'change:toolbar', this._toolbar, this );
1965         },
1966
1967         /**
1968          * @access private
1969          * @since 3.5.0
1970          */
1971         _title: function() {
1972                 this.frame.title.render( this.get('titleMode') || 'default' );
1973         },
1974
1975         /**
1976          * @access private
1977          * @since 3.5.0
1978          */
1979         _renderTitle: function( view ) {
1980                 view.$el.text( this.get('title') || '' );
1981         },
1982
1983         /**
1984          * @access private
1985          * @since 3.5.0
1986          */
1987         _router: function() {
1988                 var router = this.frame.router,
1989                         mode = this.get('router'),
1990                         view;
1991
1992                 this.frame.$el.toggleClass( 'hide-router', ! mode );
1993                 if ( ! mode ) {
1994                         return;
1995                 }
1996
1997                 this.frame.router.render( mode );
1998
1999                 view = router.get();
2000                 if ( view && view.select ) {
2001                         view.select( this.frame.content.mode() );
2002                 }
2003         },
2004
2005         /**
2006          * @access private
2007          * @since 3.5.0
2008          */
2009         _menu: function() {
2010                 var menu = this.frame.menu,
2011                         mode = this.get('menu'),
2012                         view;
2013
2014                 this.frame.$el.toggleClass( 'hide-menu', ! mode );
2015                 if ( ! mode ) {
2016                         return;
2017                 }
2018
2019                 menu.mode( mode );
2020
2021                 view = menu.get();
2022                 if ( view && view.select ) {
2023                         view.select( this.id );
2024                 }
2025         },
2026
2027         /**
2028          * @access private
2029          * @since 3.5.0
2030          */
2031         _updateMenu: function() {
2032                 var previous = this.previous('menu'),
2033                         menu = this.get('menu');
2034
2035                 if ( previous ) {
2036                         this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
2037                 }
2038
2039                 if ( menu ) {
2040                         this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
2041                 }
2042         },
2043
2044         /**
2045          * Create a view in the media menu for the state.
2046          *
2047          * @access private
2048          * @since 3.5.0
2049          *
2050          * @param {media.view.Menu} view The menu view.
2051          */
2052         _renderMenu: function( view ) {
2053                 var menuItem = this.get('menuItem'),
2054                         title = this.get('title'),
2055                         priority = this.get('priority');
2056
2057                 if ( ! menuItem && title ) {
2058                         menuItem = { text: title };
2059
2060                         if ( priority ) {
2061                                 menuItem.priority = priority;
2062                         }
2063                 }
2064
2065                 if ( ! menuItem ) {
2066                         return;
2067                 }
2068
2069                 view.set( this.id, menuItem );
2070         }
2071 });
2072
2073 _.each(['toolbar','content'], function( region ) {
2074         /**
2075          * @access private
2076          */
2077         State.prototype[ '_' + region ] = function() {
2078                 var mode = this.get( region );
2079                 if ( mode ) {
2080                         this.frame[ region ].render( mode );
2081                 }
2082         };
2083 });
2084
2085 module.exports = State;
2086
2087 },{}],18:[function(require,module,exports){
2088 /**
2089  * wp.media.selectionSync
2090  *
2091  * Sync an attachments selection in a state with another state.
2092  *
2093  * Allows for selecting multiple images in the Insert Media workflow, and then
2094  * switching to the Insert Gallery workflow while preserving the attachments selection.
2095  *
2096  * @mixin
2097  */
2098 var selectionSync = {
2099         /**
2100          * @since 3.5.0
2101          */
2102         syncSelection: function() {
2103                 var selection = this.get('selection'),
2104                         manager = this.frame._selection;
2105
2106                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2107                         return;
2108                 }
2109
2110                 // If the selection supports multiple items, validate the stored
2111                 // attachments based on the new selection's conditions. Record
2112                 // the attachments that are not included; we'll maintain a
2113                 // reference to those. Other attachments are considered in flux.
2114                 if ( selection.multiple ) {
2115                         selection.reset( [], { silent: true });
2116                         selection.validateAll( manager.attachments );
2117                         manager.difference = _.difference( manager.attachments.models, selection.models );
2118                 }
2119
2120                 // Sync the selection's single item with the master.
2121                 selection.single( manager.single );
2122         },
2123
2124         /**
2125          * Record the currently active attachments, which is a combination
2126          * of the selection's attachments and the set of selected
2127          * attachments that this specific selection considered invalid.
2128          * Reset the difference and record the single attachment.
2129          *
2130          * @since 3.5.0
2131          */
2132         recordSelection: function() {
2133                 var selection = this.get('selection'),
2134                         manager = this.frame._selection;
2135
2136                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2137                         return;
2138                 }
2139
2140                 if ( selection.multiple ) {
2141                         manager.attachments.reset( selection.toArray().concat( manager.difference ) );
2142                         manager.difference = [];
2143                 } else {
2144                         manager.attachments.add( selection.toArray() );
2145                 }
2146
2147                 manager.single = selection._single;
2148         }
2149 };
2150
2151 module.exports = selectionSync;
2152
2153 },{}],19:[function(require,module,exports){
2154 var media = wp.media,
2155         $ = jQuery,
2156         l10n;
2157
2158 media.isTouchDevice = ( 'ontouchend' in document );
2159
2160 // Link any localized strings.
2161 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
2162
2163 // Link any settings.
2164 media.view.settings = l10n.settings || {};
2165 delete l10n.settings;
2166
2167 // Copy the `post` setting over to the model settings.
2168 media.model.settings.post = media.view.settings.post;
2169
2170 // Check if the browser supports CSS 3.0 transitions
2171 $.support.transition = (function(){
2172         var style = document.documentElement.style,
2173                 transitions = {
2174                         WebkitTransition: 'webkitTransitionEnd',
2175                         MozTransition:    'transitionend',
2176                         OTransition:      'oTransitionEnd otransitionend',
2177                         transition:       'transitionend'
2178                 }, transition;
2179
2180         transition = _.find( _.keys( transitions ), function( transition ) {
2181                 return ! _.isUndefined( style[ transition ] );
2182         });
2183
2184         return transition && {
2185                 end: transitions[ transition ]
2186         };
2187 }());
2188
2189 /**
2190  * A shared event bus used to provide events into
2191  * the media workflows that 3rd-party devs can use to hook
2192  * in.
2193  */
2194 media.events = _.extend( {}, Backbone.Events );
2195
2196 /**
2197  * Makes it easier to bind events using transitions.
2198  *
2199  * @param {string} selector
2200  * @param {Number} sensitivity
2201  * @returns {Promise}
2202  */
2203 media.transition = function( selector, sensitivity ) {
2204         var deferred = $.Deferred();
2205
2206         sensitivity = sensitivity || 2000;
2207
2208         if ( $.support.transition ) {
2209                 if ( ! (selector instanceof $) ) {
2210                         selector = $( selector );
2211                 }
2212
2213                 // Resolve the deferred when the first element finishes animating.
2214                 selector.first().one( $.support.transition.end, deferred.resolve );
2215
2216                 // Just in case the event doesn't trigger, fire a callback.
2217                 _.delay( deferred.resolve, sensitivity );
2218
2219         // Otherwise, execute on the spot.
2220         } else {
2221                 deferred.resolve();
2222         }
2223
2224         return deferred.promise();
2225 };
2226
2227 media.controller.Region = require( './controllers/region.js' );
2228 media.controller.StateMachine = require( './controllers/state-machine.js' );
2229 media.controller.State = require( './controllers/state.js' );
2230
2231 media.selectionSync = require( './utils/selection-sync.js' );
2232 media.controller.Library = require( './controllers/library.js' );
2233 media.controller.ImageDetails = require( './controllers/image-details.js' );
2234 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
2235 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
2236 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
2237 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
2238 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
2239 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
2240 media.controller.EditImage = require( './controllers/edit-image.js' );
2241 media.controller.MediaLibrary = require( './controllers/media-library.js' );
2242 media.controller.Embed = require( './controllers/embed.js' );
2243 media.controller.Cropper = require( './controllers/cropper.js' );
2244 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
2245 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
2246
2247 media.View = require( './views/view.js' );
2248 media.view.Frame = require( './views/frame.js' );
2249 media.view.MediaFrame = require( './views/media-frame.js' );
2250 media.view.MediaFrame.Select = require( './views/frame/select.js' );
2251 media.view.MediaFrame.Post = require( './views/frame/post.js' );
2252 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
2253 media.view.Modal = require( './views/modal.js' );
2254 media.view.FocusManager = require( './views/focus-manager.js' );
2255 media.view.UploaderWindow = require( './views/uploader/window.js' );
2256 media.view.EditorUploader = require( './views/uploader/editor.js' );
2257 media.view.UploaderInline = require( './views/uploader/inline.js' );
2258 media.view.UploaderStatus = require( './views/uploader/status.js' );
2259 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
2260 media.view.Toolbar = require( './views/toolbar.js' );
2261 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
2262 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
2263 media.view.Button = require( './views/button.js' );
2264 media.view.ButtonGroup = require( './views/button-group.js' );
2265 media.view.PriorityList = require( './views/priority-list.js' );
2266 media.view.MenuItem = require( './views/menu-item.js' );
2267 media.view.Menu = require( './views/menu.js' );
2268 media.view.RouterItem = require( './views/router-item.js' );
2269 media.view.Router = require( './views/router.js' );
2270 media.view.Sidebar = require( './views/sidebar.js' );
2271 media.view.Attachment = require( './views/attachment.js' );
2272 media.view.Attachment.Library = require( './views/attachment/library.js' );
2273 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
2274 media.view.Attachments = require( './views/attachments.js' );
2275 media.view.Search = require( './views/search.js' );
2276 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
2277 media.view.DateFilter = require( './views/attachment-filters/date.js' );
2278 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
2279 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
2280 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
2281 media.view.Selection = require( './views/selection.js' );
2282 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
2283 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
2284 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
2285 media.view.Settings = require( './views/settings.js' );
2286 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
2287 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
2288 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
2289 media.view.Attachment.Details = require( './views/attachment/details.js' );
2290 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
2291 media.view.Iframe = require( './views/iframe.js' );
2292 media.view.Embed = require( './views/embed.js' );
2293 media.view.Label = require( './views/label.js' );
2294 media.view.EmbedUrl = require( './views/embed/url.js' );
2295 media.view.EmbedLink = require( './views/embed/link.js' );
2296 media.view.EmbedImage = require( './views/embed/image.js' );
2297 media.view.ImageDetails = require( './views/image-details.js' );
2298 media.view.Cropper = require( './views/cropper.js' );
2299 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
2300 media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
2301 media.view.EditImage = require( './views/edit-image.js' );
2302 media.view.Spinner = require( './views/spinner.js' );
2303
2304 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){
2305 /**
2306  * wp.media.view.AttachmentCompat
2307  *
2308  * A view to display fields added via the `attachment_fields_to_edit` filter.
2309  *
2310  * @class
2311  * @augments wp.media.View
2312  * @augments wp.Backbone.View
2313  * @augments Backbone.View
2314  */
2315 var View = wp.media.View,
2316         AttachmentCompat;
2317
2318 AttachmentCompat = View.extend({
2319         tagName:   'form',
2320         className: 'compat-item',
2321
2322         events: {
2323                 'submit':          'preventDefault',
2324                 'change input':    'save',
2325                 'change select':   'save',
2326                 'change textarea': 'save'
2327         },
2328
2329         initialize: function() {
2330                 this.listenTo( this.model, 'change:compat', this.render );
2331         },
2332         /**
2333          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
2334          */
2335         dispose: function() {
2336                 if ( this.$(':focus').length ) {
2337                         this.save();
2338                 }
2339                 /**
2340                  * call 'dispose' directly on the parent class
2341                  */
2342                 return View.prototype.dispose.apply( this, arguments );
2343         },
2344         /**
2345          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
2346          */
2347         render: function() {
2348                 var compat = this.model.get('compat');
2349                 if ( ! compat || ! compat.item ) {
2350                         return;
2351                 }
2352
2353                 this.views.detach();
2354                 this.$el.html( compat.item );
2355                 this.views.render();
2356                 return this;
2357         },
2358         /**
2359          * @param {Object} event
2360          */
2361         preventDefault: function( event ) {
2362                 event.preventDefault();
2363         },
2364         /**
2365          * @param {Object} event
2366          */
2367         save: function( event ) {
2368                 var data = {};
2369
2370                 if ( event ) {
2371                         event.preventDefault();
2372                 }
2373
2374                 _.each( this.$el.serializeArray(), function( pair ) {
2375                         data[ pair.name ] = pair.value;
2376                 });
2377
2378                 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
2379                 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
2380         },
2381
2382         postSave: function() {
2383                 this.controller.trigger( 'attachment:compat:ready', ['ready'] );
2384         }
2385 });
2386
2387 module.exports = AttachmentCompat;
2388
2389 },{}],21:[function(require,module,exports){
2390 /**
2391  * wp.media.view.AttachmentFilters
2392  *
2393  * @class
2394  * @augments wp.media.View
2395  * @augments wp.Backbone.View
2396  * @augments Backbone.View
2397  */
2398 var $ = jQuery,
2399         AttachmentFilters;
2400
2401 AttachmentFilters = wp.media.View.extend({
2402         tagName:   'select',
2403         className: 'attachment-filters',
2404         id:        'media-attachment-filters',
2405
2406         events: {
2407                 change: 'change'
2408         },
2409
2410         keys: [],
2411
2412         initialize: function() {
2413                 this.createFilters();
2414                 _.extend( this.filters, this.options.filters );
2415
2416                 // Build `<option>` elements.
2417                 this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
2418                         return {
2419                                 el: $( '<option></option>' ).val( value ).html( filter.text )[0],
2420                                 priority: filter.priority || 50
2421                         };
2422                 }, this ).sortBy('priority').pluck('el').value() );
2423
2424                 this.listenTo( this.model, 'change', this.select );
2425                 this.select();
2426         },
2427
2428         /**
2429          * @abstract
2430          */
2431         createFilters: function() {
2432                 this.filters = {};
2433         },
2434
2435         /**
2436          * When the selected filter changes, update the Attachment Query properties to match.
2437          */
2438         change: function() {
2439                 var filter = this.filters[ this.el.value ];
2440                 if ( filter ) {
2441                         this.model.set( filter.props );
2442                 }
2443         },
2444
2445         select: function() {
2446                 var model = this.model,
2447                         value = 'all',
2448                         props = model.toJSON();
2449
2450                 _.find( this.filters, function( filter, id ) {
2451                         var equal = _.all( filter.props, function( prop, key ) {
2452                                 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
2453                         });
2454
2455                         if ( equal ) {
2456                                 return value = id;
2457                         }
2458                 });
2459
2460                 this.$el.val( value );
2461         }
2462 });
2463
2464 module.exports = AttachmentFilters;
2465
2466 },{}],22:[function(require,module,exports){
2467 /**
2468  * wp.media.view.AttachmentFilters.All
2469  *
2470  * @class
2471  * @augments wp.media.view.AttachmentFilters
2472  * @augments wp.media.View
2473  * @augments wp.Backbone.View
2474  * @augments Backbone.View
2475  */
2476 var l10n = wp.media.view.l10n,
2477         All;
2478
2479 All = wp.media.view.AttachmentFilters.extend({
2480         createFilters: function() {
2481                 var filters = {};
2482
2483                 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
2484                         filters[ key ] = {
2485                                 text: text,
2486                                 props: {
2487                                         status:  null,
2488                                         type:    key,
2489                                         uploadedTo: null,
2490                                         orderby: 'date',
2491                                         order:   'DESC'
2492                                 }
2493                         };
2494                 });
2495
2496                 filters.all = {
2497                         text:  l10n.allMediaItems,
2498                         props: {
2499                                 status:  null,
2500                                 type:    null,
2501                                 uploadedTo: null,
2502                                 orderby: 'date',
2503                                 order:   'DESC'
2504                         },
2505                         priority: 10
2506                 };
2507
2508                 if ( wp.media.view.settings.post.id ) {
2509                         filters.uploaded = {
2510                                 text:  l10n.uploadedToThisPost,
2511                                 props: {
2512                                         status:  null,
2513                                         type:    null,
2514                                         uploadedTo: wp.media.view.settings.post.id,
2515                                         orderby: 'menuOrder',
2516                                         order:   'ASC'
2517                                 },
2518                                 priority: 20
2519                         };
2520                 }
2521
2522                 filters.unattached = {
2523                         text:  l10n.unattached,
2524                         props: {
2525                                 status:     null,
2526                                 uploadedTo: 0,
2527                                 type:       null,
2528                                 orderby:    'menuOrder',
2529                                 order:      'ASC'
2530                         },
2531                         priority: 50
2532                 };
2533
2534                 if ( wp.media.view.settings.mediaTrash &&
2535                         this.controller.isModeActive( 'grid' ) ) {
2536
2537                         filters.trash = {
2538                                 text:  l10n.trash,
2539                                 props: {
2540                                         uploadedTo: null,
2541                                         status:     'trash',
2542                                         type:       null,
2543                                         orderby:    'date',
2544                                         order:      'DESC'
2545                                 },
2546                                 priority: 50
2547                         };
2548                 }
2549
2550                 this.filters = filters;
2551         }
2552 });
2553
2554 module.exports = All;
2555
2556 },{}],23:[function(require,module,exports){
2557 /**
2558  * A filter dropdown for month/dates.
2559  *
2560  * @class
2561  * @augments wp.media.view.AttachmentFilters
2562  * @augments wp.media.View
2563  * @augments wp.Backbone.View
2564  * @augments Backbone.View
2565  */
2566 var l10n = wp.media.view.l10n,
2567         DateFilter;
2568
2569 DateFilter = wp.media.view.AttachmentFilters.extend({
2570         id: 'media-attachment-date-filters',
2571
2572         createFilters: function() {
2573                 var filters = {};
2574                 _.each( wp.media.view.settings.months || {}, function( value, index ) {
2575                         filters[ index ] = {
2576                                 text: value.text,
2577                                 props: {
2578                                         year: value.year,
2579                                         monthnum: value.month
2580                                 }
2581                         };
2582                 });
2583                 filters.all = {
2584                         text:  l10n.allDates,
2585                         props: {
2586                                 monthnum: false,
2587                                 year:  false
2588                         },
2589                         priority: 10
2590                 };
2591                 this.filters = filters;
2592         }
2593 });
2594
2595 module.exports = DateFilter;
2596
2597 },{}],24:[function(require,module,exports){
2598 /**
2599  * wp.media.view.AttachmentFilters.Uploaded
2600  *
2601  * @class
2602  * @augments wp.media.view.AttachmentFilters
2603  * @augments wp.media.View
2604  * @augments wp.Backbone.View
2605  * @augments Backbone.View
2606  */
2607 var l10n = wp.media.view.l10n,
2608         Uploaded;
2609
2610 Uploaded = wp.media.view.AttachmentFilters.extend({
2611         createFilters: function() {
2612                 var type = this.model.get('type'),
2613                         types = wp.media.view.settings.mimeTypes,
2614                         text;
2615
2616                 if ( types && type ) {
2617                         text = types[ type ];
2618                 }
2619
2620                 this.filters = {
2621                         all: {
2622                                 text:  text || l10n.allMediaItems,
2623                                 props: {
2624                                         uploadedTo: null,
2625                                         orderby: 'date',
2626                                         order:   'DESC'
2627                                 },
2628                                 priority: 10
2629                         },
2630
2631                         uploaded: {
2632                                 text:  l10n.uploadedToThisPost,
2633                                 props: {
2634                                         uploadedTo: wp.media.view.settings.post.id,
2635                                         orderby: 'menuOrder',
2636                                         order:   'ASC'
2637                                 },
2638                                 priority: 20
2639                         },
2640
2641                         unattached: {
2642                                 text:  l10n.unattached,
2643                                 props: {
2644                                         uploadedTo: 0,
2645                                         orderby: 'menuOrder',
2646                                         order:   'ASC'
2647                                 },
2648                                 priority: 50
2649                         }
2650                 };
2651         }
2652 });
2653
2654 module.exports = Uploaded;
2655
2656 },{}],25:[function(require,module,exports){
2657 /**
2658  * wp.media.view.Attachment
2659  *
2660  * @class
2661  * @augments wp.media.View
2662  * @augments wp.Backbone.View
2663  * @augments Backbone.View
2664  */
2665 var View = wp.media.View,
2666         $ = jQuery,
2667         Attachment;
2668
2669 Attachment = View.extend({
2670         tagName:   'li',
2671         className: 'attachment',
2672         template:  wp.template('attachment'),
2673
2674         attributes: function() {
2675                 return {
2676                         'tabIndex':     0,
2677                         'role':         'checkbox',
2678                         'aria-label':   this.model.get( 'title' ),
2679                         'aria-checked': false,
2680                         'data-id':      this.model.get( 'id' )
2681                 };
2682         },
2683
2684         events: {
2685                 'click .js--select-attachment':   'toggleSelectionHandler',
2686                 'change [data-setting]':          'updateSetting',
2687                 'change [data-setting] input':    'updateSetting',
2688                 'change [data-setting] select':   'updateSetting',
2689                 'change [data-setting] textarea': 'updateSetting',
2690                 'click .attachment-close':        'removeFromLibrary',
2691                 'click .check':                   'checkClickHandler',
2692                 'keydown':                        'toggleSelectionHandler'
2693         },
2694
2695         buttons: {},
2696
2697         initialize: function() {
2698                 var selection = this.options.selection,
2699                         options = _.defaults( this.options, {
2700                                 rerenderOnModelChange: true
2701                         } );
2702
2703                 if ( options.rerenderOnModelChange ) {
2704                         this.listenTo( this.model, 'change', this.render );
2705                 } else {
2706                         this.listenTo( this.model, 'change:percent', this.progress );
2707                 }
2708                 this.listenTo( this.model, 'change:title', this._syncTitle );
2709                 this.listenTo( this.model, 'change:caption', this._syncCaption );
2710                 this.listenTo( this.model, 'change:artist', this._syncArtist );
2711                 this.listenTo( this.model, 'change:album', this._syncAlbum );
2712
2713                 // Update the selection.
2714                 this.listenTo( this.model, 'add', this.select );
2715                 this.listenTo( this.model, 'remove', this.deselect );
2716                 if ( selection ) {
2717                         selection.on( 'reset', this.updateSelect, this );
2718                         // Update the model's details view.
2719                         this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
2720                         this.details( this.model, this.controller.state().get('selection') );
2721                 }
2722
2723                 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
2724         },
2725         /**
2726          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
2727          */
2728         dispose: function() {
2729                 var selection = this.options.selection;
2730
2731                 // Make sure all settings are saved before removing the view.
2732                 this.updateAll();
2733
2734                 if ( selection ) {
2735                         selection.off( null, null, this );
2736                 }
2737                 /**
2738                  * call 'dispose' directly on the parent class
2739                  */
2740                 View.prototype.dispose.apply( this, arguments );
2741                 return this;
2742         },
2743         /**
2744          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
2745          */
2746         render: function() {
2747                 var options = _.defaults( this.model.toJSON(), {
2748                                 orientation:   'landscape',
2749                                 uploading:     false,
2750                                 type:          '',
2751                                 subtype:       '',
2752                                 icon:          '',
2753                                 filename:      '',
2754                                 caption:       '',
2755                                 title:         '',
2756                                 dateFormatted: '',
2757                                 width:         '',
2758                                 height:        '',
2759                                 compat:        false,
2760                                 alt:           '',
2761                                 description:   ''
2762                         }, this.options );
2763
2764                 options.buttons  = this.buttons;
2765                 options.describe = this.controller.state().get('describe');
2766
2767                 if ( 'image' === options.type ) {
2768                         options.size = this.imageSize();
2769                 }
2770
2771                 options.can = {};
2772                 if ( options.nonces ) {
2773                         options.can.remove = !! options.nonces['delete'];
2774                         options.can.save = !! options.nonces.update;
2775                 }
2776
2777                 if ( this.controller.state().get('allowLocalEdits') ) {
2778                         options.allowLocalEdits = true;
2779                 }
2780
2781                 if ( options.uploading && ! options.percent ) {
2782                         options.percent = 0;
2783                 }
2784
2785                 this.views.detach();
2786                 this.$el.html( this.template( options ) );
2787
2788                 this.$el.toggleClass( 'uploading', options.uploading );
2789
2790                 if ( options.uploading ) {
2791                         this.$bar = this.$('.media-progress-bar div');
2792                 } else {
2793                         delete this.$bar;
2794                 }
2795
2796                 // Check if the model is selected.
2797                 this.updateSelect();
2798
2799                 // Update the save status.
2800                 this.updateSave();
2801
2802                 this.views.render();
2803
2804                 return this;
2805         },
2806
2807         progress: function() {
2808                 if ( this.$bar && this.$bar.length ) {
2809                         this.$bar.width( this.model.get('percent') + '%' );
2810                 }
2811         },
2812
2813         /**
2814          * @param {Object} event
2815          */
2816         toggleSelectionHandler: function( event ) {
2817                 var method;
2818
2819                 // Don't do anything inside inputs and on the attachment check and remove buttons.
2820                 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
2821                         return;
2822                 }
2823
2824                 // Catch arrow events
2825                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
2826                         this.controller.trigger( 'attachment:keydown:arrow', event );
2827                         return;
2828                 }
2829
2830                 // Catch enter and space events
2831                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
2832                         return;
2833                 }
2834
2835                 event.preventDefault();
2836
2837                 // In the grid view, bubble up an edit:attachment event to the controller.
2838                 if ( this.controller.isModeActive( 'grid' ) ) {
2839                         if ( this.controller.isModeActive( 'edit' ) ) {
2840                                 // Pass the current target to restore focus when closing
2841                                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
2842                                 return;
2843                         }
2844
2845                         if ( this.controller.isModeActive( 'select' ) ) {
2846                                 method = 'toggle';
2847                         }
2848                 }
2849
2850                 if ( event.shiftKey ) {
2851                         method = 'between';
2852                 } else if ( event.ctrlKey || event.metaKey ) {
2853                         method = 'toggle';
2854                 }
2855
2856                 this.toggleSelection({
2857                         method: method
2858                 });
2859
2860                 this.controller.trigger( 'selection:toggle' );
2861         },
2862         /**
2863          * @param {Object} options
2864          */
2865         toggleSelection: function( options ) {
2866                 var collection = this.collection,
2867                         selection = this.options.selection,
2868                         model = this.model,
2869                         method = options && options.method,
2870                         single, models, singleIndex, modelIndex;
2871
2872                 if ( ! selection ) {
2873                         return;
2874                 }
2875
2876                 single = selection.single();
2877                 method = _.isUndefined( method ) ? selection.multiple : method;
2878
2879                 // If the `method` is set to `between`, select all models that
2880                 // exist between the current and the selected model.
2881                 if ( 'between' === method && single && selection.multiple ) {
2882                         // If the models are the same, short-circuit.
2883                         if ( single === model ) {
2884                                 return;
2885                         }
2886
2887                         singleIndex = collection.indexOf( single );
2888                         modelIndex  = collection.indexOf( this.model );
2889
2890                         if ( singleIndex < modelIndex ) {
2891                                 models = collection.models.slice( singleIndex, modelIndex + 1 );
2892                         } else {
2893                                 models = collection.models.slice( modelIndex, singleIndex + 1 );
2894                         }
2895
2896                         selection.add( models );
2897                         selection.single( model );
2898                         return;
2899
2900                 // If the `method` is set to `toggle`, just flip the selection
2901                 // status, regardless of whether the model is the single model.
2902                 } else if ( 'toggle' === method ) {
2903                         selection[ this.selected() ? 'remove' : 'add' ]( model );
2904                         selection.single( model );
2905                         return;
2906                 } else if ( 'add' === method ) {
2907                         selection.add( model );
2908                         selection.single( model );
2909                         return;
2910                 }
2911
2912                 // Fixes bug that loses focus when selecting a featured image
2913                 if ( ! method ) {
2914                         method = 'add';
2915                 }
2916
2917                 if ( method !== 'add' ) {
2918                         method = 'reset';
2919                 }
2920
2921                 if ( this.selected() ) {
2922                         // If the model is the single model, remove it.
2923                         // If it is not the same as the single model,
2924                         // it now becomes the single model.
2925                         selection[ single === model ? 'remove' : 'single' ]( model );
2926                 } else {
2927                         // If the model is not selected, run the `method` on the
2928                         // selection. By default, we `reset` the selection, but the
2929                         // `method` can be set to `add` the model to the selection.
2930                         selection[ method ]( model );
2931                         selection.single( model );
2932                 }
2933         },
2934
2935         updateSelect: function() {
2936                 this[ this.selected() ? 'select' : 'deselect' ]();
2937         },
2938         /**
2939          * @returns {unresolved|Boolean}
2940          */
2941         selected: function() {
2942                 var selection = this.options.selection;
2943                 if ( selection ) {
2944                         return !! selection.get( this.model.cid );
2945                 }
2946         },
2947         /**
2948          * @param {Backbone.Model} model
2949          * @param {Backbone.Collection} collection
2950          */
2951         select: function( model, collection ) {
2952                 var selection = this.options.selection,
2953                         controller = this.controller;
2954
2955                 // Check if a selection exists and if it's the collection provided.
2956                 // If they're not the same collection, bail; we're in another
2957                 // selection's event loop.
2958                 if ( ! selection || ( collection && collection !== selection ) ) {
2959                         return;
2960                 }
2961
2962                 // Bail if the model is already selected.
2963                 if ( this.$el.hasClass( 'selected' ) ) {
2964                         return;
2965                 }
2966
2967                 // Add 'selected' class to model, set aria-checked to true.
2968                 this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
2969                 //  Make the checkbox tabable, except in media grid (bulk select mode).
2970                 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
2971                         this.$( '.check' ).attr( 'tabindex', '0' );
2972                 }
2973         },
2974         /**
2975          * @param {Backbone.Model} model
2976          * @param {Backbone.Collection} collection
2977          */
2978         deselect: function( model, collection ) {
2979                 var selection = this.options.selection;
2980
2981                 // Check if a selection exists and if it's the collection provided.
2982                 // If they're not the same collection, bail; we're in another
2983                 // selection's event loop.
2984                 if ( ! selection || ( collection && collection !== selection ) ) {
2985                         return;
2986                 }
2987                 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
2988                         .find( '.check' ).attr( 'tabindex', '-1' );
2989         },
2990         /**
2991          * @param {Backbone.Model} model
2992          * @param {Backbone.Collection} collection
2993          */
2994         details: function( model, collection ) {
2995                 var selection = this.options.selection,
2996                         details;
2997
2998                 if ( selection !== collection ) {
2999                         return;
3000                 }
3001
3002                 details = selection.single();
3003                 this.$el.toggleClass( 'details', details === this.model );
3004         },
3005         /**
3006          * @param {string} size
3007          * @returns {Object}
3008          */
3009         imageSize: function( size ) {
3010                 var sizes = this.model.get('sizes'), matched = false;
3011
3012                 size = size || 'medium';
3013
3014                 // Use the provided image size if possible.
3015                 if ( sizes ) {
3016                         if ( sizes[ size ] ) {
3017                                 matched = sizes[ size ];
3018                         } else if ( sizes.large ) {
3019                                 matched = sizes.large;
3020                         } else if ( sizes.thumbnail ) {
3021                                 matched = sizes.thumbnail;
3022                         } else if ( sizes.full ) {
3023                                 matched = sizes.full;
3024                         }
3025
3026                         if ( matched ) {
3027                                 return _.clone( matched );
3028                         }
3029                 }
3030
3031                 return {
3032                         url:         this.model.get('url'),
3033                         width:       this.model.get('width'),
3034                         height:      this.model.get('height'),
3035                         orientation: this.model.get('orientation')
3036                 };
3037         },
3038         /**
3039          * @param {Object} event
3040          */
3041         updateSetting: function( event ) {
3042                 var $setting = $( event.target ).closest('[data-setting]'),
3043                         setting, value;
3044
3045                 if ( ! $setting.length ) {
3046                         return;
3047                 }
3048
3049                 setting = $setting.data('setting');
3050                 value   = event.target.value;
3051
3052                 if ( this.model.get( setting ) !== value ) {
3053                         this.save( setting, value );
3054                 }
3055         },
3056
3057         /**
3058          * Pass all the arguments to the model's save method.
3059          *
3060          * Records the aggregate status of all save requests and updates the
3061          * view's classes accordingly.
3062          */
3063         save: function() {
3064                 var view = this,
3065                         save = this._save = this._save || { status: 'ready' },
3066                         request = this.model.save.apply( this.model, arguments ),
3067                         requests = save.requests ? $.when( request, save.requests ) : request;
3068
3069                 // If we're waiting to remove 'Saved.', stop.
3070                 if ( save.savedTimer ) {
3071                         clearTimeout( save.savedTimer );
3072                 }
3073
3074                 this.updateSave('waiting');
3075                 save.requests = requests;
3076                 requests.always( function() {
3077                         // If we've performed another request since this one, bail.
3078                         if ( save.requests !== requests ) {
3079                                 return;
3080                         }
3081
3082                         view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
3083                         save.savedTimer = setTimeout( function() {
3084                                 view.updateSave('ready');
3085                                 delete save.savedTimer;
3086                         }, 2000 );
3087                 });
3088         },
3089         /**
3090          * @param {string} status
3091          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
3092          */
3093         updateSave: function( status ) {
3094                 var save = this._save = this._save || { status: 'ready' };
3095
3096                 if ( status && status !== save.status ) {
3097                         this.$el.removeClass( 'save-' + save.status );
3098                         save.status = status;
3099                 }
3100
3101                 this.$el.addClass( 'save-' + save.status );
3102                 return this;
3103         },
3104
3105         updateAll: function() {
3106                 var $settings = this.$('[data-setting]'),
3107                         model = this.model,
3108                         changed;
3109
3110                 changed = _.chain( $settings ).map( function( el ) {
3111                         var $input = $('input, textarea, select, [value]', el ),
3112                                 setting, value;
3113
3114                         if ( ! $input.length ) {
3115                                 return;
3116                         }
3117
3118                         setting = $(el).data('setting');
3119                         value = $input.val();
3120
3121                         // Record the value if it changed.
3122                         if ( model.get( setting ) !== value ) {
3123                                 return [ setting, value ];
3124                         }
3125                 }).compact().object().value();
3126
3127                 if ( ! _.isEmpty( changed ) ) {
3128                         model.save( changed );
3129                 }
3130         },
3131         /**
3132          * @param {Object} event
3133          */
3134         removeFromLibrary: function( event ) {
3135                 // Catch enter and space events
3136                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
3137                         return;
3138                 }
3139
3140                 // Stop propagation so the model isn't selected.
3141                 event.stopPropagation();
3142
3143                 this.collection.remove( this.model );
3144         },
3145
3146         /**
3147          * Add the model if it isn't in the selection, if it is in the selection,
3148          * remove it.
3149          *
3150          * @param  {[type]} event [description]
3151          * @return {[type]}       [description]
3152          */
3153         checkClickHandler: function ( event ) {
3154                 var selection = this.options.selection;
3155                 if ( ! selection ) {
3156                         return;
3157                 }
3158                 event.stopPropagation();
3159                 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
3160                         selection.remove( this.model );
3161                         // Move focus back to the attachment tile (from the check).
3162                         this.$el.focus();
3163                 } else {
3164                         selection.add( this.model );
3165                 }
3166         }
3167 });
3168
3169 // Ensure settings remain in sync between attachment views.
3170 _.each({
3171         caption: '_syncCaption',
3172         title:   '_syncTitle',
3173         artist:  '_syncArtist',
3174         album:   '_syncAlbum'
3175 }, function( method, setting ) {
3176         /**
3177          * @param {Backbone.Model} model
3178          * @param {string} value
3179          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
3180          */
3181         Attachment.prototype[ method ] = function( model, value ) {
3182                 var $setting = this.$('[data-setting="' + setting + '"]');
3183
3184                 if ( ! $setting.length ) {
3185                         return this;
3186                 }
3187
3188                 // If the updated value is in sync with the value in the DOM, there
3189                 // is no need to re-render. If we're currently editing the value,
3190                 // it will automatically be in sync, suppressing the re-render for
3191                 // the view we're editing, while updating any others.
3192                 if ( value === $setting.find('input, textarea, select, [value]').val() ) {
3193                         return this;
3194                 }
3195
3196                 return this.render();
3197         };
3198 });
3199
3200 module.exports = Attachment;
3201
3202 },{}],26:[function(require,module,exports){
3203 /**
3204  * wp.media.view.Attachment.Details
3205  *
3206  * @class
3207  * @augments wp.media.view.Attachment
3208  * @augments wp.media.View
3209  * @augments wp.Backbone.View
3210  * @augments Backbone.View
3211  */
3212 var Attachment = wp.media.view.Attachment,
3213         l10n = wp.media.view.l10n,
3214         Details;
3215
3216 Details = Attachment.extend({
3217         tagName:   'div',
3218         className: 'attachment-details',
3219         template:  wp.template('attachment-details'),
3220
3221         attributes: function() {
3222                 return {
3223                         'tabIndex':     0,
3224                         'data-id':      this.model.get( 'id' )
3225                 };
3226         },
3227
3228         events: {
3229                 'change [data-setting]':          'updateSetting',
3230                 'change [data-setting] input':    'updateSetting',
3231                 'change [data-setting] select':   'updateSetting',
3232                 'change [data-setting] textarea': 'updateSetting',
3233                 'click .delete-attachment':       'deleteAttachment',
3234                 'click .trash-attachment':        'trashAttachment',
3235                 'click .untrash-attachment':      'untrashAttachment',
3236                 'click .edit-attachment':         'editAttachment',
3237                 'keydown':                        'toggleSelectionHandler'
3238         },
3239
3240         initialize: function() {
3241                 this.options = _.defaults( this.options, {
3242                         rerenderOnModelChange: false
3243                 });
3244
3245                 this.on( 'ready', this.initialFocus );
3246                 // Call 'initialize' directly on the parent class.
3247                 Attachment.prototype.initialize.apply( this, arguments );
3248         },
3249
3250         initialFocus: function() {
3251                 if ( ! wp.media.isTouchDevice ) {
3252                         /*
3253                         Previously focused the first ':input' (the readonly URL text field).
3254                         Since the first ':input' is now a button (delete/trash): when pressing
3255                         spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
3256                         as soon as focus is moved. Explicitly target the first text field for now.
3257                         @todo change initial focus logic, also for accessibility.
3258                         */
3259                         this.$( 'input[type="text"]' ).eq( 0 ).focus();
3260                 }
3261         },
3262         /**
3263          * @param {Object} event
3264          */
3265         deleteAttachment: function( event ) {
3266                 event.preventDefault();
3267
3268                 if ( window.confirm( l10n.warnDelete ) ) {
3269                         this.model.destroy();
3270                         // Keep focus inside media modal
3271                         // after image is deleted
3272                         this.controller.modal.focusManager.focus();
3273                 }
3274         },
3275         /**
3276          * @param {Object} event
3277          */
3278         trashAttachment: function( event ) {
3279                 var library = this.controller.library;
3280                 event.preventDefault();
3281
3282                 if ( wp.media.view.settings.mediaTrash &&
3283                         'edit-metadata' === this.controller.content.mode() ) {
3284
3285                         this.model.set( 'status', 'trash' );
3286                         this.model.save().done( function() {
3287                                 library._requery( true );
3288                         } );
3289                 }  else {
3290                         this.model.destroy();
3291                 }
3292         },
3293         /**
3294          * @param {Object} event
3295          */
3296         untrashAttachment: function( event ) {
3297                 var library = this.controller.library;
3298                 event.preventDefault();
3299
3300                 this.model.set( 'status', 'inherit' );
3301                 this.model.save().done( function() {
3302                         library._requery( true );
3303                 } );
3304         },
3305         /**
3306          * @param {Object} event
3307          */
3308         editAttachment: function( event ) {
3309                 var editState = this.controller.states.get( 'edit-image' );
3310                 if ( window.imageEdit && editState ) {
3311                         event.preventDefault();
3312
3313                         editState.set( 'image', this.model );
3314                         this.controller.setState( 'edit-image' );
3315                 } else {
3316                         this.$el.addClass('needs-refresh');
3317                 }
3318         },
3319         /**
3320          * When reverse tabbing(shift+tab) out of the right details panel, deliver
3321          * the focus to the item in the list that was being edited.
3322          *
3323          * @param {Object} event
3324          */
3325         toggleSelectionHandler: function( event ) {
3326                 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
3327                         this.controller.trigger( 'attachment:details:shift-tab', event );
3328                         return false;
3329                 }
3330
3331                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
3332                         this.controller.trigger( 'attachment:keydown:arrow', event );
3333                         return;
3334                 }
3335         }
3336 });
3337
3338 module.exports = Details;
3339
3340 },{}],27:[function(require,module,exports){
3341 /**
3342  * wp.media.view.Attachment.EditLibrary
3343  *
3344  * @class
3345  * @augments wp.media.view.Attachment
3346  * @augments wp.media.View
3347  * @augments wp.Backbone.View
3348  * @augments Backbone.View
3349  */
3350 var EditLibrary = wp.media.view.Attachment.extend({
3351         buttons: {
3352                 close: true
3353         }
3354 });
3355
3356 module.exports = EditLibrary;
3357
3358 },{}],28:[function(require,module,exports){
3359 /**
3360  * wp.media.view.Attachments.EditSelection
3361  *
3362  * @class
3363  * @augments wp.media.view.Attachment.Selection
3364  * @augments wp.media.view.Attachment
3365  * @augments wp.media.View
3366  * @augments wp.Backbone.View
3367  * @augments Backbone.View
3368  */
3369 var EditSelection = wp.media.view.Attachment.Selection.extend({
3370         buttons: {
3371                 close: true
3372         }
3373 });
3374
3375 module.exports = EditSelection;
3376
3377 },{}],29:[function(require,module,exports){
3378 /**
3379  * wp.media.view.Attachment.Library
3380  *
3381  * @class
3382  * @augments wp.media.view.Attachment
3383  * @augments wp.media.View
3384  * @augments wp.Backbone.View
3385  * @augments Backbone.View
3386  */
3387 var Library = wp.media.view.Attachment.extend({
3388         buttons: {
3389                 check: true
3390         }
3391 });
3392
3393 module.exports = Library;
3394
3395 },{}],30:[function(require,module,exports){
3396 /**
3397  * wp.media.view.Attachment.Selection
3398  *
3399  * @class
3400  * @augments wp.media.view.Attachment
3401  * @augments wp.media.View
3402  * @augments wp.Backbone.View
3403  * @augments Backbone.View
3404  */
3405 var Selection = wp.media.view.Attachment.extend({
3406         className: 'attachment selection',
3407
3408         // On click, just select the model, instead of removing the model from
3409         // the selection.
3410         toggleSelection: function() {
3411                 this.options.selection.single( this.model );
3412         }
3413 });
3414
3415 module.exports = Selection;
3416
3417 },{}],31:[function(require,module,exports){
3418 /**
3419  * wp.media.view.Attachments
3420  *
3421  * @class
3422  * @augments wp.media.View
3423  * @augments wp.Backbone.View
3424  * @augments Backbone.View
3425  */
3426 var View = wp.media.View,
3427         $ = jQuery,
3428         Attachments;
3429
3430 Attachments = View.extend({
3431         tagName:   'ul',
3432         className: 'attachments',
3433
3434         attributes: {
3435                 tabIndex: -1
3436         },
3437
3438         initialize: function() {
3439                 this.el.id = _.uniqueId('__attachments-view-');
3440
3441                 _.defaults( this.options, {
3442                         refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
3443                         refreshThreshold:   3,
3444                         AttachmentView:     wp.media.view.Attachment,
3445                         sortable:           false,
3446                         resize:             true,
3447                         idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
3448                 });
3449
3450                 this._viewsByCid = {};
3451                 this.$window = $( window );
3452                 this.resizeEvent = 'resize.media-modal-columns';
3453
3454                 this.collection.on( 'add', function( attachment ) {
3455                         this.views.add( this.createAttachmentView( attachment ), {
3456                                 at: this.collection.indexOf( attachment )
3457                         });
3458                 }, this );
3459
3460                 this.collection.on( 'remove', function( attachment ) {
3461                         var view = this._viewsByCid[ attachment.cid ];
3462                         delete this._viewsByCid[ attachment.cid ];
3463
3464                         if ( view ) {
3465                                 view.remove();
3466                         }
3467                 }, this );
3468
3469                 this.collection.on( 'reset', this.render, this );
3470
3471                 this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
3472
3473                 // Throttle the scroll handler and bind this.
3474                 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
3475
3476                 this.options.scrollElement = this.options.scrollElement || this.el;
3477                 $( this.options.scrollElement ).on( 'scroll', this.scroll );
3478
3479                 this.initSortable();
3480
3481                 _.bindAll( this, 'setColumns' );
3482
3483                 if ( this.options.resize ) {
3484                         this.on( 'ready', this.bindEvents );
3485                         this.controller.on( 'open', this.setColumns );
3486
3487                         // Call this.setColumns() after this view has been rendered in the DOM so
3488                         // attachments get proper width applied.
3489                         _.defer( this.setColumns, this );
3490                 }
3491         },
3492
3493         bindEvents: function() {
3494                 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
3495         },
3496
3497         attachmentFocus: function() {
3498                 this.$( 'li:first' ).focus();
3499         },
3500
3501         restoreFocus: function() {
3502                 this.$( 'li.selected:first' ).focus();
3503         },
3504
3505         arrowEvent: function( event ) {
3506                 var attachments = this.$el.children( 'li' ),
3507                         perRow = this.columns,
3508                         index = attachments.filter( ':focus' ).index(),
3509                         row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
3510
3511                 if ( index === -1 ) {
3512                         return;
3513                 }
3514
3515                 // Left arrow
3516                 if ( 37 === event.keyCode ) {
3517                         if ( 0 === index ) {
3518                                 return;
3519                         }
3520                         attachments.eq( index - 1 ).focus();
3521                 }
3522
3523                 // Up arrow
3524                 if ( 38 === event.keyCode ) {
3525                         if ( 1 === row ) {
3526                                 return;
3527                         }
3528                         attachments.eq( index - perRow ).focus();
3529                 }
3530
3531                 // Right arrow
3532                 if ( 39 === event.keyCode ) {
3533                         if ( attachments.length === index ) {
3534                                 return;
3535                         }
3536                         attachments.eq( index + 1 ).focus();
3537                 }
3538
3539                 // Down arrow
3540                 if ( 40 === event.keyCode ) {
3541                         if ( Math.ceil( attachments.length / perRow ) === row ) {
3542                                 return;
3543                         }
3544                         attachments.eq( index + perRow ).focus();
3545                 }
3546         },
3547
3548         dispose: function() {
3549                 this.collection.props.off( null, null, this );
3550                 if ( this.options.resize ) {
3551                         this.$window.off( this.resizeEvent );
3552                 }
3553
3554                 /**
3555                  * call 'dispose' directly on the parent class
3556                  */
3557                 View.prototype.dispose.apply( this, arguments );
3558         },
3559
3560         setColumns: function() {
3561                 var prev = this.columns,
3562                         width = this.$el.width();
3563
3564                 if ( width ) {
3565                         this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
3566
3567                         if ( ! prev || prev !== this.columns ) {
3568                                 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
3569                         }
3570                 }
3571         },
3572
3573         initSortable: function() {
3574                 var collection = this.collection;
3575
3576                 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
3577                         return;
3578                 }
3579
3580                 this.$el.sortable( _.extend({
3581                         // If the `collection` has a `comparator`, disable sorting.
3582                         disabled: !! collection.comparator,
3583
3584                         // Change the position of the attachment as soon as the
3585                         // mouse pointer overlaps a thumbnail.
3586                         tolerance: 'pointer',
3587
3588                         // Record the initial `index` of the dragged model.
3589                         start: function( event, ui ) {
3590                                 ui.item.data('sortableIndexStart', ui.item.index());
3591                         },
3592
3593                         // Update the model's index in the collection.
3594                         // Do so silently, as the view is already accurate.
3595                         update: function( event, ui ) {
3596                                 var model = collection.at( ui.item.data('sortableIndexStart') ),
3597                                         comparator = collection.comparator;
3598
3599                                 // Temporarily disable the comparator to prevent `add`
3600                                 // from re-sorting.
3601                                 delete collection.comparator;
3602
3603                                 // Silently shift the model to its new index.
3604                                 collection.remove( model, {
3605                                         silent: true
3606                                 });
3607                                 collection.add( model, {
3608                                         silent: true,
3609                                         at:     ui.item.index()
3610                                 });
3611
3612                                 // Restore the comparator.
3613                                 collection.comparator = comparator;
3614
3615                                 // Fire the `reset` event to ensure other collections sync.
3616                                 collection.trigger( 'reset', collection );
3617
3618                                 // If the collection is sorted by menu order,
3619                                 // update the menu order.
3620                                 collection.saveMenuOrder();
3621                         }
3622                 }, this.options.sortable ) );
3623
3624                 // If the `orderby` property is changed on the `collection`,
3625                 // check to see if we have a `comparator`. If so, disable sorting.
3626                 collection.props.on( 'change:orderby', function() {
3627                         this.$el.sortable( 'option', 'disabled', !! collection.comparator );
3628                 }, this );
3629
3630                 this.collection.props.on( 'change:orderby', this.refreshSortable, this );
3631                 this.refreshSortable();
3632         },
3633
3634         refreshSortable: function() {
3635                 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
3636                         return;
3637                 }
3638
3639                 // If the `collection` has a `comparator`, disable sorting.
3640                 var collection = this.collection,
3641                         orderby = collection.props.get('orderby'),
3642                         enabled = 'menuOrder' === orderby || ! collection.comparator;
3643
3644                 this.$el.sortable( 'option', 'disabled', ! enabled );
3645         },
3646
3647         /**
3648          * @param {wp.media.model.Attachment} attachment
3649          * @returns {wp.media.View}
3650          */
3651         createAttachmentView: function( attachment ) {
3652                 var view = new this.options.AttachmentView({
3653                         controller:           this.controller,
3654                         model:                attachment,
3655                         collection:           this.collection,
3656                         selection:            this.options.selection
3657                 });
3658
3659                 return this._viewsByCid[ attachment.cid ] = view;
3660         },
3661
3662         prepare: function() {
3663                 // Create all of the Attachment views, and replace
3664                 // the list in a single DOM operation.
3665                 if ( this.collection.length ) {
3666                         this.views.set( this.collection.map( this.createAttachmentView, this ) );
3667
3668                 // If there are no elements, clear the views and load some.
3669                 } else {
3670                         this.views.unset();
3671                         this.collection.more().done( this.scroll );
3672                 }
3673         },
3674
3675         ready: function() {
3676                 // Trigger the scroll event to check if we're within the
3677                 // threshold to query for additional attachments.
3678                 this.scroll();
3679         },
3680
3681         scroll: function() {
3682                 var view = this,
3683                         el = this.options.scrollElement,
3684                         scrollTop = el.scrollTop,
3685                         toolbar;
3686
3687                 // The scroll event occurs on the document, but the element
3688                 // that should be checked is the document body.
3689                 if ( el === document ) {
3690                         el = document.body;
3691                         scrollTop = $(document).scrollTop();
3692                 }
3693
3694                 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
3695                         return;
3696                 }
3697
3698                 toolbar = this.views.parent.toolbar;
3699
3700                 // Show the spinner only if we are close to the bottom.
3701                 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
3702                         toolbar.get('spinner').show();
3703                 }
3704
3705                 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
3706                         this.collection.more().done(function() {
3707                                 view.scroll();
3708                                 toolbar.get('spinner').hide();
3709                         });
3710                 }
3711         }
3712 });
3713
3714 module.exports = Attachments;
3715
3716 },{}],32:[function(require,module,exports){
3717 /**
3718  * wp.media.view.AttachmentsBrowser
3719  *
3720  * @class
3721  * @augments wp.media.View
3722  * @augments wp.Backbone.View
3723  * @augments Backbone.View
3724  *
3725  * @param {object}         [options]               The options hash passed to the view.
3726  * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
3727  *                                                 Accepts 'uploaded' and 'all'.
3728  * @param {boolean}        [options.search=true]   Whether to show the search interface in the
3729  *                                                 browser's toolbar.
3730  * @param {boolean}        [options.date=true]     Whether to show the date filter in the
3731  *                                                 browser's toolbar.
3732  * @param {boolean}        [options.display=false] Whether to show the attachments display settings
3733  *                                                 view in the sidebar.
3734  * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
3735  *                                                 Accepts true, false, and 'errors'.
3736  */
3737 var View = wp.media.View,
3738         mediaTrash = wp.media.view.settings.mediaTrash,
3739         l10n = wp.media.view.l10n,
3740         $ = jQuery,
3741         AttachmentsBrowser;
3742
3743 AttachmentsBrowser = View.extend({
3744         tagName:   'div',
3745         className: 'attachments-browser',
3746
3747         initialize: function() {
3748                 _.defaults( this.options, {
3749                         filters: false,
3750                         search:  true,
3751                         date:    true,
3752                         display: false,
3753                         sidebar: true,
3754                         AttachmentView: wp.media.view.Attachment.Library
3755                 });
3756
3757                 this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) );
3758                 this.controller.on( 'edit:selection', this.editSelection );
3759                 this.createToolbar();
3760                 if ( this.options.sidebar ) {
3761                         this.createSidebar();
3762                 }
3763                 this.createUploader();
3764                 this.createAttachments();
3765                 this.updateContent();
3766
3767                 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
3768                         this.$el.addClass( 'hide-sidebar' );
3769
3770                         if ( 'errors' === this.options.sidebar ) {
3771                                 this.$el.addClass( 'sidebar-for-errors' );
3772                         }
3773                 }
3774
3775                 this.collection.on( 'add remove reset', this.updateContent, this );
3776         },
3777
3778         editSelection: function( modal ) {
3779                 modal.$( '.media-button-backToLibrary' ).focus();
3780         },
3781
3782         /**
3783          * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
3784          */
3785         dispose: function() {
3786                 this.options.selection.off( null, null, this );
3787                 View.prototype.dispose.apply( this, arguments );
3788                 return this;
3789         },
3790
3791         createToolbar: function() {
3792                 var LibraryViewSwitcher, Filters, toolbarOptions;
3793
3794                 toolbarOptions = {
3795                         controller: this.controller
3796                 };
3797
3798                 if ( this.controller.isModeActive( 'grid' ) ) {
3799                         toolbarOptions.className = 'media-toolbar wp-filter';
3800                 }
3801
3802                 /**
3803                 * @member {wp.media.view.Toolbar}
3804                 */
3805                 this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
3806
3807                 this.views.add( this.toolbar );
3808
3809                 this.toolbar.set( 'spinner', new wp.media.view.Spinner({
3810                         priority: -60
3811                 }) );
3812
3813                 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
3814                         // "Filters" will return a <select>, need to render
3815                         // screen reader text before
3816                         this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
3817                                 value: l10n.filterByType,
3818                                 attributes: {
3819                                         'for':  'media-attachment-filters'
3820                                 },
3821                                 priority:   -80
3822                         }).render() );
3823
3824                         if ( 'uploaded' === this.options.filters ) {
3825                                 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
3826                                         controller: this.controller,
3827                                         model:      this.collection.props,
3828                                         priority:   -80
3829                                 }).render() );
3830                         } else {
3831                                 Filters = new wp.media.view.AttachmentFilters.All({
3832                                         controller: this.controller,
3833                                         model:      this.collection.props,
3834                                         priority:   -80
3835                                 });
3836
3837                                 this.toolbar.set( 'filters', Filters.render() );
3838                         }
3839                 }
3840
3841                 // Feels odd to bring the global media library switcher into the Attachment
3842                 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
3843                 // which the controller can tap into and add this view?
3844                 if ( this.controller.isModeActive( 'grid' ) ) {
3845                         LibraryViewSwitcher = View.extend({
3846                                 className: 'view-switch media-grid-view-switch',
3847                                 template: wp.template( 'media-library-view-switcher')
3848                         });
3849
3850                         this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
3851                                 controller: this.controller,
3852                                 priority: -90
3853                         }).render() );
3854
3855                         // DateFilter is a <select>, screen reader text needs to be rendered before
3856                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
3857                                 value: l10n.filterByDate,
3858                                 attributes: {
3859                                         'for': 'media-attachment-date-filters'
3860                                 },
3861                                 priority: -75
3862                         }).render() );
3863                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
3864                                 controller: this.controller,
3865                                 model:      this.collection.props,
3866                                 priority: -75
3867                         }).render() );
3868
3869                         // BulkSelection is a <div> with subviews, including screen reader text
3870                         this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
3871                                 text: l10n.bulkSelect,
3872                                 controller: this.controller,
3873                                 priority: -70
3874                         }).render() );
3875
3876                         this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
3877                                 filters: Filters,
3878                                 style: 'primary',
3879                                 disabled: true,
3880                                 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
3881                                 controller: this.controller,
3882                                 priority: -60,
3883                                 click: function() {
3884                                         var changed = [], removed = [],
3885                                                 selection = this.controller.state().get( 'selection' ),
3886                                                 library = this.controller.state().get( 'library' );
3887
3888                                         if ( ! selection.length ) {
3889                                                 return;
3890                                         }
3891
3892                                         if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
3893                                                 return;
3894                                         }
3895
3896                                         if ( mediaTrash &&
3897                                                 'trash' !== selection.at( 0 ).get( 'status' ) &&
3898                                                 ! window.confirm( l10n.warnBulkTrash ) ) {
3899
3900                                                 return;
3901                                         }
3902
3903                                         selection.each( function( model ) {
3904                                                 if ( ! model.get( 'nonces' )['delete'] ) {
3905                                                         removed.push( model );
3906                                                         return;
3907                                                 }
3908
3909                                                 if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
3910                                                         model.set( 'status', 'inherit' );
3911                                                         changed.push( model.save() );
3912                                                         removed.push( model );
3913                                                 } else if ( mediaTrash ) {
3914                                                         model.set( 'status', 'trash' );
3915                                                         changed.push( model.save() );
3916                                                         removed.push( model );
3917                                                 } else {
3918                                                         model.destroy({wait: true});
3919                                                 }
3920                                         } );
3921
3922                                         if ( changed.length ) {
3923                                                 selection.remove( removed );
3924
3925                                                 $.when.apply( null, changed ).then( _.bind( function() {
3926                                                         library._requery( true );
3927                                                         this.controller.trigger( 'selection:action:done' );
3928                                                 }, this ) );
3929                                         } else {
3930                                                 this.controller.trigger( 'selection:action:done' );
3931                                         }
3932                                 }
3933                         }).render() );
3934
3935                         if ( mediaTrash ) {
3936                                 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
3937                                         filters: Filters,
3938                                         style: 'primary',
3939                                         disabled: true,
3940                                         text: l10n.deleteSelected,
3941                                         controller: this.controller,
3942                                         priority: -55,
3943                                         click: function() {
3944                                                 var removed = [], selection = this.controller.state().get( 'selection' );
3945
3946                                                 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
3947                                                         return;
3948                                                 }
3949
3950                                                 selection.each( function( model ) {
3951                                                         if ( ! model.get( 'nonces' )['delete'] ) {
3952                                                                 removed.push( model );
3953                                                                 return;
3954                                                         }
3955
3956                                                         model.destroy();
3957                                                 } );
3958
3959                                                 selection.remove( removed );
3960                                                 this.controller.trigger( 'selection:action:done' );
3961                                         }
3962                                 }).render() );
3963                         }
3964
3965                 } else if ( this.options.date ) {
3966                         // DateFilter is a <select>, screen reader text needs to be rendered before
3967                         this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
3968                                 value: l10n.filterByDate,
3969                                 attributes: {
3970                                         'for': 'media-attachment-date-filters'
3971                                 },
3972                                 priority: -75
3973                         }).render() );
3974                         this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
3975                                 controller: this.controller,
3976                                 model:      this.collection.props,
3977                                 priority: -75
3978                         }).render() );
3979                 }
3980
3981                 if ( this.options.search ) {
3982                         // Search is an input, screen reader text needs to be rendered before
3983                         this.toolbar.set( 'searchLabel', new wp.media.view.Label({
3984                                 value: l10n.searchMediaLabel,
3985                                 attributes: {
3986                                         'for': 'media-search-input'
3987                                 },
3988                                 priority:   60
3989                         }).render() );
3990                         this.toolbar.set( 'search', new wp.media.view.Search({
3991                                 controller: this.controller,
3992                                 model:      this.collection.props,
3993                                 priority:   60
3994                         }).render() );
3995                 }
3996
3997                 if ( this.options.dragInfo ) {
3998                         this.toolbar.set( 'dragInfo', new View({
3999                                 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
4000                                 priority: -40
4001                         }) );
4002                 }
4003
4004                 if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
4005                         this.toolbar.set( 'suggestedDimensions', new View({
4006                                 el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' &times; ' + this.options.suggestedHeight + '</div>' )[0],
4007                                 priority: -40
4008                         }) );
4009                 }
4010         },
4011
4012         updateContent: function() {
4013                 var view = this,
4014                         noItemsView;
4015
4016                 if ( this.controller.isModeActive( 'grid' ) ) {
4017                         noItemsView = view.attachmentsNoResults;
4018                 } else {
4019                         noItemsView = view.uploader;
4020                 }
4021
4022                 if ( ! this.collection.length ) {
4023                         this.toolbar.get( 'spinner' ).show();
4024                         this.dfd = this.collection.more().done( function() {
4025                                 if ( ! view.collection.length ) {
4026                                         noItemsView.$el.removeClass( 'hidden' );
4027                                 } else {
4028                                         noItemsView.$el.addClass( 'hidden' );
4029                                 }
4030                                 view.toolbar.get( 'spinner' ).hide();
4031                         } );
4032                 } else {
4033                         noItemsView.$el.addClass( 'hidden' );
4034                         view.toolbar.get( 'spinner' ).hide();
4035                 }
4036         },
4037
4038         createUploader: function() {
4039                 this.uploader = new wp.media.view.UploaderInline({
4040                         controller: this.controller,
4041                         status:     false,
4042                         message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
4043                         canClose:   this.controller.isModeActive( 'grid' )
4044                 });
4045
4046                 this.uploader.hide();
4047                 this.views.add( this.uploader );
4048         },
4049
4050         toggleUploader: function() {
4051                 if ( this.uploader.$el.hasClass( 'hidden' ) ) {
4052                         this.uploader.show();
4053                 } else {
4054                         this.uploader.hide();
4055                 }
4056         },
4057
4058         createAttachments: function() {
4059                 this.attachments = new wp.media.view.Attachments({
4060                         controller:           this.controller,
4061                         collection:           this.collection,
4062                         selection:            this.options.selection,
4063                         model:                this.model,
4064                         sortable:             this.options.sortable,
4065                         scrollElement:        this.options.scrollElement,
4066                         idealColumnWidth:     this.options.idealColumnWidth,
4067
4068                         // The single `Attachment` view to be used in the `Attachments` view.
4069                         AttachmentView: this.options.AttachmentView
4070                 });
4071
4072                 // Add keydown listener to the instance of the Attachments view
4073                 this.attachments.listenTo( this.controller, 'attachment:keydown:arrow',     this.attachments.arrowEvent );
4074                 this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus );
4075
4076                 this.views.add( this.attachments );
4077
4078
4079                 if ( this.controller.isModeActive( 'grid' ) ) {
4080                         this.attachmentsNoResults = new View({
4081                                 controller: this.controller,
4082                                 tagName: 'p'
4083                         });
4084
4085                         this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
4086                         this.attachmentsNoResults.$el.html( l10n.noMedia );
4087
4088                         this.views.add( this.attachmentsNoResults );
4089                 }
4090         },
4091
4092         createSidebar: function() {
4093                 var options = this.options,
4094                         selection = options.selection,
4095                         sidebar = this.sidebar = new wp.media.view.Sidebar({
4096                                 controller: this.controller
4097                         });
4098
4099                 this.views.add( sidebar );
4100
4101                 if ( this.controller.uploader ) {
4102                         sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
4103                                 controller: this.controller,
4104                                 priority:   40
4105                         }) );
4106                 }
4107
4108                 selection.on( 'selection:single', this.createSingle, this );
4109                 selection.on( 'selection:unsingle', this.disposeSingle, this );
4110
4111                 if ( selection.single() ) {
4112                         this.createSingle();
4113                 }
4114         },
4115
4116         createSingle: function() {
4117                 var sidebar = this.sidebar,
4118                         single = this.options.selection.single();
4119
4120                 sidebar.set( 'details', new wp.media.view.Attachment.Details({
4121                         controller: this.controller,
4122                         model:      single,
4123                         priority:   80
4124                 }) );
4125
4126                 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
4127                         controller: this.controller,
4128                         model:      single,
4129                         priority:   120
4130                 }) );
4131
4132                 if ( this.options.display ) {
4133                         sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
4134                                 controller:   this.controller,
4135                                 model:        this.model.display( single ),
4136                                 attachment:   single,
4137                                 priority:     160,
4138                                 userSettings: this.model.get('displayUserSettings')
4139                         }) );
4140                 }
4141
4142                 // Show the sidebar on mobile
4143                 if ( this.model.id === 'insert' ) {
4144                         sidebar.$el.addClass( 'visible' );
4145                 }
4146         },
4147
4148         disposeSingle: function() {
4149                 var sidebar = this.sidebar;
4150                 sidebar.unset('details');
4151                 sidebar.unset('compat');
4152                 sidebar.unset('display');
4153                 // Hide the sidebar on mobile
4154                 sidebar.$el.removeClass( 'visible' );
4155         }
4156 });
4157
4158 module.exports = AttachmentsBrowser;
4159
4160 },{}],33:[function(require,module,exports){
4161 /**
4162  * wp.media.view.Attachments.Selection
4163  *
4164  * @class
4165  * @augments wp.media.view.Attachments
4166  * @augments wp.media.View
4167  * @augments wp.Backbone.View
4168  * @augments Backbone.View
4169  */
4170 var Attachments = wp.media.view.Attachments,
4171         Selection;
4172
4173 Selection = Attachments.extend({
4174         events: {},
4175         initialize: function() {
4176                 _.defaults( this.options, {
4177                         sortable:   false,
4178                         resize:     false,
4179
4180                         // The single `Attachment` view to be used in the `Attachments` view.
4181                         AttachmentView: wp.media.view.Attachment.Selection
4182                 });
4183                 // Call 'initialize' directly on the parent class.
4184                 return Attachments.prototype.initialize.apply( this, arguments );
4185         }
4186 });
4187
4188 module.exports = Selection;
4189
4190 },{}],34:[function(require,module,exports){
4191 /**
4192  * wp.media.view.ButtonGroup
4193  *
4194  * @class
4195  * @augments wp.media.View
4196  * @augments wp.Backbone.View
4197  * @augments Backbone.View
4198  */
4199 var $ = Backbone.$,
4200         ButtonGroup;
4201
4202 ButtonGroup = wp.media.View.extend({
4203         tagName:   'div',
4204         className: 'button-group button-large media-button-group',
4205
4206         initialize: function() {
4207                 /**
4208                  * @member {wp.media.view.Button[]}
4209                  */
4210                 this.buttons = _.map( this.options.buttons || [], function( button ) {
4211                         if ( button instanceof Backbone.View ) {
4212                                 return button;
4213                         } else {
4214                                 return new wp.media.view.Button( button ).render();
4215                         }
4216                 });
4217
4218                 delete this.options.buttons;
4219
4220                 if ( this.options.classes ) {
4221                         this.$el.addClass( this.options.classes );
4222                 }
4223         },
4224
4225         /**
4226          * @returns {wp.media.view.ButtonGroup}
4227          */
4228         render: function() {
4229                 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
4230                 return this;
4231         }
4232 });
4233
4234 module.exports = ButtonGroup;
4235
4236 },{}],35:[function(require,module,exports){
4237 /**
4238  * wp.media.view.Button
4239  *
4240  * @class
4241  * @augments wp.media.View
4242  * @augments wp.Backbone.View
4243  * @augments Backbone.View
4244  */
4245 var Button = wp.media.View.extend({
4246         tagName:    'button',
4247         className:  'media-button',
4248         attributes: { type: 'button' },
4249
4250         events: {
4251                 'click': 'click'
4252         },
4253
4254         defaults: {
4255                 text:     '',
4256                 style:    '',
4257                 size:     'large',
4258                 disabled: false
4259         },
4260
4261         initialize: function() {
4262                 /**
4263                  * Create a model with the provided `defaults`.
4264                  *
4265                  * @member {Backbone.Model}
4266                  */
4267                 this.model = new Backbone.Model( this.defaults );
4268
4269                 // If any of the `options` have a key from `defaults`, apply its
4270                 // value to the `model` and remove it from the `options object.
4271                 _.each( this.defaults, function( def, key ) {
4272                         var value = this.options[ key ];
4273                         if ( _.isUndefined( value ) ) {
4274                                 return;
4275                         }
4276
4277                         this.model.set( key, value );
4278                         delete this.options[ key ];
4279                 }, this );
4280
4281                 this.listenTo( this.model, 'change', this.render );
4282         },
4283         /**
4284          * @returns {wp.media.view.Button} Returns itself to allow chaining
4285          */
4286         render: function() {
4287                 var classes = [ 'button', this.className ],
4288                         model = this.model.toJSON();
4289
4290                 if ( model.style ) {
4291                         classes.push( 'button-' + model.style );
4292                 }
4293
4294                 if ( model.size ) {
4295                         classes.push( 'button-' + model.size );
4296                 }
4297
4298                 classes = _.uniq( classes.concat( this.options.classes ) );
4299                 this.el.className = classes.join(' ');
4300
4301                 this.$el.attr( 'disabled', model.disabled );
4302                 this.$el.text( this.model.get('text') );
4303
4304                 return this;
4305         },
4306         /**
4307          * @param {Object} event
4308          */
4309         click: function( event ) {
4310                 if ( '#' === this.attributes.href ) {
4311                         event.preventDefault();
4312                 }
4313
4314                 if ( this.options.click && ! this.model.get('disabled') ) {
4315                         this.options.click.apply( this, arguments );
4316                 }
4317         }
4318 });
4319
4320 module.exports = Button;
4321
4322 },{}],36:[function(require,module,exports){
4323 /**
4324  * wp.media.view.Cropper
4325  *
4326  * Uses the imgAreaSelect plugin to allow a user to crop an image.
4327  *
4328  * Takes imgAreaSelect options from
4329  * wp.customize.HeaderControl.calculateImageSelectOptions via
4330  * wp.customize.HeaderControl.openMM.
4331  *
4332  * @class
4333  * @augments wp.media.View
4334  * @augments wp.Backbone.View
4335  * @augments Backbone.View
4336  */
4337 var View = wp.media.View,
4338         UploaderStatus = wp.media.view.UploaderStatus,
4339         l10n = wp.media.view.l10n,
4340         $ = jQuery,
4341         Cropper;
4342
4343 Cropper = View.extend({
4344         className: 'crop-content',
4345         template: wp.template('crop-content'),
4346         initialize: function() {
4347                 _.bindAll(this, 'onImageLoad');
4348         },
4349         ready: function() {
4350                 this.controller.frame.on('content:error:crop', this.onError, this);
4351                 this.$image = this.$el.find('.crop-image');
4352                 this.$image.on('load', this.onImageLoad);
4353                 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
4354         },
4355         remove: function() {
4356                 $(window).off('resize.cropper');
4357                 this.$el.remove();
4358                 this.$el.off();
4359                 View.prototype.remove.apply(this, arguments);
4360         },
4361         prepare: function() {
4362                 return {
4363                         title: l10n.cropYourImage,
4364                         url: this.options.attachment.get('url')
4365                 };
4366         },
4367         onImageLoad: function() {
4368                 var imgOptions = this.controller.get('imgSelectOptions');
4369                 if (typeof imgOptions === 'function') {
4370                         imgOptions = imgOptions(this.options.attachment, this.controller);
4371                 }
4372
4373                 imgOptions = _.extend(imgOptions, {parent: this.$el});
4374                 this.trigger('image-loaded');
4375                 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
4376         },
4377         onError: function() {
4378                 var filename = this.options.attachment.get('filename');
4379
4380                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
4381                         filename: UploaderStatus.prototype.filename(filename),
4382                         message: window._wpMediaViewsL10n.cropError
4383                 }), { at: 0 });
4384         }
4385 });
4386
4387 module.exports = Cropper;
4388
4389 },{}],37:[function(require,module,exports){
4390 /**
4391  * wp.media.view.EditImage
4392  *
4393  * @class
4394  * @augments wp.media.View
4395  * @augments wp.Backbone.View
4396  * @augments Backbone.View
4397  */
4398 var View = wp.media.View,
4399         EditImage;
4400
4401 EditImage = View.extend({
4402         className: 'image-editor',
4403         template: wp.template('image-editor'),
4404
4405         initialize: function( options ) {
4406                 this.editor = window.imageEdit;
4407                 this.controller = options.controller;
4408                 View.prototype.initialize.apply( this, arguments );
4409         },
4410
4411         prepare: function() {
4412                 return this.model.toJSON();
4413         },
4414
4415         loadEditor: function() {
4416                 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
4417                 dfd.done( _.bind( this.focus, this ) );
4418         },
4419
4420         focus: function() {
4421                 this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
4422         },
4423
4424         back: function() {
4425                 var lastState = this.controller.lastState();
4426                 this.controller.setState( lastState );
4427         },
4428
4429         refresh: function() {
4430                 this.model.fetch();
4431         },
4432
4433         save: function() {
4434                 var lastState = this.controller.lastState();
4435
4436                 this.model.fetch().done( _.bind( function() {
4437                         this.controller.setState( lastState );
4438                 }, this ) );
4439         }
4440
4441 });
4442
4443 module.exports = EditImage;
4444
4445 },{}],38:[function(require,module,exports){
4446 /**
4447  * wp.media.view.Embed
4448  *
4449  * @class
4450  * @augments wp.media.View
4451  * @augments wp.Backbone.View
4452  * @augments Backbone.View
4453  */
4454 var Embed = wp.media.View.extend({
4455         className: 'media-embed',
4456
4457         initialize: function() {
4458                 /**
4459                  * @member {wp.media.view.EmbedUrl}
4460                  */
4461                 this.url = new wp.media.view.EmbedUrl({
4462                         controller: this.controller,
4463                         model:      this.model.props
4464                 }).render();
4465
4466                 this.views.set([ this.url ]);
4467                 this.refresh();
4468                 this.listenTo( this.model, 'change:type', this.refresh );
4469                 this.listenTo( this.model, 'change:loading', this.loading );
4470         },
4471
4472         /**
4473          * @param {Object} view
4474          */
4475         settings: function( view ) {
4476                 if ( this._settings ) {
4477                         this._settings.remove();
4478                 }
4479                 this._settings = view;
4480                 this.views.add( view );
4481         },
4482
4483         refresh: function() {
4484                 var type = this.model.get('type'),
4485                         constructor;
4486
4487                 if ( 'image' === type ) {
4488                         constructor = wp.media.view.EmbedImage;
4489                 } else if ( 'link' === type ) {
4490                         constructor = wp.media.view.EmbedLink;
4491                 } else {
4492                         return;
4493                 }
4494
4495                 this.settings( new constructor({
4496                         controller: this.controller,
4497                         model:      this.model.props,
4498                         priority:   40
4499                 }) );
4500         },
4501
4502         loading: function() {
4503                 this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
4504         }
4505 });
4506
4507 module.exports = Embed;
4508
4509 },{}],39:[function(require,module,exports){
4510 /**
4511  * wp.media.view.EmbedImage
4512  *
4513  * @class
4514  * @augments wp.media.view.Settings.AttachmentDisplay
4515  * @augments wp.media.view.Settings
4516  * @augments wp.media.View
4517  * @augments wp.Backbone.View
4518  * @augments Backbone.View
4519  */
4520 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
4521         EmbedImage;
4522
4523 EmbedImage = AttachmentDisplay.extend({
4524         className: 'embed-media-settings',
4525         template:  wp.template('embed-image-settings'),
4526
4527         initialize: function() {
4528                 /**
4529                  * Call `initialize` directly on parent class with passed arguments
4530                  */
4531                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
4532                 this.listenTo( this.model, 'change:url', this.updateImage );
4533         },
4534
4535         updateImage: function() {
4536                 this.$('img').attr( 'src', this.model.get('url') );
4537         }
4538 });
4539
4540 module.exports = EmbedImage;
4541
4542 },{}],40:[function(require,module,exports){
4543 /**
4544  * wp.media.view.EmbedLink
4545  *
4546  * @class
4547  * @augments wp.media.view.Settings
4548  * @augments wp.media.View
4549  * @augments wp.Backbone.View
4550  * @augments Backbone.View
4551  */
4552 var $ = jQuery,
4553         EmbedLink;
4554
4555 EmbedLink = wp.media.view.Settings.extend({
4556         className: 'embed-link-settings',
4557         template:  wp.template('embed-link-settings'),
4558
4559         initialize: function() {
4560                 this.listenTo( this.model, 'change:url', this.updateoEmbed );
4561         },
4562
4563         updateoEmbed: _.debounce( function() {
4564                 var url = this.model.get( 'url' );
4565
4566                 // clear out previous results
4567                 this.$('.embed-container').hide().find('.embed-preview').empty();
4568                 this.$( '.setting' ).hide();
4569
4570                 // only proceed with embed if the field contains more than 11 characters
4571                 // Example: http://a.io is 11 chars
4572                 if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
4573                         return;
4574                 }
4575
4576                 this.fetch();
4577         }, wp.media.controller.Embed.sensitivity ),
4578
4579         fetch: function() {
4580                 var embed;
4581
4582                 // check if they haven't typed in 500 ms
4583                 if ( $('#embed-url-field').val() !== this.model.get('url') ) {
4584                         return;
4585                 }
4586
4587                 if ( this.dfd && 'pending' === this.dfd.state() ) {
4588                         this.dfd.abort();
4589                 }
4590
4591                 embed = new wp.shortcode({
4592                         tag: 'embed',
4593                         attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ),
4594                         content: this.model.get('url')
4595                 });
4596
4597                 this.dfd = $.ajax({
4598                         type:    'POST',
4599                         url:     wp.ajax.settings.url,
4600                         context: this,
4601                         data:    {
4602                                 action: 'parse-embed',
4603                                 post_ID: wp.media.view.settings.post.id,
4604                                 shortcode: embed.string()
4605                         }
4606                 })
4607                         .done( this.renderoEmbed )
4608                         .fail( this.renderFail );
4609         },
4610
4611         renderFail: function ( response, status ) {
4612                 if ( 'abort' === status ) {
4613                         return;
4614                 }
4615                 this.$( '.link-text' ).show();
4616         },
4617
4618         renderoEmbed: function( response ) {
4619                 var html = ( response && response.data && response.data.body ) || '';
4620
4621                 if ( html ) {
4622                         this.$('.embed-container').show().find('.embed-preview').html( html );
4623                 } else {
4624                         this.renderFail();
4625                 }
4626         }
4627 });
4628
4629 module.exports = EmbedLink;
4630
4631 },{}],41:[function(require,module,exports){
4632 /**
4633  * wp.media.view.EmbedUrl
4634  *
4635  * @class
4636  * @augments wp.media.View
4637  * @augments wp.Backbone.View
4638  * @augments Backbone.View
4639  */
4640 var View = wp.media.View,
4641         $ = jQuery,
4642         EmbedUrl;
4643
4644 EmbedUrl = View.extend({
4645         tagName:   'label',
4646         className: 'embed-url',
4647
4648         events: {
4649                 'input':  'url',
4650                 'keyup':  'url',
4651                 'change': 'url'
4652         },
4653
4654         initialize: function() {
4655                 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
4656                 this.input = this.$input[0];
4657
4658                 this.spinner = $('<span class="spinner" />')[0];
4659                 this.$el.append([ this.input, this.spinner ]);
4660
4661                 this.listenTo( this.model, 'change:url', this.render );
4662
4663                 if ( this.model.get( 'url' ) ) {
4664                         _.delay( _.bind( function () {
4665                                 this.model.trigger( 'change:url' );
4666                         }, this ), 500 );
4667                 }
4668         },
4669         /**
4670          * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
4671          */
4672         render: function() {
4673                 var $input = this.$input;
4674
4675                 if ( $input.is(':focus') ) {
4676                         return;
4677                 }
4678
4679                 this.input.value = this.model.get('url') || 'http://';
4680                 /**
4681                  * Call `render` directly on parent class with passed arguments
4682                  */
4683                 View.prototype.render.apply( this, arguments );
4684                 return this;
4685         },
4686
4687         ready: function() {
4688                 if ( ! wp.media.isTouchDevice ) {
4689                         this.focus();
4690                 }
4691         },
4692
4693         url: function( event ) {
4694                 this.model.set( 'url', event.target.value );
4695         },
4696
4697         /**
4698          * If the input is visible, focus and select its contents.
4699          */
4700         focus: function() {
4701                 var $input = this.$input;
4702                 if ( $input.is(':visible') ) {
4703                         $input.focus()[0].select();
4704                 }
4705         }
4706 });
4707
4708 module.exports = EmbedUrl;
4709
4710 },{}],42:[function(require,module,exports){
4711 /**
4712  * wp.media.view.FocusManager
4713  *
4714  * @class
4715  * @augments wp.media.View
4716  * @augments wp.Backbone.View
4717  * @augments Backbone.View
4718  */
4719 var FocusManager = wp.media.View.extend({
4720
4721         events: {
4722                 'keydown': 'constrainTabbing'
4723         },
4724
4725         focus: function() { // Reset focus on first left menu item
4726                 this.$('.media-menu-item').first().focus();
4727         },
4728         /**
4729          * @param {Object} event
4730          */
4731         constrainTabbing: function( event ) {
4732                 var tabbables;
4733
4734                 // Look for the tab key.
4735                 if ( 9 !== event.keyCode ) {
4736                         return;
4737                 }
4738
4739                 // Skip the file input added by Plupload.
4740                 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
4741
4742                 // Keep tab focus within media modal while it's open
4743                 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
4744                         tabbables.first().focus();
4745                         return false;
4746                 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
4747                         tabbables.last().focus();
4748                         return false;
4749                 }
4750         }
4751
4752 });
4753
4754 module.exports = FocusManager;
4755
4756 },{}],43:[function(require,module,exports){
4757 /**
4758  * wp.media.view.Frame
4759  *
4760  * A frame is a composite view consisting of one or more regions and one or more
4761  * states.
4762  *
4763  * @see wp.media.controller.State
4764  * @see wp.media.controller.Region
4765  *
4766  * @class
4767  * @augments wp.media.View
4768  * @augments wp.Backbone.View
4769  * @augments Backbone.View
4770  * @mixes wp.media.controller.StateMachine
4771  */
4772 var Frame = wp.media.View.extend({
4773         initialize: function() {
4774                 _.defaults( this.options, {
4775                         mode: [ 'select' ]
4776                 });
4777                 this._createRegions();
4778                 this._createStates();
4779                 this._createModes();
4780         },
4781
4782         _createRegions: function() {
4783                 // Clone the regions array.
4784                 this.regions = this.regions ? this.regions.slice() : [];
4785
4786                 // Initialize regions.
4787                 _.each( this.regions, function( region ) {
4788                         this[ region ] = new wp.media.controller.Region({
4789                                 view:     this,
4790                                 id:       region,
4791                                 selector: '.media-frame-' + region
4792                         });
4793                 }, this );
4794         },
4795         /**
4796          * Create the frame's states.
4797          *
4798          * @see wp.media.controller.State
4799          * @see wp.media.controller.StateMachine
4800          *
4801          * @fires wp.media.controller.State#ready
4802          */
4803         _createStates: function() {
4804                 // Create the default `states` collection.
4805                 this.states = new Backbone.Collection( null, {
4806                         model: wp.media.controller.State
4807                 });
4808
4809                 // Ensure states have a reference to the frame.
4810                 this.states.on( 'add', function( model ) {
4811                         model.frame = this;
4812                         model.trigger('ready');
4813                 }, this );
4814
4815                 if ( this.options.states ) {
4816                         this.states.add( this.options.states );
4817                 }
4818         },
4819
4820         /**
4821          * A frame can be in a mode or multiple modes at one time.
4822          *
4823          * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
4824          */
4825         _createModes: function() {
4826                 // Store active "modes" that the frame is in. Unrelated to region modes.
4827                 this.activeModes = new Backbone.Collection();
4828                 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
4829
4830                 _.each( this.options.mode, function( mode ) {
4831                         this.activateMode( mode );
4832                 }, this );
4833         },
4834         /**
4835          * Reset all states on the frame to their defaults.
4836          *
4837          * @returns {wp.media.view.Frame} Returns itself to allow chaining
4838          */
4839         reset: function() {
4840                 this.states.invoke( 'trigger', 'reset' );
4841                 return this;
4842         },
4843         /**
4844          * Map activeMode collection events to the frame.
4845          */
4846         triggerModeEvents: function( model, collection, options ) {
4847                 var collectionEvent,
4848                         modeEventMap = {
4849                                 add: 'activate',
4850                                 remove: 'deactivate'
4851                         },
4852                         eventToTrigger;
4853                 // Probably a better way to do this.
4854                 _.each( options, function( value, key ) {
4855                         if ( value ) {
4856                                 collectionEvent = key;
4857                         }
4858                 } );
4859
4860                 if ( ! _.has( modeEventMap, collectionEvent ) ) {
4861                         return;
4862                 }
4863
4864                 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
4865                 this.trigger( eventToTrigger );
4866         },
4867         /**
4868          * Activate a mode on the frame.
4869          *
4870          * @param string mode Mode ID.
4871          * @returns {this} Returns itself to allow chaining.
4872          */
4873         activateMode: function( mode ) {
4874                 // Bail if the mode is already active.
4875                 if ( this.isModeActive( mode ) ) {
4876                         return;
4877                 }
4878                 this.activeModes.add( [ { id: mode } ] );
4879                 // Add a CSS class to the frame so elements can be styled for the mode.
4880                 this.$el.addClass( 'mode-' + mode );
4881
4882                 return this;
4883         },
4884         /**
4885          * Deactivate a mode on the frame.
4886          *
4887          * @param string mode Mode ID.
4888          * @returns {this} Returns itself to allow chaining.
4889          */
4890         deactivateMode: function( mode ) {
4891                 // Bail if the mode isn't active.
4892                 if ( ! this.isModeActive( mode ) ) {
4893                         return this;
4894                 }
4895                 this.activeModes.remove( this.activeModes.where( { id: mode } ) );
4896                 this.$el.removeClass( 'mode-' + mode );
4897                 /**
4898                  * Frame mode deactivation event.
4899                  *
4900                  * @event this#{mode}:deactivate
4901                  */
4902                 this.trigger( mode + ':deactivate' );
4903
4904                 return this;
4905         },
4906         /**
4907          * Check if a mode is enabled on the frame.
4908          *
4909          * @param  string mode Mode ID.
4910          * @return bool
4911          */
4912         isModeActive: function( mode ) {
4913                 return Boolean( this.activeModes.where( { id: mode } ).length );
4914         }
4915 });
4916
4917 // Make the `Frame` a `StateMachine`.
4918 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
4919
4920 module.exports = Frame;
4921
4922 },{}],44:[function(require,module,exports){
4923 /**
4924  * wp.media.view.MediaFrame.ImageDetails
4925  *
4926  * A media frame for manipulating an image that's already been inserted
4927  * into a post.
4928  *
4929  * @class
4930  * @augments wp.media.view.MediaFrame.Select
4931  * @augments wp.media.view.MediaFrame
4932  * @augments wp.media.view.Frame
4933  * @augments wp.media.View
4934  * @augments wp.Backbone.View
4935  * @augments Backbone.View
4936  * @mixes wp.media.controller.StateMachine
4937  */
4938 var Select = wp.media.view.MediaFrame.Select,
4939         l10n = wp.media.view.l10n,
4940         ImageDetails;
4941
4942 ImageDetails = Select.extend({
4943         defaults: {
4944                 id:      'image',
4945                 url:     '',
4946                 menu:    'image-details',
4947                 content: 'image-details',
4948                 toolbar: 'image-details',
4949                 type:    'link',
4950                 title:    l10n.imageDetailsTitle,
4951                 priority: 120
4952         },
4953
4954         initialize: function( options ) {
4955                 this.image = new wp.media.model.PostImage( options.metadata );
4956                 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
4957                 Select.prototype.initialize.apply( this, arguments );
4958         },
4959
4960         bindHandlers: function() {
4961                 Select.prototype.bindHandlers.apply( this, arguments );
4962                 this.on( 'menu:create:image-details', this.createMenu, this );
4963                 this.on( 'content:create:image-details', this.imageDetailsContent, this );
4964                 this.on( 'content:render:edit-image', this.editImageContent, this );
4965                 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
4966                 // override the select toolbar
4967                 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
4968         },
4969
4970         createStates: function() {
4971                 this.states.add([
4972                         new wp.media.controller.ImageDetails({
4973                                 image: this.image,
4974                                 editable: false
4975                         }),
4976                         new wp.media.controller.ReplaceImage({
4977                                 id: 'replace-image',
4978                                 library: wp.media.query( { type: 'image' } ),
4979                                 image: this.image,
4980                                 multiple:  false,
4981                                 title:     l10n.imageReplaceTitle,
4982                                 toolbar: 'replace',
4983                                 priority:  80,
4984                                 displaySettings: true
4985                         }),
4986                         new wp.media.controller.EditImage( {
4987                                 image: this.image,
4988                                 selection: this.options.selection
4989                         } )
4990                 ]);
4991         },
4992
4993         imageDetailsContent: function( options ) {
4994                 options.view = new wp.media.view.ImageDetails({
4995                         controller: this,
4996                         model: this.state().image,
4997                         attachment: this.state().image.attachment
4998                 });
4999         },
5000
5001         editImageContent: function() {
5002                 var state = this.state(),
5003                         model = state.get('image'),
5004                         view;
5005
5006                 if ( ! model ) {
5007                         return;
5008                 }
5009
5010                 view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
5011
5012                 this.content.set( view );
5013
5014                 // after bringing in the frame, load the actual editor via an ajax call
5015                 view.loadEditor();
5016
5017         },
5018
5019         renderImageDetailsToolbar: function() {
5020                 this.toolbar.set( new wp.media.view.Toolbar({
5021                         controller: this,
5022                         items: {
5023                                 select: {
5024                                         style:    'primary',
5025                                         text:     l10n.update,
5026                                         priority: 80,
5027
5028                                         click: function() {
5029                                                 var controller = this.controller,
5030                                                         state = controller.state();
5031
5032                                                 controller.close();
5033
5034                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
5035                                                 // perhaps wp.html.string to at least to build the <img />
5036                                                 state.trigger( 'update', controller.image.toJSON() );
5037
5038                                                 // Restore and reset the default state.
5039                                                 controller.setState( controller.options.state );
5040                                                 controller.reset();
5041                                         }
5042                                 }
5043                         }
5044                 }) );
5045         },
5046
5047         renderReplaceImageToolbar: function() {
5048                 var frame = this,
5049                         lastState = frame.lastState(),
5050                         previous = lastState && lastState.id;
5051
5052                 this.toolbar.set( new wp.media.view.Toolbar({
5053                         controller: this,
5054                         items: {
5055                                 back: {
5056                                         text:     l10n.back,
5057                                         priority: 20,
5058                                         click:    function() {
5059                                                 if ( previous ) {
5060                                                         frame.setState( previous );
5061                                                 } else {
5062                                                         frame.close();
5063                                                 }
5064                                         }
5065                                 },
5066
5067                                 replace: {
5068                                         style:    'primary',
5069                                         text:     l10n.replace,
5070                                         priority: 80,
5071
5072                                         click: function() {
5073                                                 var controller = this.controller,
5074                                                         state = controller.state(),
5075                                                         selection = state.get( 'selection' ),
5076                                                         attachment = selection.single();
5077
5078                                                 controller.close();
5079
5080                                                 controller.image.changeAttachment( attachment, state.display( attachment ) );
5081
5082                                                 // not sure if we want to use wp.media.string.image which will create a shortcode or
5083                                                 // perhaps wp.html.string to at least to build the <img />
5084                                                 state.trigger( 'replace', controller.image.toJSON() );
5085
5086                                                 // Restore and reset the default state.
5087                                                 controller.setState( controller.options.state );
5088                                                 controller.reset();
5089                                         }
5090                                 }
5091                         }
5092                 }) );
5093         }
5094
5095 });
5096
5097 module.exports = ImageDetails;
5098
5099 },{}],45:[function(require,module,exports){
5100 /**
5101  * wp.media.view.MediaFrame.Post
5102  *
5103  * The frame for manipulating media on the Edit Post page.
5104  *
5105  * @class
5106  * @augments wp.media.view.MediaFrame.Select
5107  * @augments wp.media.view.MediaFrame
5108  * @augments wp.media.view.Frame
5109  * @augments wp.media.View
5110  * @augments wp.Backbone.View
5111  * @augments Backbone.View
5112  * @mixes wp.media.controller.StateMachine
5113  */
5114 var Select = wp.media.view.MediaFrame.Select,
5115         Library = wp.media.controller.Library,
5116         l10n = wp.media.view.l10n,
5117         Post;
5118
5119 Post = Select.extend({
5120         initialize: function() {
5121                 this.counts = {
5122                         audio: {
5123                                 count: wp.media.view.settings.attachmentCounts.audio,
5124                                 state: 'playlist'
5125                         },
5126                         video: {
5127                                 count: wp.media.view.settings.attachmentCounts.video,
5128                                 state: 'video-playlist'
5129                         }
5130                 };
5131
5132                 _.defaults( this.options, {
5133                         multiple:  true,
5134                         editing:   false,
5135                         state:    'insert',
5136                         metadata:  {}
5137                 });
5138
5139                 // Call 'initialize' directly on the parent class.
5140                 Select.prototype.initialize.apply( this, arguments );
5141                 this.createIframeStates();
5142
5143         },
5144
5145         /**
5146          * Create the default states.
5147          */
5148         createStates: function() {
5149                 var options = this.options;
5150
5151                 this.states.add([
5152                         // Main states.
5153                         new Library({
5154                                 id:         'insert',
5155                                 title:      l10n.insertMediaTitle,
5156                                 priority:   20,
5157                                 toolbar:    'main-insert',
5158                                 filterable: 'all',
5159                                 library:    wp.media.query( options.library ),
5160                                 multiple:   options.multiple ? 'reset' : false,
5161                                 editable:   true,
5162
5163                                 // If the user isn't allowed to edit fields,
5164                                 // can they still edit it locally?
5165                                 allowLocalEdits: true,
5166
5167                                 // Show the attachment display settings.
5168                                 displaySettings: true,
5169                                 // Update user settings when users adjust the
5170                                 // attachment display settings.
5171                                 displayUserSettings: true
5172                         }),
5173
5174                         new Library({
5175                                 id:         'gallery',
5176                                 title:      l10n.createGalleryTitle,
5177                                 priority:   40,
5178                                 toolbar:    'main-gallery',
5179                                 filterable: 'uploaded',
5180                                 multiple:   'add',
5181                                 editable:   false,
5182
5183                                 library:  wp.media.query( _.defaults({
5184                                         type: 'image'
5185                                 }, options.library ) )
5186                         }),
5187
5188                         // Embed states.
5189                         new wp.media.controller.Embed( { metadata: options.metadata } ),
5190
5191                         new wp.media.controller.EditImage( { model: options.editImage } ),
5192
5193                         // Gallery states.
5194                         new wp.media.controller.GalleryEdit({
5195                                 library: options.selection,
5196                                 editing: options.editing,
5197                                 menu:    'gallery'
5198                         }),
5199
5200                         new wp.media.controller.GalleryAdd(),
5201
5202                         new Library({
5203                                 id:         'playlist',
5204                                 title:      l10n.createPlaylistTitle,
5205                                 priority:   60,
5206                                 toolbar:    'main-playlist',
5207                                 filterable: 'uploaded',
5208                                 multiple:   'add',
5209                                 editable:   false,
5210
5211                                 library:  wp.media.query( _.defaults({
5212                                         type: 'audio'
5213                                 }, options.library ) )
5214                         }),
5215
5216                         // Playlist states.
5217                         new wp.media.controller.CollectionEdit({
5218                                 type: 'audio',
5219                                 collectionType: 'playlist',
5220                                 title:          l10n.editPlaylistTitle,
5221                                 SettingsView:   wp.media.view.Settings.Playlist,
5222                                 library:        options.selection,
5223                                 editing:        options.editing,
5224                                 menu:           'playlist',
5225                                 dragInfoText:   l10n.playlistDragInfo,
5226                                 dragInfo:       false
5227                         }),
5228
5229                         new wp.media.controller.CollectionAdd({
5230                                 type: 'audio',
5231                                 collectionType: 'playlist',
5232                                 title: l10n.addToPlaylistTitle
5233                         }),
5234
5235                         new Library({
5236                                 id:         'video-playlist',
5237                                 title:      l10n.createVideoPlaylistTitle,
5238                                 priority:   60,
5239                                 toolbar:    'main-video-playlist',
5240                                 filterable: 'uploaded',
5241                                 multiple:   'add',
5242                                 editable:   false,
5243
5244                                 library:  wp.media.query( _.defaults({
5245                                         type: 'video'
5246                                 }, options.library ) )
5247                         }),
5248
5249                         new wp.media.controller.CollectionEdit({
5250                                 type: 'video',
5251                                 collectionType: 'playlist',
5252                                 title:          l10n.editVideoPlaylistTitle,
5253                                 SettingsView:   wp.media.view.Settings.Playlist,
5254                                 library:        options.selection,
5255                                 editing:        options.editing,
5256                                 menu:           'video-playlist',
5257                                 dragInfoText:   l10n.videoPlaylistDragInfo,
5258                                 dragInfo:       false
5259                         }),
5260
5261                         new wp.media.controller.CollectionAdd({
5262                                 type: 'video',
5263                                 collectionType: 'playlist',
5264                                 title: l10n.addToVideoPlaylistTitle
5265                         })
5266                 ]);
5267
5268                 if ( wp.media.view.settings.post.featuredImageId ) {
5269                         this.states.add( new wp.media.controller.FeaturedImage() );
5270                 }
5271         },
5272
5273         bindHandlers: function() {
5274                 var handlers, checkCounts;
5275
5276                 Select.prototype.bindHandlers.apply( this, arguments );
5277
5278                 this.on( 'activate', this.activate, this );
5279
5280                 // Only bother checking media type counts if one of the counts is zero
5281                 checkCounts = _.find( this.counts, function( type ) {
5282                         return type.count === 0;
5283                 } );
5284
5285                 if ( typeof checkCounts !== 'undefined' ) {
5286                         this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
5287                 }
5288
5289                 this.on( 'menu:create:gallery', this.createMenu, this );
5290                 this.on( 'menu:create:playlist', this.createMenu, this );
5291                 this.on( 'menu:create:video-playlist', this.createMenu, this );
5292                 this.on( 'toolbar:create:main-insert', this.createToolbar, this );
5293                 this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
5294                 this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
5295                 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
5296                 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
5297                 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
5298
5299                 handlers = {
5300                         menu: {
5301                                 'default': 'mainMenu',
5302                                 'gallery': 'galleryMenu',
5303                                 'playlist': 'playlistMenu',
5304                                 'video-playlist': 'videoPlaylistMenu'
5305                         },
5306
5307                         content: {
5308                                 'embed':          'embedContent',
5309                                 'edit-image':     'editImageContent',
5310                                 'edit-selection': 'editSelectionContent'
5311                         },
5312
5313                         toolbar: {
5314                                 'main-insert':      'mainInsertToolbar',
5315                                 'main-gallery':     'mainGalleryToolbar',
5316                                 'gallery-edit':     'galleryEditToolbar',
5317                                 'gallery-add':      'galleryAddToolbar',
5318                                 'main-playlist':        'mainPlaylistToolbar',
5319                                 'playlist-edit':        'playlistEditToolbar',
5320                                 'playlist-add':         'playlistAddToolbar',
5321                                 'main-video-playlist': 'mainVideoPlaylistToolbar',
5322                                 'video-playlist-edit': 'videoPlaylistEditToolbar',
5323                                 'video-playlist-add': 'videoPlaylistAddToolbar'
5324                         }
5325                 };
5326
5327                 _.each( handlers, function( regionHandlers, region ) {
5328                         _.each( regionHandlers, function( callback, handler ) {
5329                                 this.on( region + ':render:' + handler, this[ callback ], this );
5330                         }, this );
5331                 }, this );
5332         },
5333
5334         activate: function() {
5335                 // Hide menu items for states tied to particular media types if there are no items
5336                 _.each( this.counts, function( type ) {
5337                         if ( type.count < 1 ) {
5338                                 this.menuItemVisibility( type.state, 'hide' );
5339                         }
5340                 }, this );
5341         },
5342
5343         mediaTypeCounts: function( model, attr ) {
5344                 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
5345                         this.counts[ attr ].count++;
5346                         this.menuItemVisibility( this.counts[ attr ].state, 'show' );
5347                 }
5348         },
5349
5350         // Menus
5351         /**
5352          * @param {wp.Backbone.View} view
5353          */
5354         mainMenu: function( view ) {
5355                 view.set({
5356                         'library-separator': new wp.media.View({
5357                                 className: 'separator',
5358                                 priority: 100
5359                         })
5360                 });
5361         },
5362
5363         menuItemVisibility: function( state, visibility ) {
5364                 var menu = this.menu.get();
5365                 if ( visibility === 'hide' ) {
5366                         menu.hide( state );
5367                 } else if ( visibility === 'show' ) {
5368                         menu.show( state );
5369                 }
5370         },
5371         /**
5372          * @param {wp.Backbone.View} view
5373          */
5374         galleryMenu: function( view ) {
5375                 var lastState = this.lastState(),
5376                         previous = lastState && lastState.id,
5377                         frame = this;
5378
5379                 view.set({
5380                         cancel: {
5381                                 text:     l10n.cancelGalleryTitle,
5382                                 priority: 20,
5383                                 click:    function() {
5384                                         if ( previous ) {
5385                                                 frame.setState( previous );
5386                                         } else {
5387                                                 frame.close();
5388                                         }
5389
5390                                         // Keep focus inside media modal
5391                                         // after canceling a gallery
5392                                         this.controller.modal.focusManager.focus();
5393                                 }
5394                         },
5395                         separateCancel: new wp.media.View({
5396                                 className: 'separator',
5397                                 priority: 40
5398                         })
5399                 });
5400         },
5401
5402         playlistMenu: function( view ) {
5403                 var lastState = this.lastState(),
5404                         previous = lastState && lastState.id,
5405                         frame = this;
5406
5407                 view.set({
5408                         cancel: {
5409                                 text:     l10n.cancelPlaylistTitle,
5410                                 priority: 20,
5411                                 click:    function() {
5412                                         if ( previous ) {
5413                                                 frame.setState( previous );
5414                                         } else {
5415                                                 frame.close();
5416                                         }
5417                                 }
5418                         },
5419                         separateCancel: new wp.media.View({
5420                                 className: 'separator',
5421                                 priority: 40
5422                         })
5423                 });
5424         },
5425
5426         videoPlaylistMenu: function( view ) {
5427                 var lastState = this.lastState(),
5428                         previous = lastState && lastState.id,
5429                         frame = this;
5430
5431                 view.set({
5432                         cancel: {
5433                                 text:     l10n.cancelVideoPlaylistTitle,
5434                                 priority: 20,
5435                                 click:    function() {
5436                                         if ( previous ) {
5437                                                 frame.setState( previous );
5438                                         } else {
5439                                                 frame.close();
5440                                         }
5441                                 }
5442                         },
5443                         separateCancel: new wp.media.View({
5444                                 className: 'separator',
5445                                 priority: 40
5446                         })
5447                 });
5448         },
5449
5450         // Content
5451         embedContent: function() {
5452                 var view = new wp.media.view.Embed({
5453                         controller: this,
5454                         model:      this.state()
5455                 }).render();
5456
5457                 this.content.set( view );
5458
5459                 if ( ! wp.media.isTouchDevice ) {
5460                         view.url.focus();
5461                 }
5462         },
5463
5464         editSelectionContent: function() {
5465                 var state = this.state(),
5466                         selection = state.get('selection'),
5467                         view;
5468
5469                 view = new wp.media.view.AttachmentsBrowser({
5470                         controller: this,
5471                         collection: selection,
5472                         selection:  selection,
5473                         model:      state,
5474                         sortable:   true,
5475                         search:     false,
5476                         date:       false,
5477                         dragInfo:   true,
5478
5479                         AttachmentView: wp.media.view.Attachments.EditSelection
5480                 }).render();
5481
5482                 view.toolbar.set( 'backToLibrary', {
5483                         text:     l10n.returnToLibrary,
5484                         priority: -100,
5485
5486                         click: function() {
5487                                 this.controller.content.mode('browse');
5488                         }
5489                 });
5490
5491                 // Browse our library of attachments.
5492                 this.content.set( view );
5493
5494                 // Trigger the controller to set focus
5495                 this.trigger( 'edit:selection', this );
5496         },
5497
5498         editImageContent: function() {
5499                 var image = this.state().get('image'),
5500                         view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
5501
5502                 this.content.set( view );
5503
5504                 // after creating the wrapper view, load the actual editor via an ajax call
5505                 view.loadEditor();
5506
5507         },
5508
5509         // Toolbars
5510
5511         /**
5512          * @param {wp.Backbone.View} view
5513          */
5514         selectionStatusToolbar: function( view ) {
5515                 var editable = this.state().get('editable');
5516
5517                 view.set( 'selection', new wp.media.view.Selection({
5518                         controller: this,
5519                         collection: this.state().get('selection'),
5520                         priority:   -40,
5521
5522                         // If the selection is editable, pass the callback to
5523                         // switch the content mode.
5524                         editable: editable && function() {
5525                                 this.controller.content.mode('edit-selection');
5526                         }
5527                 }).render() );
5528         },
5529
5530         /**
5531          * @param {wp.Backbone.View} view
5532          */
5533         mainInsertToolbar: function( view ) {
5534                 var controller = this;
5535
5536                 this.selectionStatusToolbar( view );
5537
5538                 view.set( 'insert', {
5539                         style:    'primary',
5540                         priority: 80,
5541                         text:     l10n.insertIntoPost,
5542                         requires: { selection: true },
5543
5544                         /**
5545                          * @fires wp.media.controller.State#insert
5546                          */
5547                         click: function() {
5548                                 var state = controller.state(),
5549                                         selection = state.get('selection');
5550
5551                                 controller.close();
5552                                 state.trigger( 'insert', selection ).reset();
5553                         }
5554                 });
5555         },
5556
5557         /**
5558          * @param {wp.Backbone.View} view
5559          */
5560         mainGalleryToolbar: function( view ) {
5561                 var controller = this;
5562
5563                 this.selectionStatusToolbar( view );
5564
5565                 view.set( 'gallery', {
5566                         style:    'primary',
5567                         text:     l10n.createNewGallery,
5568                         priority: 60,
5569                         requires: { selection: true },
5570
5571                         click: function() {
5572                                 var selection = controller.state().get('selection'),
5573                                         edit = controller.state('gallery-edit'),
5574                                         models = selection.where({ type: 'image' });
5575
5576                                 edit.set( 'library', new wp.media.model.Selection( models, {
5577                                         props:    selection.props.toJSON(),
5578                                         multiple: true
5579                                 }) );
5580
5581                                 this.controller.setState('gallery-edit');
5582
5583                                 // Keep focus inside media modal
5584                                 // after jumping to gallery view
5585                                 this.controller.modal.focusManager.focus();
5586                         }
5587                 });
5588         },
5589
5590         mainPlaylistToolbar: function( view ) {
5591                 var controller = this;
5592
5593                 this.selectionStatusToolbar( view );
5594
5595                 view.set( 'playlist', {
5596                         style:    'primary',
5597                         text:     l10n.createNewPlaylist,
5598                         priority: 100,
5599                         requires: { selection: true },
5600
5601                         click: function() {
5602                                 var selection = controller.state().get('selection'),
5603                                         edit = controller.state('playlist-edit'),
5604                                         models = selection.where({ type: 'audio' });
5605
5606                                 edit.set( 'library', new wp.media.model.Selection( models, {
5607                                         props:    selection.props.toJSON(),
5608                                         multiple: true
5609                                 }) );
5610
5611                                 this.controller.setState('playlist-edit');
5612
5613                                 // Keep focus inside media modal
5614                                 // after jumping to playlist view
5615                                 this.controller.modal.focusManager.focus();
5616                         }
5617                 });
5618         },
5619
5620         mainVideoPlaylistToolbar: function( view ) {
5621                 var controller = this;
5622
5623                 this.selectionStatusToolbar( view );
5624
5625                 view.set( 'video-playlist', {
5626                         style:    'primary',
5627                         text:     l10n.createNewVideoPlaylist,
5628                         priority: 100,
5629                         requires: { selection: true },
5630
5631                         click: function() {
5632                                 var selection = controller.state().get('selection'),
5633                                         edit = controller.state('video-playlist-edit'),
5634                                         models = selection.where({ type: 'video' });
5635
5636                                 edit.set( 'library', new wp.media.model.Selection( models, {
5637                                         props:    selection.props.toJSON(),
5638                                         multiple: true
5639                                 }) );
5640
5641                                 this.controller.setState('video-playlist-edit');
5642
5643                                 // Keep focus inside media modal
5644                                 // after jumping to video playlist view
5645                                 this.controller.modal.focusManager.focus();
5646                         }
5647                 });
5648         },
5649
5650         featuredImageToolbar: function( toolbar ) {
5651                 this.createSelectToolbar( toolbar, {
5652                         text:  l10n.setFeaturedImage,
5653                         state: this.options.state
5654                 });
5655         },
5656
5657         mainEmbedToolbar: function( toolbar ) {
5658                 toolbar.view = new wp.media.view.Toolbar.Embed({
5659                         controller: this
5660                 });
5661         },
5662
5663         galleryEditToolbar: function() {
5664                 var editing = this.state().get('editing');
5665                 this.toolbar.set( new wp.media.view.Toolbar({
5666                         controller: this,
5667                         items: {
5668                                 insert: {
5669                                         style:    'primary',
5670                                         text:     editing ? l10n.updateGallery : l10n.insertGallery,
5671                                         priority: 80,
5672                                         requires: { library: true },
5673
5674                                         /**
5675                                          * @fires wp.media.controller.State#update
5676                                          */
5677                                         click: function() {
5678                                                 var controller = this.controller,
5679                                                         state = controller.state();
5680
5681                                                 controller.close();
5682                                                 state.trigger( 'update', state.get('library') );
5683
5684                                                 // Restore and reset the default state.
5685                                                 controller.setState( controller.options.state );
5686                                                 controller.reset();
5687                                         }
5688                                 }
5689                         }
5690                 }) );
5691         },
5692
5693         galleryAddToolbar: function() {
5694                 this.toolbar.set( new wp.media.view.Toolbar({
5695                         controller: this,
5696                         items: {
5697                                 insert: {
5698                                         style:    'primary',
5699                                         text:     l10n.addToGallery,
5700                                         priority: 80,
5701                                         requires: { selection: true },
5702
5703                                         /**
5704                                          * @fires wp.media.controller.State#reset
5705                                          */
5706                                         click: function() {
5707                                                 var controller = this.controller,
5708                                                         state = controller.state(),
5709                                                         edit = controller.state('gallery-edit');
5710
5711                                                 edit.get('library').add( state.get('selection').models );
5712                                                 state.trigger('reset');
5713                                                 controller.setState('gallery-edit');
5714                                         }
5715                                 }
5716                         }
5717                 }) );
5718         },
5719
5720         playlistEditToolbar: function() {
5721                 var editing = this.state().get('editing');
5722                 this.toolbar.set( new wp.media.view.Toolbar({
5723                         controller: this,
5724                         items: {
5725                                 insert: {
5726                                         style:    'primary',
5727                                         text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
5728                                         priority: 80,
5729                                         requires: { library: true },
5730
5731                                         /**
5732                                          * @fires wp.media.controller.State#update
5733                                          */
5734                                         click: function() {
5735                                                 var controller = this.controller,
5736                                                         state = controller.state();
5737
5738                                                 controller.close();
5739                                                 state.trigger( 'update', state.get('library') );
5740
5741                                                 // Restore and reset the default state.
5742                                                 controller.setState( controller.options.state );
5743                                                 controller.reset();
5744                                         }
5745                                 }
5746                         }
5747                 }) );
5748         },
5749
5750         playlistAddToolbar: function() {
5751                 this.toolbar.set( new wp.media.view.Toolbar({
5752                         controller: this,
5753                         items: {
5754                                 insert: {
5755                                         style:    'primary',
5756                                         text:     l10n.addToPlaylist,
5757                                         priority: 80,
5758                                         requires: { selection: true },
5759
5760                                         /**
5761                                          * @fires wp.media.controller.State#reset
5762                                          */
5763                                         click: function() {
5764                                                 var controller = this.controller,
5765                                                         state = controller.state(),
5766                                                         edit = controller.state('playlist-edit');
5767
5768                                                 edit.get('library').add( state.get('selection').models );
5769                                                 state.trigger('reset');
5770                                                 controller.setState('playlist-edit');
5771                                         }
5772                                 }
5773                         }
5774                 }) );
5775         },
5776
5777         videoPlaylistEditToolbar: function() {
5778                 var editing = this.state().get('editing');
5779                 this.toolbar.set( new wp.media.view.Toolbar({
5780                         controller: this,
5781                         items: {
5782                                 insert: {
5783                                         style:    'primary',
5784                                         text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
5785                                         priority: 140,
5786                                         requires: { library: true },
5787
5788                                         click: function() {
5789                                                 var controller = this.controller,
5790                                                         state = controller.state(),
5791                                                         library = state.get('library');
5792
5793                                                 library.type = 'video';
5794
5795                                                 controller.close();
5796                                                 state.trigger( 'update', library );
5797
5798                                                 // Restore and reset the default state.
5799                                                 controller.setState( controller.options.state );
5800                                                 controller.reset();
5801                                         }
5802                                 }
5803                         }
5804                 }) );
5805         },
5806
5807         videoPlaylistAddToolbar: function() {
5808                 this.toolbar.set( new wp.media.view.Toolbar({
5809                         controller: this,
5810                         items: {
5811                                 insert: {
5812                                         style:    'primary',
5813                                         text:     l10n.addToVideoPlaylist,
5814                                         priority: 140,
5815                                         requires: { selection: true },
5816
5817                                         click: function() {
5818                                                 var controller = this.controller,
5819                                                         state = controller.state(),
5820                                                         edit = controller.state('video-playlist-edit');
5821
5822                                                 edit.get('library').add( state.get('selection').models );
5823                                                 state.trigger('reset');
5824                                                 controller.setState('video-playlist-edit');
5825                                         }
5826                                 }
5827                         }
5828                 }) );
5829         }
5830 });
5831
5832 module.exports = Post;
5833
5834 },{}],46:[function(require,module,exports){
5835 /**
5836  * wp.media.view.MediaFrame.Select
5837  *
5838  * A frame for selecting an item or items from the media library.
5839  *
5840  * @class
5841  * @augments wp.media.view.MediaFrame
5842  * @augments wp.media.view.Frame
5843  * @augments wp.media.View
5844  * @augments wp.Backbone.View
5845  * @augments Backbone.View
5846  * @mixes wp.media.controller.StateMachine
5847  */
5848
5849 var MediaFrame = wp.media.view.MediaFrame,
5850         l10n = wp.media.view.l10n,
5851         Select;
5852
5853 Select = MediaFrame.extend({
5854         initialize: function() {
5855                 // Call 'initialize' directly on the parent class.
5856                 MediaFrame.prototype.initialize.apply( this, arguments );
5857
5858                 _.defaults( this.options, {
5859                         selection: [],
5860                         library:   {},
5861                         multiple:  false,
5862                         state:    'library'
5863                 });
5864
5865                 this.createSelection();
5866                 this.createStates();
5867                 this.bindHandlers();
5868         },
5869
5870         /**
5871          * Attach a selection collection to the frame.
5872          *
5873          * A selection is a collection of attachments used for a specific purpose
5874          * by a media frame. e.g. Selecting an attachment (or many) to insert into
5875          * post content.
5876          *
5877          * @see media.model.Selection
5878          */
5879         createSelection: function() {
5880                 var selection = this.options.selection;
5881
5882                 if ( ! (selection instanceof wp.media.model.Selection) ) {
5883                         this.options.selection = new wp.media.model.Selection( selection, {
5884                                 multiple: this.options.multiple
5885                         });
5886                 }
5887
5888                 this._selection = {
5889                         attachments: new wp.media.model.Attachments(),
5890                         difference: []
5891                 };
5892         },
5893
5894         /**
5895          * Create the default states on the frame.
5896          */
5897         createStates: function() {
5898                 var options = this.options;
5899
5900                 if ( this.options.states ) {
5901                         return;
5902                 }
5903
5904                 // Add the default states.
5905                 this.states.add([
5906                         // Main states.
5907                         new wp.media.controller.Library({
5908                                 library:   wp.media.query( options.library ),
5909                                 multiple:  options.multiple,
5910                                 title:     options.title,
5911                                 priority:  20
5912                         })
5913                 ]);
5914         },
5915
5916         /**
5917          * Bind region mode event callbacks.
5918          *
5919          * @see media.controller.Region.render
5920          */
5921         bindHandlers: function() {
5922                 this.on( 'router:create:browse', this.createRouter, this );
5923                 this.on( 'router:render:browse', this.browseRouter, this );
5924                 this.on( 'content:create:browse', this.browseContent, this );
5925                 this.on( 'content:render:upload', this.uploadContent, this );
5926                 this.on( 'toolbar:create:select', this.createSelectToolbar, this );
5927         },
5928
5929         /**
5930          * Render callback for the router region in the `browse` mode.
5931          *
5932          * @param {wp.media.view.Router} routerView
5933          */
5934         browseRouter: function( routerView ) {
5935                 routerView.set({
5936                         upload: {
5937                                 text:     l10n.uploadFilesTitle,
5938                                 priority: 20
5939                         },
5940                         browse: {
5941                                 text:     l10n.mediaLibraryTitle,
5942                                 priority: 40
5943                         }
5944                 });
5945         },
5946
5947         /**
5948          * Render callback for the content region in the `browse` mode.
5949          *
5950          * @param {wp.media.controller.Region} contentRegion
5951          */
5952         browseContent: function( contentRegion ) {
5953                 var state = this.state();
5954
5955                 this.$el.removeClass('hide-toolbar');
5956
5957                 // Browse our library of attachments.
5958                 contentRegion.view = new wp.media.view.AttachmentsBrowser({
5959                         controller: this,
5960                         collection: state.get('library'),
5961                         selection:  state.get('selection'),
5962                         model:      state,
5963                         sortable:   state.get('sortable'),
5964                         search:     state.get('searchable'),
5965                         filters:    state.get('filterable'),
5966                         date:       state.get('date'),
5967                         display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
5968                         dragInfo:   state.get('dragInfo'),
5969
5970                         idealColumnWidth: state.get('idealColumnWidth'),
5971                         suggestedWidth:   state.get('suggestedWidth'),
5972                         suggestedHeight:  state.get('suggestedHeight'),
5973
5974                         AttachmentView: state.get('AttachmentView')
5975                 });
5976         },
5977
5978         /**
5979          * Render callback for the content region in the `upload` mode.
5980          */
5981         uploadContent: function() {
5982                 this.$el.removeClass( 'hide-toolbar' );
5983                 this.content.set( new wp.media.view.UploaderInline({
5984                         controller: this
5985                 }) );
5986         },
5987
5988         /**
5989          * Toolbars
5990          *
5991          * @param {Object} toolbar
5992          * @param {Object} [options={}]
5993          * @this wp.media.controller.Region
5994          */
5995         createSelectToolbar: function( toolbar, options ) {
5996                 options = options || this.options.button || {};
5997                 options.controller = this;
5998
5999                 toolbar.view = new wp.media.view.Toolbar.Select( options );
6000         }
6001 });
6002
6003 module.exports = Select;
6004
6005 },{}],47:[function(require,module,exports){
6006 /**
6007  * wp.media.view.Iframe
6008  *
6009  * @class
6010  * @augments wp.media.View
6011  * @augments wp.Backbone.View
6012  * @augments Backbone.View
6013  */
6014 var Iframe = wp.media.View.extend({
6015         className: 'media-iframe',
6016         /**
6017          * @returns {wp.media.view.Iframe} Returns itself to allow chaining
6018          */
6019         render: function() {
6020                 this.views.detach();
6021                 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
6022                 this.views.render();
6023                 return this;
6024         }
6025 });
6026
6027 module.exports = Iframe;
6028
6029 },{}],48:[function(require,module,exports){
6030 /**
6031  * wp.media.view.ImageDetails
6032  *
6033  * @class
6034  * @augments wp.media.view.Settings.AttachmentDisplay
6035  * @augments wp.media.view.Settings
6036  * @augments wp.media.View
6037  * @augments wp.Backbone.View
6038  * @augments Backbone.View
6039  */
6040 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
6041         $ = jQuery,
6042         ImageDetails;
6043
6044 ImageDetails = AttachmentDisplay.extend({
6045         className: 'image-details',
6046         template:  wp.template('image-details'),
6047         events: _.defaults( AttachmentDisplay.prototype.events, {
6048                 'click .edit-attachment': 'editAttachment',
6049                 'click .replace-attachment': 'replaceAttachment',
6050                 'click .advanced-toggle': 'onToggleAdvanced',
6051                 'change [data-setting="customWidth"]': 'onCustomSize',
6052                 'change [data-setting="customHeight"]': 'onCustomSize',
6053                 'keyup [data-setting="customWidth"]': 'onCustomSize',
6054                 'keyup [data-setting="customHeight"]': 'onCustomSize'
6055         } ),
6056         initialize: function() {
6057                 // used in AttachmentDisplay.prototype.updateLinkTo
6058                 this.options.attachment = this.model.attachment;
6059                 this.listenTo( this.model, 'change:url', this.updateUrl );
6060                 this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
6061                 this.listenTo( this.model, 'change:size', this.toggleCustomSize );
6062
6063                 AttachmentDisplay.prototype.initialize.apply( this, arguments );
6064         },
6065
6066         prepare: function() {
6067                 var attachment = false;
6068
6069                 if ( this.model.attachment ) {
6070                         attachment = this.model.attachment.toJSON();
6071                 }
6072                 return _.defaults({
6073                         model: this.model.toJSON(),
6074                         attachment: attachment
6075                 }, this.options );
6076         },
6077
6078         render: function() {
6079                 var args = arguments;
6080
6081                 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
6082                         this.model.dfd
6083                                 .done( _.bind( function() {
6084                                         AttachmentDisplay.prototype.render.apply( this, args );
6085                                         this.postRender();
6086                                 }, this ) )
6087                                 .fail( _.bind( function() {
6088                                         this.model.attachment = false;
6089                                         AttachmentDisplay.prototype.render.apply( this, args );
6090                                         this.postRender();
6091                                 }, this ) );
6092                 } else {
6093                         AttachmentDisplay.prototype.render.apply( this, arguments );
6094                         this.postRender();
6095                 }
6096
6097                 return this;
6098         },
6099
6100         postRender: function() {
6101                 setTimeout( _.bind( this.resetFocus, this ), 10 );
6102                 this.toggleLinkSettings();
6103                 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
6104                         this.toggleAdvanced( true );
6105                 }
6106                 this.trigger( 'post-render' );
6107         },
6108
6109         resetFocus: function() {
6110                 this.$( '.link-to-custom' ).blur();
6111                 this.$( '.embed-media-settings' ).scrollTop( 0 );
6112         },
6113
6114         updateUrl: function() {
6115                 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
6116                 this.$( '.url' ).val( this.model.get( 'url' ) );
6117         },
6118
6119         toggleLinkSettings: function() {
6120                 if ( this.model.get( 'link' ) === 'none' ) {
6121                         this.$( '.link-settings' ).addClass('hidden');
6122                 } else {
6123                         this.$( '.link-settings' ).removeClass('hidden');
6124                 }
6125         },
6126
6127         toggleCustomSize: function() {
6128                 if ( this.model.get( 'size' ) !== 'custom' ) {
6129                         this.$( '.custom-size' ).addClass('hidden');
6130                 } else {
6131                         this.$( '.custom-size' ).removeClass('hidden');
6132                 }
6133         },
6134
6135         onCustomSize: function( event ) {
6136                 var dimension = $( event.target ).data('setting'),
6137                         num = $( event.target ).val(),
6138                         value;
6139
6140                 // Ignore bogus input
6141                 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
6142                         event.preventDefault();
6143                         return;
6144                 }
6145
6146                 if ( dimension === 'customWidth' ) {
6147                         value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
6148                         this.model.set( 'customHeight', value, { silent: true } );
6149                         this.$( '[data-setting="customHeight"]' ).val( value );
6150                 } else {
6151                         value = Math.round( this.model.get( 'aspectRatio' ) * num );
6152                         this.model.set( 'customWidth', value, { silent: true  } );
6153                         this.$( '[data-setting="customWidth"]' ).val( value );
6154                 }
6155         },
6156
6157         onToggleAdvanced: function( event ) {
6158                 event.preventDefault();
6159                 this.toggleAdvanced();
6160         },
6161
6162         toggleAdvanced: function( show ) {
6163                 var $advanced = this.$el.find( '.advanced-section' ),
6164                         mode;
6165
6166                 if ( $advanced.hasClass('advanced-visible') || show === false ) {
6167                         $advanced.removeClass('advanced-visible');
6168                         $advanced.find('.advanced-settings').addClass('hidden');
6169                         mode = 'hide';
6170                 } else {
6171                         $advanced.addClass('advanced-visible');
6172                         $advanced.find('.advanced-settings').removeClass('hidden');
6173                         mode = 'show';
6174                 }
6175
6176                 window.setUserSetting( 'advImgDetails', mode );
6177         },
6178
6179         editAttachment: function( event ) {
6180                 var editState = this.controller.states.get( 'edit-image' );
6181
6182                 if ( window.imageEdit && editState ) {
6183                         event.preventDefault();
6184                         editState.set( 'image', this.model.attachment );
6185                         this.controller.setState( 'edit-image' );
6186                 }
6187         },
6188
6189         replaceAttachment: function( event ) {
6190                 event.preventDefault();
6191                 this.controller.setState( 'replace-image' );
6192         }
6193 });
6194
6195 module.exports = ImageDetails;
6196
6197 },{}],49:[function(require,module,exports){
6198 /**
6199  * wp.media.view.Label
6200  *
6201  * @class
6202  * @augments wp.media.View
6203  * @augments wp.Backbone.View
6204  * @augments Backbone.View
6205  */
6206 var Label = wp.media.View.extend({
6207         tagName: 'label',
6208         className: 'screen-reader-text',
6209
6210         initialize: function() {
6211                 this.value = this.options.value;
6212         },
6213
6214         render: function() {
6215                 this.$el.html( this.value );
6216
6217                 return this;
6218         }
6219 });
6220
6221 module.exports = Label;
6222
6223 },{}],50:[function(require,module,exports){
6224 /**
6225  * wp.media.view.MediaFrame
6226  *
6227  * The frame used to create the media modal.
6228  *
6229  * @class
6230  * @augments wp.media.view.Frame
6231  * @augments wp.media.View
6232  * @augments wp.Backbone.View
6233  * @augments Backbone.View
6234  * @mixes wp.media.controller.StateMachine
6235  */
6236 var Frame = wp.media.view.Frame,
6237         $ = jQuery,
6238         MediaFrame;
6239
6240 MediaFrame = Frame.extend({
6241         className: 'media-frame',
6242         template:  wp.template('media-frame'),
6243         regions:   ['menu','title','content','toolbar','router'],
6244
6245         events: {
6246                 'click div.media-frame-title h1': 'toggleMenu'
6247         },
6248
6249         /**
6250          * @global wp.Uploader
6251          */
6252         initialize: function() {
6253                 Frame.prototype.initialize.apply( this, arguments );
6254
6255                 _.defaults( this.options, {
6256                         title:    '',
6257                         modal:    true,
6258                         uploader: true
6259                 });
6260
6261                 // Ensure core UI is enabled.
6262                 this.$el.addClass('wp-core-ui');
6263
6264                 // Initialize modal container view.
6265                 if ( this.options.modal ) {
6266                         this.modal = new wp.media.view.Modal({
6267                                 controller: this,
6268                                 title:      this.options.title
6269                         });
6270
6271                         this.modal.content( this );
6272                 }
6273
6274                 // Force the uploader off if the upload limit has been exceeded or
6275                 // if the browser isn't supported.
6276                 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
6277                         this.options.uploader = false;
6278                 }
6279
6280                 // Initialize window-wide uploader.
6281                 if ( this.options.uploader ) {
6282                         this.uploader = new wp.media.view.UploaderWindow({
6283                                 controller: this,
6284                                 uploader: {
6285                                         dropzone:  this.modal ? this.modal.$el : this.$el,
6286                                         container: this.$el
6287                                 }
6288                         });
6289                         this.views.set( '.media-frame-uploader', this.uploader );
6290                 }
6291
6292                 this.on( 'attach', _.bind( this.views.ready, this.views ), this );
6293
6294                 // Bind default title creation.
6295                 this.on( 'title:create:default', this.createTitle, this );
6296                 this.title.mode('default');
6297
6298                 this.on( 'title:render', function( view ) {
6299                         view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
6300                 });
6301
6302                 // Bind default menu.
6303                 this.on( 'menu:create:default', this.createMenu, this );
6304         },
6305         /**
6306          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
6307          */
6308         render: function() {
6309                 // Activate the default state if no active state exists.
6310                 if ( ! this.state() && this.options.state ) {
6311                         this.setState( this.options.state );
6312                 }
6313                 /**
6314                  * call 'render' directly on the parent class
6315                  */
6316                 return Frame.prototype.render.apply( this, arguments );
6317         },
6318         /**
6319          * @param {Object} title
6320          * @this wp.media.controller.Region
6321          */
6322         createTitle: function( title ) {
6323                 title.view = new wp.media.View({
6324                         controller: this,
6325                         tagName: 'h1'
6326                 });
6327         },
6328         /**
6329          * @param {Object} menu
6330          * @this wp.media.controller.Region
6331          */
6332         createMenu: function( menu ) {
6333                 menu.view = new wp.media.view.Menu({
6334                         controller: this
6335                 });
6336         },
6337
6338         toggleMenu: function() {
6339                 this.$el.find( '.media-menu' ).toggleClass( 'visible' );
6340         },
6341
6342         /**
6343          * @param {Object} toolbar
6344          * @this wp.media.controller.Region
6345          */
6346         createToolbar: function( toolbar ) {
6347                 toolbar.view = new wp.media.view.Toolbar({
6348                         controller: this
6349                 });
6350         },
6351         /**
6352          * @param {Object} router
6353          * @this wp.media.controller.Region
6354          */
6355         createRouter: function( router ) {
6356                 router.view = new wp.media.view.Router({
6357                         controller: this
6358                 });
6359         },
6360         /**
6361          * @param {Object} options
6362          */
6363         createIframeStates: function( options ) {
6364                 var settings = wp.media.view.settings,
6365                         tabs = settings.tabs,
6366                         tabUrl = settings.tabUrl,
6367                         $postId;
6368
6369                 if ( ! tabs || ! tabUrl ) {
6370                         return;
6371                 }
6372
6373                 // Add the post ID to the tab URL if it exists.
6374                 $postId = $('#post_ID');
6375                 if ( $postId.length ) {
6376                         tabUrl += '&post_id=' + $postId.val();
6377                 }
6378
6379                 // Generate the tab states.
6380                 _.each( tabs, function( title, id ) {
6381                         this.state( 'iframe:' + id ).set( _.defaults({
6382                                 tab:     id,
6383                                 src:     tabUrl + '&tab=' + id,
6384                                 title:   title,
6385                                 content: 'iframe',
6386                                 menu:    'default'
6387                         }, options ) );
6388                 }, this );
6389
6390                 this.on( 'content:create:iframe', this.iframeContent, this );
6391                 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
6392                 this.on( 'menu:render:default', this.iframeMenu, this );
6393                 this.on( 'open', this.hijackThickbox, this );
6394                 this.on( 'close', this.restoreThickbox, this );
6395         },
6396
6397         /**
6398          * @param {Object} content
6399          * @this wp.media.controller.Region
6400          */
6401         iframeContent: function( content ) {
6402                 this.$el.addClass('hide-toolbar');
6403                 content.view = new wp.media.view.Iframe({
6404                         controller: this
6405                 });
6406         },
6407
6408         iframeContentCleanup: function() {
6409                 this.$el.removeClass('hide-toolbar');
6410         },
6411
6412         iframeMenu: function( view ) {
6413                 var views = {};
6414
6415                 if ( ! view ) {
6416                         return;
6417                 }
6418
6419                 _.each( wp.media.view.settings.tabs, function( title, id ) {
6420                         views[ 'iframe:' + id ] = {
6421                                 text: this.state( 'iframe:' + id ).get('title'),
6422                                 priority: 200
6423                         };
6424                 }, this );
6425
6426                 view.set( views );
6427         },
6428
6429         hijackThickbox: function() {
6430                 var frame = this;
6431
6432                 if ( ! window.tb_remove || this._tb_remove ) {
6433                         return;
6434                 }
6435
6436                 this._tb_remove = window.tb_remove;
6437                 window.tb_remove = function() {
6438                         frame.close();
6439                         frame.reset();
6440                         frame.setState( frame.options.state );
6441                         frame._tb_remove.call( window );
6442                 };
6443         },
6444
6445         restoreThickbox: function() {
6446                 if ( ! this._tb_remove ) {
6447                         return;
6448                 }
6449
6450                 window.tb_remove = this._tb_remove;
6451                 delete this._tb_remove;
6452         }
6453 });
6454
6455 // Map some of the modal's methods to the frame.
6456 _.each(['open','close','attach','detach','escape'], function( method ) {
6457         /**
6458          * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
6459          */
6460         MediaFrame.prototype[ method ] = function() {
6461                 if ( this.modal ) {
6462                         this.modal[ method ].apply( this.modal, arguments );
6463                 }
6464                 return this;
6465         };
6466 });
6467
6468 module.exports = MediaFrame;
6469
6470 },{}],51:[function(require,module,exports){
6471 /**
6472  * wp.media.view.MenuItem
6473  *
6474  * @class
6475  * @augments wp.media.View
6476  * @augments wp.Backbone.View
6477  * @augments Backbone.View
6478  */
6479 var $ = jQuery,
6480         MenuItem;
6481
6482 MenuItem = wp.media.View.extend({
6483         tagName:   'a',
6484         className: 'media-menu-item',
6485
6486         attributes: {
6487                 href: '#'
6488         },
6489
6490         events: {
6491                 'click': '_click'
6492         },
6493         /**
6494          * @param {Object} event
6495          */
6496         _click: function( event ) {
6497                 var clickOverride = this.options.click;
6498
6499                 if ( event ) {
6500                         event.preventDefault();
6501                 }
6502
6503                 if ( clickOverride ) {
6504                         clickOverride.call( this );
6505                 } else {
6506                         this.click();
6507                 }
6508
6509                 // When selecting a tab along the left side,
6510                 // focus should be transferred into the main panel
6511                 if ( ! wp.media.isTouchDevice ) {
6512                         $('.media-frame-content input').first().focus();
6513                 }
6514         },
6515
6516         click: function() {
6517                 var state = this.options.state;
6518
6519                 if ( state ) {
6520                         this.controller.setState( state );
6521                         this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
6522                 }
6523         },
6524         /**
6525          * @returns {wp.media.view.MenuItem} returns itself to allow chaining
6526          */
6527         render: function() {
6528                 var options = this.options;
6529
6530                 if ( options.text ) {
6531                         this.$el.text( options.text );
6532                 } else if ( options.html ) {
6533                         this.$el.html( options.html );
6534                 }
6535
6536                 return this;
6537         }
6538 });
6539
6540 module.exports = MenuItem;
6541
6542 },{}],52:[function(require,module,exports){
6543 /**
6544  * wp.media.view.Menu
6545  *
6546  * @class
6547  * @augments wp.media.view.PriorityList
6548  * @augments wp.media.View
6549  * @augments wp.Backbone.View
6550  * @augments Backbone.View
6551  */
6552 var MenuItem = wp.media.view.MenuItem,
6553         PriorityList = wp.media.view.PriorityList,
6554         Menu;
6555
6556 Menu = PriorityList.extend({
6557         tagName:   'div',
6558         className: 'media-menu',
6559         property:  'state',
6560         ItemView:  MenuItem,
6561         region:    'menu',
6562
6563         /* TODO: alternatively hide on any click anywhere
6564         events: {
6565                 'click': 'click'
6566         },
6567
6568         click: function() {
6569                 this.$el.removeClass( 'visible' );
6570         },
6571         */
6572
6573         /**
6574          * @param {Object} options
6575          * @param {string} id
6576          * @returns {wp.media.View}
6577          */
6578         toView: function( options, id ) {
6579                 options = options || {};
6580                 options[ this.property ] = options[ this.property ] || id;
6581                 return new this.ItemView( options ).render();
6582         },
6583
6584         ready: function() {
6585                 /**
6586                  * call 'ready' directly on the parent class
6587                  */
6588                 PriorityList.prototype.ready.apply( this, arguments );
6589                 this.visibility();
6590         },
6591
6592         set: function() {
6593                 /**
6594                  * call 'set' directly on the parent class
6595                  */
6596                 PriorityList.prototype.set.apply( this, arguments );
6597                 this.visibility();
6598         },
6599
6600         unset: function() {
6601                 /**
6602                  * call 'unset' directly on the parent class
6603                  */
6604                 PriorityList.prototype.unset.apply( this, arguments );
6605                 this.visibility();
6606         },
6607
6608         visibility: function() {
6609                 var region = this.region,
6610                         view = this.controller[ region ].get(),
6611                         views = this.views.get(),
6612                         hide = ! views || views.length < 2;
6613
6614                 if ( this === view ) {
6615                         this.controller.$el.toggleClass( 'hide-' + region, hide );
6616                 }
6617         },
6618         /**
6619          * @param {string} id
6620          */
6621         select: function( id ) {
6622                 var view = this.get( id );
6623
6624                 if ( ! view ) {
6625                         return;
6626                 }
6627
6628                 this.deselect();
6629                 view.$el.addClass('active');
6630         },
6631
6632         deselect: function() {
6633                 this.$el.children().removeClass('active');
6634         },
6635
6636         hide: function( id ) {
6637                 var view = this.get( id );
6638
6639                 if ( ! view ) {
6640                         return;
6641                 }
6642
6643                 view.$el.addClass('hidden');
6644         },
6645
6646         show: function( id ) {
6647                 var view = this.get( id );
6648
6649                 if ( ! view ) {
6650                         return;
6651                 }
6652
6653                 view.$el.removeClass('hidden');
6654         }
6655 });
6656
6657 module.exports = Menu;
6658
6659 },{}],53:[function(require,module,exports){
6660 /**
6661  * wp.media.view.Modal
6662  *
6663  * A modal view, which the media modal uses as its default container.
6664  *
6665  * @class
6666  * @augments wp.media.View
6667  * @augments wp.Backbone.View
6668  * @augments Backbone.View
6669  */
6670 var $ = jQuery,
6671         Modal;
6672
6673 Modal = wp.media.View.extend({
6674         tagName:  'div',
6675         template: wp.template('media-modal'),
6676
6677         attributes: {
6678                 tabindex: 0
6679         },
6680
6681         events: {
6682                 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
6683                 'keydown': 'keydown'
6684         },
6685
6686         initialize: function() {
6687                 _.defaults( this.options, {
6688                         container: document.body,
6689                         title:     '',
6690                         propagate: true,
6691                         freeze:    true
6692                 });
6693
6694                 this.focusManager = new wp.media.view.FocusManager({
6695                         el: this.el
6696                 });
6697         },
6698         /**
6699          * @returns {Object}
6700          */
6701         prepare: function() {
6702                 return {
6703                         title: this.options.title
6704                 };
6705         },
6706
6707         /**
6708          * @returns {wp.media.view.Modal} Returns itself to allow chaining
6709          */
6710         attach: function() {
6711                 if ( this.views.attached ) {
6712                         return this;
6713                 }
6714
6715                 if ( ! this.views.rendered ) {
6716                         this.render();
6717                 }
6718
6719                 this.$el.appendTo( this.options.container );
6720
6721                 // Manually mark the view as attached and trigger ready.
6722                 this.views.attached = true;
6723                 this.views.ready();
6724
6725                 return this.propagate('attach');
6726         },
6727
6728         /**
6729          * @returns {wp.media.view.Modal} Returns itself to allow chaining
6730          */
6731         detach: function() {
6732                 if ( this.$el.is(':visible') ) {
6733                         this.close();
6734                 }
6735
6736                 this.$el.detach();
6737                 this.views.attached = false;
6738                 return this.propagate('detach');
6739         },
6740
6741         /**
6742          * @returns {wp.media.view.Modal} Returns itself to allow chaining
6743          */
6744         open: function() {
6745                 var $el = this.$el,
6746                         options = this.options,
6747                         mceEditor;
6748
6749                 if ( $el.is(':visible') ) {
6750                         return this;
6751                 }
6752
6753                 if ( ! this.views.attached ) {
6754                         this.attach();
6755                 }
6756
6757                 // If the `freeze` option is set, record the window's scroll position.
6758                 if ( options.freeze ) {
6759                         this._freeze = {
6760                                 scrollTop: $( window ).scrollTop()
6761                         };
6762                 }
6763
6764                 // Disable page scrolling.
6765                 $( 'body' ).addClass( 'modal-open' );
6766
6767                 $el.show();
6768
6769                 // Try to close the onscreen keyboard
6770                 if ( 'ontouchend' in document ) {
6771                         if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor )  && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
6772                                 mceEditor.iframeElement.focus();
6773                                 mceEditor.iframeElement.blur();
6774
6775                                 setTimeout( function() {
6776                                         mceEditor.iframeElement.blur();
6777                                 }, 100 );
6778                         }
6779                 }
6780
6781                 this.$el.focus();
6782
6783                 return this.propagate('open');
6784         },
6785
6786         /**
6787          * @param {Object} options
6788          * @returns {wp.media.view.Modal} Returns itself to allow chaining
6789          */
6790         close: function( options ) {
6791                 var freeze = this._freeze;
6792
6793                 if ( ! this.views.attached || ! this.$el.is(':visible') ) {
6794                         return this;
6795                 }
6796
6797                 // Enable page scrolling.
6798                 $( 'body' ).removeClass( 'modal-open' );
6799
6800                 // Hide modal and remove restricted media modal tab focus once it's closed
6801                 this.$el.hide().undelegate( 'keydown' );
6802
6803                 // Put focus back in useful location once modal is closed
6804                 $('#wpbody-content').focus();
6805
6806                 this.propagate('close');
6807
6808                 // If the `freeze` option is set, restore the container's scroll position.
6809                 if ( freeze ) {
6810                         $( window ).scrollTop( freeze.scrollTop );
6811                 }
6812
6813                 if ( options && options.escape ) {
6814                         this.propagate('escape');
6815                 }
6816
6817                 return this;
6818         },
6819         /**
6820          * @returns {wp.media.view.Modal} Returns itself to allow chaining
6821          */
6822         escape: function() {
6823                 return this.close({ escape: true });
6824         },
6825         /**
6826          * @param {Object} event
6827          */
6828         escapeHandler: function( event ) {
6829                 event.preventDefault();
6830                 this.escape();
6831         },
6832
6833         /**
6834          * @param {Array|Object} content Views to register to '.media-modal-content'
6835          * @returns {wp.media.view.Modal} Returns itself to allow chaining
6836          */
6837         content: function( content ) {
6838                 this.views.set( '.media-modal-content', content );
6839                 return this;
6840         },
6841
6842         /**
6843          * Triggers a modal event and if the `propagate` option is set,
6844          * forwards events to the modal's controller.
6845          *
6846          * @param {string} id
6847          * @returns {wp.media.view.Modal} Returns itself to allow chaining
6848          */
6849         propagate: function( id ) {
6850                 this.trigger( id );
6851
6852                 if ( this.options.propagate ) {
6853                         this.controller.trigger( id );
6854                 }
6855
6856                 return this;
6857         },
6858         /**
6859          * @param {Object} event
6860          */
6861         keydown: function( event ) {
6862                 // Close the modal when escape is pressed.
6863                 if ( 27 === event.which && this.$el.is(':visible') ) {
6864                         this.escape();
6865                         event.stopImmediatePropagation();
6866                 }
6867         }
6868 });
6869
6870 module.exports = Modal;
6871
6872 },{}],54:[function(require,module,exports){
6873 /**
6874  * wp.media.view.PriorityList
6875  *
6876  * @class
6877  * @augments wp.media.View
6878  * @augments wp.Backbone.View
6879  * @augments Backbone.View
6880  */
6881 var PriorityList = wp.media.View.extend({
6882         tagName:   'div',
6883
6884         initialize: function() {
6885                 this._views = {};
6886
6887                 this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
6888                 delete this.options.views;
6889
6890                 if ( ! this.options.silent ) {
6891                         this.render();
6892                 }
6893         },
6894         /**
6895          * @param {string} id
6896          * @param {wp.media.View|Object} view
6897          * @param {Object} options
6898          * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
6899          */
6900         set: function( id, view, options ) {
6901                 var priority, views, index;
6902
6903                 options = options || {};
6904
6905                 // Accept an object with an `id` : `view` mapping.
6906                 if ( _.isObject( id ) ) {
6907                         _.each( id, function( view, id ) {
6908                                 this.set( id, view );
6909                         }, this );
6910                         return this;
6911                 }
6912
6913                 if ( ! (view instanceof Backbone.View) ) {
6914                         view = this.toView( view, id, options );
6915                 }
6916                 view.controller = view.controller || this.controller;
6917
6918                 this.unset( id );
6919
6920                 priority = view.options.priority || 10;
6921                 views = this.views.get() || [];
6922
6923                 _.find( views, function( existing, i ) {
6924                         if ( existing.options.priority > priority ) {
6925                                 index = i;
6926                                 return true;
6927                         }
6928                 });
6929
6930                 this._views[ id ] = view;
6931                 this.views.add( view, {
6932                         at: _.isNumber( index ) ? index : views.length || 0
6933                 });
6934
6935                 return this;
6936         },
6937         /**
6938          * @param {string} id
6939          * @returns {wp.media.View}
6940          */
6941         get: function( id ) {
6942                 return this._views[ id ];
6943         },
6944         /**
6945          * @param {string} id
6946          * @returns {wp.media.view.PriorityList}
6947          */
6948         unset: function( id ) {
6949                 var view = this.get( id );
6950
6951                 if ( view ) {
6952                         view.remove();
6953                 }
6954
6955                 delete this._views[ id ];
6956                 return this;
6957         },
6958         /**
6959          * @param {Object} options
6960          * @returns {wp.media.View}
6961          */
6962         toView: function( options ) {
6963                 return new wp.media.View( options );
6964         }
6965 });
6966
6967 module.exports = PriorityList;
6968
6969 },{}],55:[function(require,module,exports){
6970 /**
6971  * wp.media.view.RouterItem
6972  *
6973  * @class
6974  * @augments wp.media.view.MenuItem
6975  * @augments wp.media.View
6976  * @augments wp.Backbone.View
6977  * @augments Backbone.View
6978  */
6979 var RouterItem = wp.media.view.MenuItem.extend({
6980         /**
6981          * On click handler to activate the content region's corresponding mode.
6982          */
6983         click: function() {
6984                 var contentMode = this.options.contentMode;
6985                 if ( contentMode ) {
6986                         this.controller.content.mode( contentMode );
6987                 }
6988         }
6989 });
6990
6991 module.exports = RouterItem;
6992
6993 },{}],56:[function(require,module,exports){
6994 /**
6995  * wp.media.view.Router
6996  *
6997  * @class
6998  * @augments wp.media.view.Menu
6999  * @augments wp.media.view.PriorityList
7000  * @augments wp.media.View
7001  * @augments wp.Backbone.View
7002  * @augments Backbone.View
7003  */
7004 var Menu = wp.media.view.Menu,
7005         Router;
7006
7007 Router = Menu.extend({
7008         tagName:   'div',
7009         className: 'media-router',
7010         property:  'contentMode',
7011         ItemView:  wp.media.view.RouterItem,
7012         region:    'router',
7013
7014         initialize: function() {
7015                 this.controller.on( 'content:render', this.update, this );
7016                 // Call 'initialize' directly on the parent class.
7017                 Menu.prototype.initialize.apply( this, arguments );
7018         },
7019
7020         update: function() {
7021                 var mode = this.controller.content.mode();
7022                 if ( mode ) {
7023                         this.select( mode );
7024                 }
7025         }
7026 });
7027
7028 module.exports = Router;
7029
7030 },{}],57:[function(require,module,exports){
7031 /**
7032  * wp.media.view.Search
7033  *
7034  * @class
7035  * @augments wp.media.View
7036  * @augments wp.Backbone.View
7037  * @augments Backbone.View
7038  */
7039 var l10n = wp.media.view.l10n,
7040         Search;
7041
7042 Search = wp.media.View.extend({
7043         tagName:   'input',
7044         className: 'search',
7045         id:        'media-search-input',
7046
7047         attributes: {
7048                 type:        'search',
7049                 placeholder: l10n.search
7050         },
7051
7052         events: {
7053                 'input':  'search',
7054                 'keyup':  'search',
7055                 'change': 'search',
7056                 'search': 'search'
7057         },
7058
7059         /**
7060          * @returns {wp.media.view.Search} Returns itself to allow chaining
7061          */
7062         render: function() {
7063                 this.el.value = this.model.escape('search');
7064                 return this;
7065         },
7066
7067         search: function( event ) {
7068                 if ( event.target.value ) {
7069                         this.model.set( 'search', event.target.value );
7070                 } else {
7071                         this.model.unset('search');
7072                 }
7073         }
7074 });
7075
7076 module.exports = Search;
7077
7078 },{}],58:[function(require,module,exports){
7079 /**
7080  * wp.media.view.Selection
7081  *
7082  * @class
7083  * @augments wp.media.View
7084  * @augments wp.Backbone.View
7085  * @augments Backbone.View
7086  */
7087 var l10n = wp.media.view.l10n,
7088         Selection;
7089
7090 Selection = wp.media.View.extend({
7091         tagName:   'div',
7092         className: 'media-selection',
7093         template:  wp.template('media-selection'),
7094
7095         events: {
7096                 'click .edit-selection':  'edit',
7097                 'click .clear-selection': 'clear'
7098         },
7099
7100         initialize: function() {
7101                 _.defaults( this.options, {
7102                         editable:  false,
7103                         clearable: true
7104                 });
7105
7106                 /**
7107                  * @member {wp.media.view.Attachments.Selection}
7108                  */
7109                 this.attachments = new wp.media.view.Attachments.Selection({
7110                         controller: this.controller,
7111                         collection: this.collection,
7112                         selection:  this.collection,
7113                         model:      new Backbone.Model()
7114                 });
7115
7116                 this.views.set( '.selection-view', this.attachments );
7117                 this.collection.on( 'add remove reset', this.refresh, this );
7118                 this.controller.on( 'content:activate', this.refresh, this );
7119         },
7120
7121         ready: function() {
7122                 this.refresh();
7123         },
7124
7125         refresh: function() {
7126                 // If the selection hasn't been rendered, bail.
7127                 if ( ! this.$el.children().length ) {
7128                         return;
7129                 }
7130
7131                 var collection = this.collection,
7132                         editing = 'edit-selection' === this.controller.content.mode();
7133
7134                 // If nothing is selected, display nothing.
7135                 this.$el.toggleClass( 'empty', ! collection.length );
7136                 this.$el.toggleClass( 'one', 1 === collection.length );
7137                 this.$el.toggleClass( 'editing', editing );
7138
7139                 this.$('.count').text( l10n.selected.replace('%d', collection.length) );
7140         },
7141
7142         edit: function( event ) {
7143                 event.preventDefault();
7144                 if ( this.options.editable ) {
7145                         this.options.editable.call( this, this.collection );
7146                 }
7147         },
7148
7149         clear: function( event ) {
7150                 event.preventDefault();
7151                 this.collection.reset();
7152
7153                 // Keep focus inside media modal
7154                 // after clear link is selected
7155                 this.controller.modal.focusManager.focus();
7156         }
7157 });
7158
7159 module.exports = Selection;
7160
7161 },{}],59:[function(require,module,exports){
7162 /**
7163  * wp.media.view.Settings
7164  *
7165  * @class
7166  * @augments wp.media.View
7167  * @augments wp.Backbone.View
7168  * @augments Backbone.View
7169  */
7170 var View = wp.media.View,
7171         $ = Backbone.$,
7172         Settings;
7173
7174 Settings = View.extend({
7175         events: {
7176                 'click button':    'updateHandler',
7177                 'change input':    'updateHandler',
7178                 'change select':   'updateHandler',
7179                 'change textarea': 'updateHandler'
7180         },
7181
7182         initialize: function() {
7183                 this.model = this.model || new Backbone.Model();
7184                 this.listenTo( this.model, 'change', this.updateChanges );
7185         },
7186
7187         prepare: function() {
7188                 return _.defaults({
7189                         model: this.model.toJSON()
7190                 }, this.options );
7191         },
7192         /**
7193          * @returns {wp.media.view.Settings} Returns itself to allow chaining
7194          */
7195         render: function() {
7196                 View.prototype.render.apply( this, arguments );
7197                 // Select the correct values.
7198                 _( this.model.attributes ).chain().keys().each( this.update, this );
7199                 return this;
7200         },
7201         /**
7202          * @param {string} key
7203          */
7204         update: function( key ) {
7205                 var value = this.model.get( key ),
7206                         $setting = this.$('[data-setting="' + key + '"]'),
7207                         $buttons, $value;
7208
7209                 // Bail if we didn't find a matching setting.
7210                 if ( ! $setting.length ) {
7211                         return;
7212                 }
7213
7214                 // Attempt to determine how the setting is rendered and update
7215                 // the selected value.
7216
7217                 // Handle dropdowns.
7218                 if ( $setting.is('select') ) {
7219                         $value = $setting.find('[value="' + value + '"]');
7220
7221                         if ( $value.length ) {
7222                                 $setting.find('option').prop( 'selected', false );
7223                                 $value.prop( 'selected', true );
7224                         } else {
7225                                 // If we can't find the desired value, record what *is* selected.
7226                                 this.model.set( key, $setting.find(':selected').val() );
7227                         }
7228
7229                 // Handle button groups.
7230                 } else if ( $setting.hasClass('button-group') ) {
7231                         $buttons = $setting.find('button').removeClass('active');
7232                         $buttons.filter( '[value="' + value + '"]' ).addClass('active');
7233
7234                 // Handle text inputs and textareas.
7235                 } else if ( $setting.is('input[type="text"], textarea') ) {
7236                         if ( ! $setting.is(':focus') ) {
7237                                 $setting.val( value );
7238                         }
7239                 // Handle checkboxes.
7240                 } else if ( $setting.is('input[type="checkbox"]') ) {
7241                         $setting.prop( 'checked', !! value && 'false' !== value );
7242                 }
7243         },
7244         /**
7245          * @param {Object} event
7246          */
7247         updateHandler: function( event ) {
7248                 var $setting = $( event.target ).closest('[data-setting]'),
7249                         value = event.target.value,
7250                         userSetting;
7251
7252                 event.preventDefault();
7253
7254                 if ( ! $setting.length ) {
7255                         return;
7256                 }
7257
7258                 // Use the correct value for checkboxes.
7259                 if ( $setting.is('input[type="checkbox"]') ) {
7260                         value = $setting[0].checked;
7261                 }
7262
7263                 // Update the corresponding setting.
7264                 this.model.set( $setting.data('setting'), value );
7265
7266                 // If the setting has a corresponding user setting,
7267                 // update that as well.
7268                 if ( userSetting = $setting.data('userSetting') ) {
7269                         window.setUserSetting( userSetting, value );
7270                 }
7271         },
7272
7273         updateChanges: function( model ) {
7274                 if ( model.hasChanged() ) {
7275                         _( model.changed ).chain().keys().each( this.update, this );
7276                 }
7277         }
7278 });
7279
7280 module.exports = Settings;
7281
7282 },{}],60:[function(require,module,exports){
7283 /**
7284  * wp.media.view.Settings.AttachmentDisplay
7285  *
7286  * @class
7287  * @augments wp.media.view.Settings
7288  * @augments wp.media.View
7289  * @augments wp.Backbone.View
7290  * @augments Backbone.View
7291  */
7292 var Settings = wp.media.view.Settings,
7293         AttachmentDisplay;
7294
7295 AttachmentDisplay = Settings.extend({
7296         className: 'attachment-display-settings',
7297         template:  wp.template('attachment-display-settings'),
7298
7299         initialize: function() {
7300                 var attachment = this.options.attachment;
7301
7302                 _.defaults( this.options, {
7303                         userSettings: false
7304                 });
7305                 // Call 'initialize' directly on the parent class.
7306                 Settings.prototype.initialize.apply( this, arguments );
7307                 this.listenTo( this.model, 'change:link', this.updateLinkTo );
7308
7309                 if ( attachment ) {
7310                         attachment.on( 'change:uploading', this.render, this );
7311                 }
7312         },
7313
7314         dispose: function() {
7315                 var attachment = this.options.attachment;
7316                 if ( attachment ) {
7317                         attachment.off( null, null, this );
7318                 }
7319                 /**
7320                  * call 'dispose' directly on the parent class
7321                  */
7322                 Settings.prototype.dispose.apply( this, arguments );
7323         },
7324         /**
7325          * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
7326          */
7327         render: function() {
7328                 var attachment = this.options.attachment;
7329                 if ( attachment ) {
7330                         _.extend( this.options, {
7331                                 sizes: attachment.get('sizes'),
7332                                 type:  attachment.get('type')
7333                         });
7334                 }
7335                 /**
7336                  * call 'render' directly on the parent class
7337                  */
7338                 Settings.prototype.render.call( this );
7339                 this.updateLinkTo();
7340                 return this;
7341         },
7342
7343         updateLinkTo: function() {
7344                 var linkTo = this.model.get('link'),
7345                         $input = this.$('.link-to-custom'),
7346                         attachment = this.options.attachment;
7347
7348                 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
7349                         $input.addClass( 'hidden' );
7350                         return;
7351                 }
7352
7353                 if ( attachment ) {
7354                         if ( 'post' === linkTo ) {
7355                                 $input.val( attachment.get('link') );
7356                         } else if ( 'file' === linkTo ) {
7357                                 $input.val( attachment.get('url') );
7358                         } else if ( ! this.model.get('linkUrl') ) {
7359                                 $input.val('http://');
7360                         }
7361
7362                         $input.prop( 'readonly', 'custom' !== linkTo );
7363                 }
7364
7365                 $input.removeClass( 'hidden' );
7366
7367                 // If the input is visible, focus and select its contents.
7368                 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
7369                         $input.focus()[0].select();
7370                 }
7371         }
7372 });
7373
7374 module.exports = AttachmentDisplay;
7375
7376 },{}],61:[function(require,module,exports){
7377 /**
7378  * wp.media.view.Settings.Gallery
7379  *
7380  * @class
7381  * @augments wp.media.view.Settings
7382  * @augments wp.media.View
7383  * @augments wp.Backbone.View
7384  * @augments Backbone.View
7385  */
7386 var Gallery = wp.media.view.Settings.extend({
7387         className: 'collection-settings gallery-settings',
7388         template:  wp.template('gallery-settings')
7389 });
7390
7391 module.exports = Gallery;
7392
7393 },{}],62:[function(require,module,exports){
7394 /**
7395  * wp.media.view.Settings.Playlist
7396  *
7397  * @class
7398  * @augments wp.media.view.Settings
7399  * @augments wp.media.View
7400  * @augments wp.Backbone.View
7401  * @augments Backbone.View
7402  */
7403 var Playlist = wp.media.view.Settings.extend({
7404         className: 'collection-settings playlist-settings',
7405         template:  wp.template('playlist-settings')
7406 });
7407
7408 module.exports = Playlist;
7409
7410 },{}],63:[function(require,module,exports){
7411 /**
7412  * wp.media.view.Sidebar
7413  *
7414  * @class
7415  * @augments wp.media.view.PriorityList
7416  * @augments wp.media.View
7417  * @augments wp.Backbone.View
7418  * @augments Backbone.View
7419  */
7420 var Sidebar = wp.media.view.PriorityList.extend({
7421         className: 'media-sidebar'
7422 });
7423
7424 module.exports = Sidebar;
7425
7426 },{}],64:[function(require,module,exports){
7427 /**
7428  * wp.media.view.SiteIconCropper
7429  *
7430  * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
7431  *
7432  * Takes imgAreaSelect options from
7433  * wp.customize.SiteIconControl.calculateImageSelectOptions.
7434  *
7435  * @class
7436  * @augments wp.media.view.Cropper
7437  * @augments wp.media.View
7438  * @augments wp.Backbone.View
7439  * @augments Backbone.View
7440  */
7441 var View = wp.media.view,
7442         SiteIconCropper;
7443
7444 SiteIconCropper = View.Cropper.extend({
7445         className: 'crop-content site-icon',
7446
7447         ready: function () {
7448                 View.Cropper.prototype.ready.apply( this, arguments );
7449
7450                 this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
7451         },
7452
7453         addSidebar: function() {
7454                 this.sidebar = new wp.media.view.Sidebar({
7455                         controller: this.controller
7456                 });
7457
7458                 this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
7459                         controller: this.controller,
7460                         attachment: this.options.attachment
7461                 }) );
7462
7463                 this.controller.cropperView.views.add( this.sidebar );
7464         }
7465 });
7466
7467 module.exports = SiteIconCropper;
7468
7469 },{}],65:[function(require,module,exports){
7470 /**
7471  * wp.media.view.SiteIconPreview
7472  *
7473  * Shows a preview of the Site Icon as a favicon and app icon while cropping.
7474  *
7475  * @class
7476  * @augments wp.media.View
7477  * @augments wp.Backbone.View
7478  * @augments Backbone.View
7479  */
7480 var View = wp.media.View,
7481         $ = jQuery,
7482         SiteIconPreview;
7483
7484 SiteIconPreview = View.extend({
7485         className: 'site-icon-preview',
7486         template: wp.template( 'site-icon-preview' ),
7487
7488         ready: function() {
7489                 this.controller.imgSelect.setOptions({
7490                         onInit: this.updatePreview,
7491                         onSelectChange: this.updatePreview
7492                 });
7493         },
7494
7495         prepare: function() {
7496                 return {
7497                         url: this.options.attachment.get( 'url' )
7498                 };
7499         },
7500
7501         updatePreview: function( img, coords ) {
7502                 var rx = 64 / coords.width,
7503                         ry = 64 / coords.height,
7504                         preview_rx = 16 / coords.width,
7505                         preview_ry = 16 / coords.height;
7506
7507                 $( '#preview-app-icon' ).css({
7508                         width: Math.round(rx * this.imageWidth ) + 'px',
7509                         height: Math.round(ry * this.imageHeight ) + 'px',
7510                         marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
7511                         marginTop: '-' + Math.round(ry * coords.y1) + 'px'
7512                 });
7513
7514                 $( '#preview-favicon' ).css({
7515                         width: Math.round( preview_rx * this.imageWidth ) + 'px',
7516                         height: Math.round( preview_ry * this.imageHeight ) + 'px',
7517                         marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
7518                         marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
7519                 });
7520         }
7521 });
7522
7523 module.exports = SiteIconPreview;
7524
7525 },{}],66:[function(require,module,exports){
7526 /**
7527  * wp.media.view.Spinner
7528  *
7529  * @class
7530  * @augments wp.media.View
7531  * @augments wp.Backbone.View
7532  * @augments Backbone.View
7533  */
7534 var Spinner = wp.media.View.extend({
7535         tagName:   'span',
7536         className: 'spinner',
7537         spinnerTimeout: false,
7538         delay: 400,
7539
7540         show: function() {
7541                 if ( ! this.spinnerTimeout ) {
7542                         this.spinnerTimeout = _.delay(function( $el ) {
7543                                 $el.addClass( 'is-active' );
7544                         }, this.delay, this.$el );
7545                 }
7546
7547                 return this;
7548         },
7549
7550         hide: function() {
7551                 this.$el.removeClass( 'is-active' );
7552                 this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
7553
7554                 return this;
7555         }
7556 });
7557
7558 module.exports = Spinner;
7559
7560 },{}],67:[function(require,module,exports){
7561 /**
7562  * wp.media.view.Toolbar
7563  *
7564  * A toolbar which consists of a primary and a secondary section. Each sections
7565  * can be filled with views.
7566  *
7567  * @class
7568  * @augments wp.media.View
7569  * @augments wp.Backbone.View
7570  * @augments Backbone.View
7571  */
7572 var View = wp.media.View,
7573         Toolbar;
7574
7575 Toolbar = View.extend({
7576         tagName:   'div',
7577         className: 'media-toolbar',
7578
7579         initialize: function() {
7580                 var state = this.controller.state(),
7581                         selection = this.selection = state.get('selection'),
7582                         library = this.library = state.get('library');
7583
7584                 this._views = {};
7585
7586                 // The toolbar is composed of two `PriorityList` views.
7587                 this.primary   = new wp.media.view.PriorityList();
7588                 this.secondary = new wp.media.view.PriorityList();
7589                 this.primary.$el.addClass('media-toolbar-primary search-form');
7590                 this.secondary.$el.addClass('media-toolbar-secondary');
7591
7592                 this.views.set([ this.secondary, this.primary ]);
7593
7594                 if ( this.options.items ) {
7595                         this.set( this.options.items, { silent: true });
7596                 }
7597
7598                 if ( ! this.options.silent ) {
7599                         this.render();
7600                 }
7601
7602                 if ( selection ) {
7603                         selection.on( 'add remove reset', this.refresh, this );
7604                 }
7605
7606                 if ( library ) {
7607                         library.on( 'add remove reset', this.refresh, this );
7608                 }
7609         },
7610         /**
7611          * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
7612          */
7613         dispose: function() {
7614                 if ( this.selection ) {
7615                         this.selection.off( null, null, this );
7616                 }
7617
7618                 if ( this.library ) {
7619                         this.library.off( null, null, this );
7620                 }
7621                 /**
7622                  * call 'dispose' directly on the parent class
7623                  */
7624                 return View.prototype.dispose.apply( this, arguments );
7625         },
7626
7627         ready: function() {
7628                 this.refresh();
7629         },
7630
7631         /**
7632          * @param {string} id
7633          * @param {Backbone.View|Object} view
7634          * @param {Object} [options={}]
7635          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
7636          */
7637         set: function( id, view, options ) {
7638                 var list;
7639                 options = options || {};
7640
7641                 // Accept an object with an `id` : `view` mapping.
7642                 if ( _.isObject( id ) ) {
7643                         _.each( id, function( view, id ) {
7644                                 this.set( id, view, { silent: true });
7645                         }, this );
7646
7647                 } else {
7648                         if ( ! ( view instanceof Backbone.View ) ) {
7649                                 view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
7650                                 view = new wp.media.view.Button( view ).render();
7651                         }
7652
7653                         view.controller = view.controller || this.controller;
7654
7655                         this._views[ id ] = view;
7656
7657                         list = view.options.priority < 0 ? 'secondary' : 'primary';
7658                         this[ list ].set( id, view, options );
7659                 }
7660
7661                 if ( ! options.silent ) {
7662                         this.refresh();
7663                 }
7664
7665                 return this;
7666         },
7667         /**
7668          * @param {string} id
7669          * @returns {wp.media.view.Button}
7670          */
7671         get: function( id ) {
7672                 return this._views[ id ];
7673         },
7674         /**
7675          * @param {string} id
7676          * @param {Object} options
7677          * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
7678          */
7679         unset: function( id, options ) {
7680                 delete this._views[ id ];
7681                 this.primary.unset( id, options );
7682                 this.secondary.unset( id, options );
7683
7684                 if ( ! options || ! options.silent ) {
7685                         this.refresh();
7686                 }
7687                 return this;
7688         },
7689
7690         refresh: function() {
7691                 var state = this.controller.state(),
7692                         library = state.get('library'),
7693                         selection = state.get('selection');
7694
7695                 _.each( this._views, function( button ) {
7696                         if ( ! button.model || ! button.options || ! button.options.requires ) {
7697                                 return;
7698                         }
7699
7700                         var requires = button.options.requires,
7701                                 disabled = false;
7702
7703                         // Prevent insertion of attachments if any of them are still uploading
7704                         disabled = _.some( selection.models, function( attachment ) {
7705                                 return attachment.get('uploading') === true;
7706                         });
7707
7708                         if ( requires.selection && selection && ! selection.length ) {
7709                                 disabled = true;
7710                         } else if ( requires.library && library && ! library.length ) {
7711                                 disabled = true;
7712                         }
7713                         button.model.set( 'disabled', disabled );
7714                 });
7715         }
7716 });
7717
7718 module.exports = Toolbar;
7719
7720 },{}],68:[function(require,module,exports){
7721 /**
7722  * wp.media.view.Toolbar.Embed
7723  *
7724  * @class
7725  * @augments wp.media.view.Toolbar.Select
7726  * @augments wp.media.view.Toolbar
7727  * @augments wp.media.View
7728  * @augments wp.Backbone.View
7729  * @augments Backbone.View
7730  */
7731 var Select = wp.media.view.Toolbar.Select,
7732         l10n = wp.media.view.l10n,
7733         Embed;
7734
7735 Embed = Select.extend({
7736         initialize: function() {
7737                 _.defaults( this.options, {
7738                         text: l10n.insertIntoPost,
7739                         requires: false
7740                 });
7741                 // Call 'initialize' directly on the parent class.
7742                 Select.prototype.initialize.apply( this, arguments );
7743         },
7744
7745         refresh: function() {
7746                 var url = this.controller.state().props.get('url');
7747                 this.get('select').model.set( 'disabled', ! url || url === 'http://' );
7748                 /**
7749                  * call 'refresh' directly on the parent class
7750                  */
7751                 Select.prototype.refresh.apply( this, arguments );
7752         }
7753 });
7754
7755 module.exports = Embed;
7756
7757 },{}],69:[function(require,module,exports){
7758 /**
7759  * wp.media.view.Toolbar.Select
7760  *
7761  * @class
7762  * @augments wp.media.view.Toolbar
7763  * @augments wp.media.View
7764  * @augments wp.Backbone.View
7765  * @augments Backbone.View
7766  */
7767 var Toolbar = wp.media.view.Toolbar,
7768         l10n = wp.media.view.l10n,
7769         Select;
7770
7771 Select = Toolbar.extend({
7772         initialize: function() {
7773                 var options = this.options;
7774
7775                 _.bindAll( this, 'clickSelect' );
7776
7777                 _.defaults( options, {
7778                         event: 'select',
7779                         state: false,
7780                         reset: true,
7781                         close: true,
7782                         text:  l10n.select,
7783
7784                         // Does the button rely on the selection?
7785                         requires: {
7786                                 selection: true
7787                         }
7788                 });
7789
7790                 options.items = _.defaults( options.items || {}, {
7791                         select: {
7792                                 style:    'primary',
7793                                 text:     options.text,
7794                                 priority: 80,
7795                                 click:    this.clickSelect,
7796                                 requires: options.requires
7797                         }
7798                 });
7799                 // Call 'initialize' directly on the parent class.
7800                 Toolbar.prototype.initialize.apply( this, arguments );
7801         },
7802
7803         clickSelect: function() {
7804                 var options = this.options,
7805                         controller = this.controller;
7806
7807                 if ( options.close ) {
7808                         controller.close();
7809                 }
7810
7811                 if ( options.event ) {
7812                         controller.state().trigger( options.event );
7813                 }
7814
7815                 if ( options.state ) {
7816                         controller.setState( options.state );
7817                 }
7818
7819                 if ( options.reset ) {
7820                         controller.reset();
7821                 }
7822         }
7823 });
7824
7825 module.exports = Select;
7826
7827 },{}],70:[function(require,module,exports){
7828 /**
7829  * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
7830  * and relays drag'n'dropped files to a media workflow.
7831  *
7832  * wp.media.view.EditorUploader
7833  *
7834  * @class
7835  * @augments wp.media.View
7836  * @augments wp.Backbone.View
7837  * @augments Backbone.View
7838  */
7839 var View = wp.media.View,
7840         l10n = wp.media.view.l10n,
7841         $ = jQuery,
7842         EditorUploader;
7843
7844 EditorUploader = View.extend({
7845         tagName:   'div',
7846         className: 'uploader-editor',
7847         template:  wp.template( 'uploader-editor' ),
7848
7849         localDrag: false,
7850         overContainer: false,
7851         overDropzone: false,
7852         draggingFile: null,
7853
7854         /**
7855          * Bind drag'n'drop events to callbacks.
7856          */
7857         initialize: function() {
7858                 this.initialized = false;
7859
7860                 // Bail if not enabled or UA does not support drag'n'drop or File API.
7861                 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
7862                         return this;
7863                 }
7864
7865                 this.$document = $(document);
7866                 this.dropzones = [];
7867                 this.files = [];
7868
7869                 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
7870                 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
7871                 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
7872                 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
7873
7874                 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
7875                 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
7876
7877                 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
7878                         this.localDrag = event.type === 'dragstart';
7879
7880                         if ( event.type === 'drop' ) {
7881                                 this.containerDragleave();
7882                         }
7883                 }, this ) );
7884
7885                 this.initialized = true;
7886                 return this;
7887         },
7888
7889         /**
7890          * Check browser support for drag'n'drop.
7891          *
7892          * @return Boolean
7893          */
7894         browserSupport: function() {
7895                 var supports = false, div = document.createElement('div');
7896
7897                 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
7898                 supports = supports && !! ( window.File && window.FileList && window.FileReader );
7899                 return supports;
7900         },
7901
7902         isDraggingFile: function( event ) {
7903                 if ( this.draggingFile !== null ) {
7904                         return this.draggingFile;
7905                 }
7906
7907                 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
7908                         return false;
7909                 }
7910
7911                 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
7912                         _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
7913
7914                 return this.draggingFile;
7915         },
7916
7917         refresh: function( e ) {
7918                 var dropzone_id;
7919                 for ( dropzone_id in this.dropzones ) {
7920                         // Hide the dropzones only if dragging has left the screen.
7921                         this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
7922                 }
7923
7924                 if ( ! _.isUndefined( e ) ) {
7925                         $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
7926                 }
7927
7928                 if ( ! this.overContainer && ! this.overDropzone ) {
7929                         this.draggingFile = null;
7930                 }
7931
7932                 return this;
7933         },
7934
7935         render: function() {
7936                 if ( ! this.initialized ) {
7937                         return this;
7938                 }
7939
7940                 View.prototype.render.apply( this, arguments );
7941                 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
7942                 return this;
7943         },
7944
7945         attach: function( index, editor ) {
7946                 // Attach a dropzone to an editor.
7947                 var dropzone = this.$el.clone();
7948                 this.dropzones.push( dropzone );
7949                 $( editor ).append( dropzone );
7950                 return this;
7951         },
7952
7953         /**
7954          * When a file is dropped on the editor uploader, open up an editor media workflow
7955          * and upload the file immediately.
7956          *
7957          * @param  {jQuery.Event} event The 'drop' event.
7958          */
7959         drop: function( event ) {
7960                 var $wrap, uploadView;
7961
7962                 this.containerDragleave( event );
7963                 this.dropzoneDragleave( event );
7964
7965                 this.files = event.originalEvent.dataTransfer.files;
7966                 if ( this.files.length < 1 ) {
7967                         return;
7968                 }
7969
7970                 // Set the active editor to the drop target.
7971                 $wrap = $( event.target ).parents( '.wp-editor-wrap' );
7972                 if ( $wrap.length > 0 && $wrap[0].id ) {
7973                         window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
7974                 }
7975
7976                 if ( ! this.workflow ) {
7977                         this.workflow = wp.media.editor.open( window.wpActiveEditor, {
7978                                 frame:    'post',
7979                                 state:    'insert',
7980                                 title:    l10n.addMedia,
7981                                 multiple: true
7982                         });
7983
7984                         uploadView = this.workflow.uploader;
7985
7986                         if ( uploadView.uploader && uploadView.uploader.ready ) {
7987                                 this.addFiles.apply( this );
7988                         } else {
7989                                 this.workflow.on( 'uploader:ready', this.addFiles, this );
7990                         }
7991                 } else {
7992                         this.workflow.state().reset();
7993                         this.addFiles.apply( this );
7994                         this.workflow.open();
7995                 }
7996
7997                 return false;
7998         },
7999
8000         /**
8001          * Add the files to the uploader.
8002          */
8003         addFiles: function() {
8004                 if ( this.files.length ) {
8005                         this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
8006                         this.files = [];
8007                 }
8008                 return this;
8009         },
8010
8011         containerDragover: function( event ) {
8012                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
8013                         return;
8014                 }
8015
8016                 this.overContainer = true;
8017                 this.refresh();
8018         },
8019
8020         containerDragleave: function() {
8021                 this.overContainer = false;
8022
8023                 // Throttle dragleave because it's called when bouncing from some elements to others.
8024                 _.delay( _.bind( this.refresh, this ), 50 );
8025         },
8026
8027         dropzoneDragover: function( event ) {
8028                 if ( this.localDrag || ! this.isDraggingFile( event ) ) {
8029                         return;
8030                 }
8031
8032                 this.overDropzone = true;
8033                 this.refresh( event );
8034                 return false;
8035         },
8036
8037         dropzoneDragleave: function( e ) {
8038                 this.overDropzone = false;
8039                 _.delay( _.bind( this.refresh, this, e ), 50 );
8040         },
8041
8042         click: function( e ) {
8043                 // In the rare case where the dropzone gets stuck, hide it on click.
8044                 this.containerDragleave( e );
8045                 this.dropzoneDragleave( e );
8046                 this.localDrag = false;
8047         }
8048 });
8049
8050 module.exports = EditorUploader;
8051
8052 },{}],71:[function(require,module,exports){
8053 /**
8054  * wp.media.view.UploaderInline
8055  *
8056  * The inline uploader that shows up in the 'Upload Files' tab.
8057  *
8058  * @class
8059  * @augments wp.media.View
8060  * @augments wp.Backbone.View
8061  * @augments Backbone.View
8062  */
8063 var View = wp.media.View,
8064         UploaderInline;
8065
8066 UploaderInline = View.extend({
8067         tagName:   'div',
8068         className: 'uploader-inline',
8069         template:  wp.template('uploader-inline'),
8070
8071         events: {
8072                 'click .close': 'hide'
8073         },
8074
8075         initialize: function() {
8076                 _.defaults( this.options, {
8077                         message: '',
8078                         status:  true,
8079                         canClose: false
8080                 });
8081
8082                 if ( ! this.options.$browser && this.controller.uploader ) {
8083                         this.options.$browser = this.controller.uploader.$browser;
8084                 }
8085
8086                 if ( _.isUndefined( this.options.postId ) ) {
8087                         this.options.postId = wp.media.view.settings.post.id;
8088                 }
8089
8090                 if ( this.options.status ) {
8091                         this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
8092                                 controller: this.controller
8093                         }) );
8094                 }
8095         },
8096
8097         prepare: function() {
8098                 var suggestedWidth = this.controller.state().get('suggestedWidth'),
8099                         suggestedHeight = this.controller.state().get('suggestedHeight'),
8100                         data = {};
8101
8102                 data.message = this.options.message;
8103                 data.canClose = this.options.canClose;
8104
8105                 if ( suggestedWidth && suggestedHeight ) {
8106                         data.suggestedWidth = suggestedWidth;
8107                         data.suggestedHeight = suggestedHeight;
8108                 }
8109
8110                 return data;
8111         },
8112         /**
8113          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
8114          */
8115         dispose: function() {
8116                 if ( this.disposing ) {
8117                         /**
8118                          * call 'dispose' directly on the parent class
8119                          */
8120                         return View.prototype.dispose.apply( this, arguments );
8121                 }
8122
8123                 // Run remove on `dispose`, so we can be sure to refresh the
8124                 // uploader with a view-less DOM. Track whether we're disposing
8125                 // so we don't trigger an infinite loop.
8126                 this.disposing = true;
8127                 return this.remove();
8128         },
8129         /**
8130          * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
8131          */
8132         remove: function() {
8133                 /**
8134                  * call 'remove' directly on the parent class
8135                  */
8136                 var result = View.prototype.remove.apply( this, arguments );
8137
8138                 _.defer( _.bind( this.refresh, this ) );
8139                 return result;
8140         },
8141
8142         refresh: function() {
8143                 var uploader = this.controller.uploader;
8144
8145                 if ( uploader ) {
8146                         uploader.refresh();
8147                 }
8148         },
8149         /**
8150          * @returns {wp.media.view.UploaderInline}
8151          */
8152         ready: function() {
8153                 var $browser = this.options.$browser,
8154                         $placeholder;
8155
8156                 if ( this.controller.uploader ) {
8157                         $placeholder = this.$('.browser');
8158
8159                         // Check if we've already replaced the placeholder.
8160                         if ( $placeholder[0] === $browser[0] ) {
8161                                 return;
8162                         }
8163
8164                         $browser.detach().text( $placeholder.text() );
8165                         $browser[0].className = $placeholder[0].className;
8166                         $placeholder.replaceWith( $browser.show() );
8167                 }
8168
8169                 this.refresh();
8170                 return this;
8171         },
8172         show: function() {
8173                 this.$el.removeClass( 'hidden' );
8174         },
8175         hide: function() {
8176                 this.$el.addClass( 'hidden' );
8177         }
8178
8179 });
8180
8181 module.exports = UploaderInline;
8182
8183 },{}],72:[function(require,module,exports){
8184 /**
8185  * wp.media.view.UploaderStatusError
8186  *
8187  * @class
8188  * @augments wp.media.View
8189  * @augments wp.Backbone.View
8190  * @augments Backbone.View
8191  */
8192 var UploaderStatusError = wp.media.View.extend({
8193         className: 'upload-error',
8194         template:  wp.template('uploader-status-error')
8195 });
8196
8197 module.exports = UploaderStatusError;
8198
8199 },{}],73:[function(require,module,exports){
8200 /**
8201  * wp.media.view.UploaderStatus
8202  *
8203  * An uploader status for on-going uploads.
8204  *
8205  * @class
8206  * @augments wp.media.View
8207  * @augments wp.Backbone.View
8208  * @augments Backbone.View
8209  */
8210 var View = wp.media.View,
8211         UploaderStatus;
8212
8213 UploaderStatus = View.extend({
8214         className: 'media-uploader-status',
8215         template:  wp.template('uploader-status'),
8216
8217         events: {
8218                 'click .upload-dismiss-errors': 'dismiss'
8219         },
8220
8221         initialize: function() {
8222                 this.queue = wp.Uploader.queue;
8223                 this.queue.on( 'add remove reset', this.visibility, this );
8224                 this.queue.on( 'add remove reset change:percent', this.progress, this );
8225                 this.queue.on( 'add remove reset change:uploading', this.info, this );
8226
8227                 this.errors = wp.Uploader.errors;
8228                 this.errors.reset();
8229                 this.errors.on( 'add remove reset', this.visibility, this );
8230                 this.errors.on( 'add', this.error, this );
8231         },
8232         /**
8233          * @global wp.Uploader
8234          * @returns {wp.media.view.UploaderStatus}
8235          */
8236         dispose: function() {
8237                 wp.Uploader.queue.off( null, null, this );
8238                 /**
8239                  * call 'dispose' directly on the parent class
8240                  */
8241                 View.prototype.dispose.apply( this, arguments );
8242                 return this;
8243         },
8244
8245         visibility: function() {
8246                 this.$el.toggleClass( 'uploading', !! this.queue.length );
8247                 this.$el.toggleClass( 'errors', !! this.errors.length );
8248                 this.$el.toggle( !! this.queue.length || !! this.errors.length );
8249         },
8250
8251         ready: function() {
8252                 _.each({
8253                         '$bar':      '.media-progress-bar div',
8254                         '$index':    '.upload-index',
8255                         '$total':    '.upload-total',
8256                         '$filename': '.upload-filename'
8257                 }, function( selector, key ) {
8258                         this[ key ] = this.$( selector );
8259                 }, this );
8260
8261                 this.visibility();
8262                 this.progress();
8263                 this.info();
8264         },
8265
8266         progress: function() {
8267                 var queue = this.queue,
8268                         $bar = this.$bar;
8269
8270                 if ( ! $bar || ! queue.length ) {
8271                         return;
8272                 }
8273
8274                 $bar.width( ( queue.reduce( function( memo, attachment ) {
8275                         if ( ! attachment.get('uploading') ) {
8276                                 return memo + 100;
8277                         }
8278
8279                         var percent = attachment.get('percent');
8280                         return memo + ( _.isNumber( percent ) ? percent : 100 );
8281                 }, 0 ) / queue.length ) + '%' );
8282         },
8283
8284         info: function() {
8285                 var queue = this.queue,
8286                         index = 0, active;
8287
8288                 if ( ! queue.length ) {
8289                         return;
8290                 }
8291
8292                 active = this.queue.find( function( attachment, i ) {
8293                         index = i;
8294                         return attachment.get('uploading');
8295                 });
8296
8297                 this.$index.text( index + 1 );
8298                 this.$total.text( queue.length );
8299                 this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
8300         },
8301         /**
8302          * @param {string} filename
8303          * @returns {string}
8304          */
8305         filename: function( filename ) {
8306                 return _.escape( filename );
8307         },
8308         /**
8309          * @param {Backbone.Model} error
8310          */
8311         error: function( error ) {
8312                 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
8313                         filename: this.filename( error.get('file').name ),
8314                         message:  error.get('message')
8315                 }), { at: 0 });
8316         },
8317
8318         /**
8319          * @global wp.Uploader
8320          *
8321          * @param {Object} event
8322          */
8323         dismiss: function( event ) {
8324                 var errors = this.views.get('.upload-errors');
8325
8326                 event.preventDefault();
8327
8328                 if ( errors ) {
8329                         _.invoke( errors, 'remove' );
8330                 }
8331                 wp.Uploader.errors.reset();
8332         }
8333 });
8334
8335 module.exports = UploaderStatus;
8336
8337 },{}],74:[function(require,module,exports){
8338 /**
8339  * wp.media.view.UploaderWindow
8340  *
8341  * An uploader window that allows for dragging and dropping media.
8342  *
8343  * @class
8344  * @augments wp.media.View
8345  * @augments wp.Backbone.View
8346  * @augments Backbone.View
8347  *
8348  * @param {object} [options]                   Options hash passed to the view.
8349  * @param {object} [options.uploader]          Uploader properties.
8350  * @param {jQuery} [options.uploader.browser]
8351  * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
8352  * @param {object} [options.uploader.params]
8353  */
8354 var $ = jQuery,
8355         UploaderWindow;
8356
8357 UploaderWindow = wp.media.View.extend({
8358         tagName:   'div',
8359         className: 'uploader-window',
8360         template:  wp.template('uploader-window'),
8361
8362         initialize: function() {
8363                 var uploader;
8364
8365                 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
8366
8367                 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
8368                         dropzone:  this.$el,
8369                         browser:   this.$browser,
8370                         params:    {}
8371                 });
8372
8373                 // Ensure the dropzone is a jQuery collection.
8374                 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
8375                         uploader.dropzone = $( uploader.dropzone );
8376                 }
8377
8378                 this.controller.on( 'activate', this.refresh, this );
8379
8380                 this.controller.on( 'detach', function() {
8381                         this.$browser.remove();
8382                 }, this );
8383         },
8384
8385         refresh: function() {
8386                 if ( this.uploader ) {
8387                         this.uploader.refresh();
8388                 }
8389         },
8390
8391         ready: function() {
8392                 var postId = wp.media.view.settings.post.id,
8393                         dropzone;
8394
8395                 // If the uploader already exists, bail.
8396                 if ( this.uploader ) {
8397                         return;
8398                 }
8399
8400                 if ( postId ) {
8401                         this.options.uploader.params.post_id = postId;
8402                 }
8403                 this.uploader = new wp.Uploader( this.options.uploader );
8404
8405                 dropzone = this.uploader.dropzone;
8406                 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
8407                 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
8408
8409                 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
8410         },
8411
8412         _ready: function() {
8413                 this.controller.trigger( 'uploader:ready' );
8414         },
8415
8416         show: function() {
8417                 var $el = this.$el.show();
8418
8419                 // Ensure that the animation is triggered by waiting until
8420                 // the transparent element is painted into the DOM.
8421                 _.defer( function() {
8422                         $el.css({ opacity: 1 });
8423                 });
8424         },
8425
8426         hide: function() {
8427                 var $el = this.$el.css({ opacity: 0 });
8428
8429                 wp.media.transition( $el ).done( function() {
8430                         // Transition end events are subject to race conditions.
8431                         // Make sure that the value is set as intended.
8432                         if ( '0' === $el.css('opacity') ) {
8433                                 $el.hide();
8434                         }
8435                 });
8436
8437                 // https://core.trac.wordpress.org/ticket/27341
8438                 _.delay( function() {
8439                         if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
8440                                 $el.hide();
8441                         }
8442                 }, 500 );
8443         }
8444 });
8445
8446 module.exports = UploaderWindow;
8447
8448 },{}],75:[function(require,module,exports){
8449 /**
8450  * wp.media.View
8451  *
8452  * The base view class for media.
8453  *
8454  * Undelegating events, removing events from the model, and
8455  * removing events from the controller mirror the code for
8456  * `Backbone.View.dispose` in Backbone 0.9.8 development.
8457  *
8458  * This behavior has since been removed, and should not be used
8459  * outside of the media manager.
8460  *
8461  * @class
8462  * @augments wp.Backbone.View
8463  * @augments Backbone.View
8464  */
8465 var View = wp.Backbone.View.extend({
8466         constructor: function( options ) {
8467                 if ( options && options.controller ) {
8468                         this.controller = options.controller;
8469                 }
8470                 wp.Backbone.View.apply( this, arguments );
8471         },
8472         /**
8473          * @todo The internal comment mentions this might have been a stop-gap
8474          *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
8475          *       care of this in Backbone.View now.
8476          *
8477          * @returns {wp.media.View} Returns itself to allow chaining
8478          */
8479         dispose: function() {
8480                 // Undelegating events, removing events from the model, and
8481                 // removing events from the controller mirror the code for
8482                 // `Backbone.View.dispose` in Backbone 0.9.8 development.
8483                 this.undelegateEvents();
8484
8485                 if ( this.model && this.model.off ) {
8486                         this.model.off( null, null, this );
8487                 }
8488
8489                 if ( this.collection && this.collection.off ) {
8490                         this.collection.off( null, null, this );
8491                 }
8492
8493                 // Unbind controller events.
8494                 if ( this.controller && this.controller.off ) {
8495                         this.controller.off( null, null, this );
8496                 }
8497
8498                 return this;
8499         },
8500         /**
8501          * @returns {wp.media.View} Returns itself to allow chaining
8502          */
8503         remove: function() {
8504                 this.dispose();
8505                 /**
8506                  * call 'remove' directly on the parent class
8507                  */
8508                 return wp.Backbone.View.prototype.remove.apply( this, arguments );
8509         }
8510 });
8511
8512 module.exports = View;
8513
8514 },{}]},{},[19]);