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