5d2fe6056219063c109a93d19686fc04621bfe3e
[autoinstalls/wordpress.git] / wp-includes / js / media-views.js
1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2 /**
3  * wp.media.controller.CollectionAdd
4  *
5  * A state for adding attachments to a collection (e.g. video playlist).
6  *
7  * @class
8  * @augments wp.media.controller.Library
9  * @augments wp.media.controller.State
10  * @augments Backbone.Model
11  *
12  * @param {object}                     [attributes]                         The attributes hash passed to the state.
13  * @param {string}                     [attributes.id=library]      Unique identifier.
14  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
15  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
16  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
17  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
18  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
19  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
20  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
21  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
22  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
23  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
24  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
25  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
26  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
27  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
28  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
29  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
30  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
31  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
32  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
33  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
34  */
35 var Selection = wp.media.model.Selection,
36         Library = wp.media.controller.Library,
37         CollectionAdd;
38
39 CollectionAdd = Library.extend({
40         defaults: _.defaults( {
41                 // Selection defaults. @see media.model.Selection
42                 multiple:      'add',
43                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
44                 filterable:    'uploaded',
45
46                 priority:      100,
47                 syncSelection: false
48         }, Library.prototype.defaults ),
49
50         /**
51          * @since 3.9.0
52          */
53         initialize: function() {
54                 var collectionType = this.get('collectionType');
55
56                 if ( 'video' === this.get( 'type' ) ) {
57                         collectionType = 'video-' + collectionType;
58                 }
59
60                 this.set( 'id', collectionType + '-library' );
61                 this.set( 'toolbar', collectionType + '-add' );
62                 this.set( 'menu', collectionType );
63
64                 // If we haven't been provided a `library`, create a `Selection`.
65                 if ( ! this.get('library') ) {
66                         this.set( 'library', wp.media.query({ type: this.get('type') }) );
67                 }
68                 Library.prototype.initialize.apply( this, arguments );
69         },
70
71         /**
72          * @since 3.9.0
73          */
74         activate: function() {
75                 var library = this.get('library'),
76                         editLibrary = this.get('editLibrary'),
77                         edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
78
79                 if ( editLibrary && editLibrary !== edit ) {
80                         library.unobserve( editLibrary );
81                 }
82
83                 // Accepts attachments that exist in the original library and
84                 // that do not exist in gallery's library.
85                 library.validator = function( attachment ) {
86                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
87                 };
88
89                 // Reset the library to ensure that all attachments are re-added
90                 // to the collection. Do so silently, as calling `observe` will
91                 // trigger the `reset` event.
92                 library.reset( library.mirroring.models, { silent: true });
93                 library.observe( edit );
94                 this.set('editLibrary', edit);
95
96                 Library.prototype.activate.apply( this, arguments );
97         }
98 });
99
100 module.exports = CollectionAdd;
101
102 },{}],2:[function(require,module,exports){
103 /**
104  * wp.media.controller.CollectionEdit
105  *
106  * A state for editing a collection, which is used by audio and video playlists,
107  * and can be used for other collections.
108  *
109  * @class
110  * @augments wp.media.controller.Library
111  * @augments wp.media.controller.State
112  * @augments Backbone.Model
113  *
114  * @param {object}                     [attributes]                      The attributes hash passed to the state.
115  * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
116  * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
117  *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
118  * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
119  * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
120  * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
121  * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
122  * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
123  * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
124  * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
125  * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
126  * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
127  * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
128  * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
129  * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
130  * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
131  *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
132  * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
133  * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
134  *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
135  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
136  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
137  */
138 var Library = wp.media.controller.Library,
139         l10n = wp.media.view.l10n,
140         $ = jQuery,
141         CollectionEdit;
142
143 CollectionEdit = Library.extend({
144         defaults: {
145                 multiple:         false,
146                 sortable:         true,
147                 date:             false,
148                 searchable:       false,
149                 content:          'browse',
150                 describe:         true,
151                 dragInfo:         true,
152                 idealColumnWidth: 170,
153                 editing:          false,
154                 priority:         60,
155                 SettingsView:     false,
156                 syncSelection:    false
157         },
158
159         /**
160          * @since 3.9.0
161          */
162         initialize: function() {
163                 var collectionType = this.get('collectionType');
164
165                 if ( 'video' === this.get( 'type' ) ) {
166                         collectionType = 'video-' + collectionType;
167                 }
168
169                 this.set( 'id', collectionType + '-edit' );
170                 this.set( 'toolbar', collectionType + '-edit' );
171
172                 // If we haven't been provided a `library`, create a `Selection`.
173                 if ( ! this.get('library') ) {
174                         this.set( 'library', new wp.media.model.Selection() );
175                 }
176                 // The single `Attachment` view to be used in the `Attachments` view.
177                 if ( ! this.get('AttachmentView') ) {
178                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
179                 }
180                 Library.prototype.initialize.apply( this, arguments );
181         },
182
183         /**
184          * @since 3.9.0
185          */
186         activate: function() {
187                 var library = this.get('library');
188
189                 // Limit the library to images only.
190                 library.props.set( 'type', this.get( 'type' ) );
191
192                 // Watch for uploaded attachments.
193                 this.get('library').observe( wp.Uploader.queue );
194
195                 this.frame.on( 'content:render:browse', this.renderSettings, this );
196
197                 Library.prototype.activate.apply( this, arguments );
198         },
199
200         /**
201          * @since 3.9.0
202          */
203         deactivate: function() {
204                 // Stop watching for uploaded attachments.
205                 this.get('library').unobserve( wp.Uploader.queue );
206
207                 this.frame.off( 'content:render:browse', this.renderSettings, this );
208
209                 Library.prototype.deactivate.apply( this, arguments );
210         },
211
212         /**
213          * Render the collection embed settings view in the browser sidebar.
214          *
215          * @todo This is against the pattern elsewhere in media. Typically the frame
216          *       is responsible for adding region mode callbacks. Explain.
217          *
218          * @since 3.9.0
219          *
220          * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
221          */
222         renderSettings: function( attachmentsBrowserView ) {
223                 var library = this.get('library'),
224                         collectionType = this.get('collectionType'),
225                         dragInfoText = this.get('dragInfoText'),
226                         SettingsView = this.get('SettingsView'),
227                         obj = {};
228
229                 if ( ! library || ! attachmentsBrowserView ) {
230                         return;
231                 }
232
233                 library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
234
235                 obj[ collectionType ] = new SettingsView({
236                         controller: this,
237                         model:      library[ collectionType ],
238                         priority:   40
239                 });
240
241                 attachmentsBrowserView.sidebar.set( obj );
242
243                 if ( dragInfoText ) {
244                         attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
245                                 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
246                                 priority: -40
247                         }) );
248                 }
249
250                 // Add the 'Reverse order' button to the toolbar.
251                 attachmentsBrowserView.toolbar.set( 'reverse', {
252                         text:     l10n.reverseOrder,
253                         priority: 80,
254
255                         click: function() {
256                                 library.reset( library.toArray().reverse() );
257                         }
258                 });
259         }
260 });
261
262 module.exports = CollectionEdit;
263
264 },{}],3:[function(require,module,exports){
265 /**
266  * wp.media.controller.Cropper
267  *
268  * A state for cropping an image.
269  *
270  * @class
271  * @augments wp.media.controller.State
272  * @augments Backbone.Model
273  */
274 var l10n = wp.media.view.l10n,
275         Cropper;
276
277 Cropper = wp.media.controller.State.extend({
278         defaults: {
279                 id:          'cropper',
280                 title:       l10n.cropImage,
281                 // Region mode defaults.
282                 toolbar:     'crop',
283                 content:     'crop',
284                 router:      false,
285
286                 canSkipCrop: false
287         },
288
289         activate: function() {
290                 this.frame.on( 'content:create:crop', this.createCropContent, this );
291                 this.frame.on( 'close', this.removeCropper, this );
292                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
293         },
294
295         deactivate: function() {
296                 this.frame.toolbar.mode('browse');
297         },
298
299         createCropContent: function() {
300                 this.cropperView = new wp.media.view.Cropper({
301                         controller: this,
302                         attachment: this.get('selection').first()
303                 });
304                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
305                 this.frame.content.set(this.cropperView);
306
307         },
308         removeCropper: function() {
309                 this.imgSelect.cancelSelection();
310                 this.imgSelect.setOptions({remove: true});
311                 this.imgSelect.update();
312                 this.cropperView.remove();
313         },
314         createCropToolbar: function() {
315                 var canSkipCrop, toolbarOptions;
316
317                 canSkipCrop = this.get('canSkipCrop') || false;
318
319                 toolbarOptions = {
320                         controller: this.frame,
321                         items: {
322                                 insert: {
323                                         style:    'primary',
324                                         text:     l10n.cropImage,
325                                         priority: 80,
326                                         requires: { library: false, selection: false },
327
328                                         click: function() {
329                                                 var controller = this.controller,
330                                                         selection;
331
332                                                 selection = controller.state().get('selection').first();
333                                                 selection.set({cropDetails: controller.state().imgSelect.getSelection()});
334
335                                                 this.$el.text(l10n.cropping);
336                                                 this.$el.attr('disabled', true);
337
338                                                 controller.state().doCrop( selection ).done( function( croppedImage ) {
339                                                         controller.trigger('cropped', croppedImage );
340                                                         controller.close();
341                                                 }).fail( function() {
342                                                         controller.trigger('content:error:crop');
343                                                 });
344                                         }
345                                 }
346                         }
347                 };
348
349                 if ( canSkipCrop ) {
350                         _.extend( toolbarOptions.items, {
351                                 skip: {
352                                         style:      'secondary',
353                                         text:       l10n.skipCropping,
354                                         priority:   70,
355                                         requires:   { library: false, selection: false },
356                                         click:      function() {
357                                                 var selection = this.controller.state().get('selection').first();
358                                                 this.controller.state().cropperView.remove();
359                                                 this.controller.trigger('skippedcrop', selection);
360                                                 this.controller.close();
361                                         }
362                                 }
363                         });
364                 }
365
366                 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
367         },
368
369         doCrop: function( attachment ) {
370                 return wp.ajax.post( 'custom-header-crop', {
371                         nonce: attachment.get('nonces').edit,
372                         id: attachment.get('id'),
373                         cropDetails: attachment.get('cropDetails')
374                 } );
375         }
376 });
377
378 module.exports = Cropper;
379
380 },{}],4:[function(require,module,exports){
381 /**
382  * wp.media.controller.CustomizeImageCropper
383  *
384  * A state for cropping an image.
385  *
386  * @class
387  * @augments wp.media.controller.Cropper
388  * @augments wp.media.controller.State
389  * @augments Backbone.Model
390  */
391 var Controller = wp.media.controller,
392         CustomizeImageCropper;
393
394 CustomizeImageCropper = Controller.Cropper.extend({
395         doCrop: function( attachment ) {
396                 var cropDetails = attachment.get( 'cropDetails' ),
397                         control = this.get( 'control' );
398
399                 cropDetails.dst_width  = control.params.width;
400                 cropDetails.dst_height = control.params.height;
401
402                 return wp.ajax.post( 'crop-image', {
403                         wp_customize: 'on',
404                         nonce: attachment.get( 'nonces' ).edit,
405                         id: attachment.get( 'id' ),
406                         context: control.id,
407                         cropDetails: cropDetails
408                 } );
409         }
410 });
411
412 module.exports = CustomizeImageCropper;
413
414 },{}],5:[function(require,module,exports){
415 /**
416  * wp.media.controller.EditImage
417  *
418  * A state for editing (cropping, etc.) an image.
419  *
420  * @class
421  * @augments wp.media.controller.State
422  * @augments Backbone.Model
423  *
424  * @param {object}                    attributes                      The attributes hash passed to the state.
425  * @param {wp.media.model.Attachment} attributes.model                The attachment.
426  * @param {string}                    [attributes.id=edit-image]      Unique identifier.
427  * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
428  * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
429  * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
430  * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
431  * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
432  */
433 var l10n = wp.media.view.l10n,
434         EditImage;
435
436 EditImage = wp.media.controller.State.extend({
437         defaults: {
438                 id:      'edit-image',
439                 title:   l10n.editImage,
440                 menu:    false,
441                 toolbar: 'edit-image',
442                 content: 'edit-image',
443                 url:     ''
444         },
445
446         /**
447          * @since 3.9.0
448          */
449         activate: function() {
450                 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
451         },
452
453         /**
454          * @since 3.9.0
455          */
456         deactivate: function() {
457                 this.stopListening( this.frame );
458         },
459
460         /**
461          * @since 3.9.0
462          */
463         toolbar: function() {
464                 var frame = this.frame,
465                         lastState = frame.lastState(),
466                         previous = lastState && lastState.id;
467
468                 frame.toolbar.set( new wp.media.view.Toolbar({
469                         controller: frame,
470                         items: {
471                                 back: {
472                                         style: 'primary',
473                                         text:     l10n.back,
474                                         priority: 20,
475                                         click:    function() {
476                                                 if ( previous ) {
477                                                         frame.setState( previous );
478                                                 } else {
479                                                         frame.close();
480                                                 }
481                                         }
482                                 }
483                         }
484                 }) );
485         }
486 });
487
488 module.exports = EditImage;
489
490 },{}],6:[function(require,module,exports){
491 /**
492  * wp.media.controller.Embed
493  *
494  * A state for embedding media from a URL.
495  *
496  * @class
497  * @augments wp.media.controller.State
498  * @augments Backbone.Model
499  *
500  * @param {object} attributes                         The attributes hash passed to the state.
501  * @param {string} [attributes.id=embed]              Unique identifier.
502  * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
503  * @param {string} [attributes.content=embed]         Initial mode for the content region.
504  * @param {string} [attributes.menu=default]          Initial mode for the menu region.
505  * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
506  * @param {string} [attributes.menu=false]            Initial mode for the menu region.
507  * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
508  * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
509  * @param {string} [attributes.url]                   The embed URL.
510  * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
511  */
512 var l10n = wp.media.view.l10n,
513         $ = Backbone.$,
514         Embed;
515
516 Embed = wp.media.controller.State.extend({
517         defaults: {
518                 id:       'embed',
519                 title:    l10n.insertFromUrlTitle,
520                 content:  'embed',
521                 menu:     'default',
522                 toolbar:  'main-embed',
523                 priority: 120,
524                 type:     'link',
525                 url:      '',
526                 metadata: {}
527         },
528
529         // The amount of time used when debouncing the scan.
530         sensitivity: 400,
531
532         initialize: function(options) {
533                 this.metadata = options.metadata;
534                 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
535                 this.props = new Backbone.Model( this.metadata || { url: '' });
536                 this.props.on( 'change:url', this.debouncedScan, this );
537                 this.props.on( 'change:url', this.refresh, this );
538                 this.on( 'scan', this.scanImage, this );
539         },
540
541         /**
542          * Trigger a scan of the embedded URL's content for metadata required to embed.
543          *
544          * @fires wp.media.controller.Embed#scan
545          */
546         scan: function() {
547                 var scanners,
548                         embed = this,
549                         attributes = {
550                                 type: 'link',
551                                 scanners: []
552                         };
553
554                 // Scan is triggered with the list of `attributes` to set on the
555                 // state, useful for the 'type' attribute and 'scanners' attribute,
556                 // an array of promise objects for asynchronous scan operations.
557                 if ( this.props.get('url') ) {
558                         this.trigger( 'scan', attributes );
559                 }
560
561                 if ( attributes.scanners.length ) {
562                         scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
563                         scanners.always( function() {
564                                 if ( embed.get('scanners') === scanners ) {
565                                         embed.set( 'loading', false );
566                                 }
567                         });
568                 } else {
569                         attributes.scanners = null;
570                 }
571
572                 attributes.loading = !! attributes.scanners;
573                 this.set( attributes );
574         },
575         /**
576          * Try scanning the embed as an image to discover its dimensions.
577          *
578          * @param {Object} attributes
579          */
580         scanImage: function( attributes ) {
581                 var frame = this.frame,
582                         state = this,
583                         url = this.props.get('url'),
584                         image = new Image(),
585                         deferred = $.Deferred();
586
587                 attributes.scanners.push( deferred.promise() );
588
589                 // Try to load the image and find its width/height.
590                 image.onload = function() {
591                         deferred.resolve();
592
593                         if ( state !== frame.state() || url !== state.props.get('url') ) {
594                                 return;
595                         }
596
597                         state.set({
598                                 type: 'image'
599                         });
600
601                         state.props.set({
602                                 width:  image.width,
603                                 height: image.height
604                         });
605                 };
606
607                 image.onerror = deferred.reject;
608                 image.src = url;
609         },
610
611         refresh: function() {
612                 this.frame.toolbar.get().refresh();
613         },
614
615         reset: function() {
616                 this.props.clear().set({ url: '' });
617
618                 if ( this.active ) {
619                         this.refresh();
620                 }
621         }
622 });
623
624 module.exports = Embed;
625
626 },{}],7:[function(require,module,exports){
627 /**
628  * wp.media.controller.FeaturedImage
629  *
630  * A state for selecting a featured image for a post.
631  *
632  * @class
633  * @augments wp.media.controller.Library
634  * @augments wp.media.controller.State
635  * @augments Backbone.Model
636  *
637  * @param {object}                     [attributes]                          The attributes hash passed to the state.
638  * @param {string}                     [attributes.id=featured-image]        Unique identifier.
639  * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
640  * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
641  *                                                                           If one is not supplied, a collection of all images will be created.
642  * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
643  * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
644  *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
645  * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
646  * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
647  * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
648  * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
649  * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
650  * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
651  *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
652  * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
653  * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
654  * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
655  * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
656  * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
657  */
658 var Attachment = wp.media.model.Attachment,
659         Library = wp.media.controller.Library,
660         l10n = wp.media.view.l10n,
661         FeaturedImage;
662
663 FeaturedImage = Library.extend({
664         defaults: _.defaults({
665                 id:            'featured-image',
666                 title:         l10n.setFeaturedImageTitle,
667                 multiple:      false,
668                 filterable:    'uploaded',
669                 toolbar:       'featured-image',
670                 priority:      60,
671                 syncSelection: true
672         }, Library.prototype.defaults ),
673
674         /**
675          * @since 3.5.0
676          */
677         initialize: function() {
678                 var library, comparator;
679
680                 // If we haven't been provided a `library`, create a `Selection`.
681                 if ( ! this.get('library') ) {
682                         this.set( 'library', wp.media.query({ type: 'image' }) );
683                 }
684
685                 Library.prototype.initialize.apply( this, arguments );
686
687                 library    = this.get('library');
688                 comparator = library.comparator;
689
690                 // Overload the library's comparator to push items that are not in
691                 // the mirrored query to the front of the aggregate collection.
692                 library.comparator = function( a, b ) {
693                         var aInQuery = !! this.mirroring.get( a.cid ),
694                                 bInQuery = !! this.mirroring.get( b.cid );
695
696                         if ( ! aInQuery && bInQuery ) {
697                                 return -1;
698                         } else if ( aInQuery && ! bInQuery ) {
699                                 return 1;
700                         } else {
701                                 return comparator.apply( this, arguments );
702                         }
703                 };
704
705                 // Add all items in the selection to the library, so any featured
706                 // images that are not initially loaded still appear.
707                 library.observe( this.get('selection') );
708         },
709
710         /**
711          * @since 3.5.0
712          */
713         activate: function() {
714                 this.updateSelection();
715                 this.frame.on( 'open', this.updateSelection, this );
716
717                 Library.prototype.activate.apply( this, arguments );
718         },
719
720         /**
721          * @since 3.5.0
722          */
723         deactivate: function() {
724                 this.frame.off( 'open', this.updateSelection, this );
725
726                 Library.prototype.deactivate.apply( this, arguments );
727         },
728
729         /**
730          * @since 3.5.0
731          */
732         updateSelection: function() {
733                 var selection = this.get('selection'),
734                         id = wp.media.view.settings.post.featuredImageId,
735                         attachment;
736
737                 if ( '' !== id && -1 !== id ) {
738                         attachment = Attachment.get( id );
739                         attachment.fetch();
740                 }
741
742                 selection.reset( attachment ? [ attachment ] : [] );
743         }
744 });
745
746 module.exports = FeaturedImage;
747
748 },{}],8:[function(require,module,exports){
749 /**
750  * wp.media.controller.GalleryAdd
751  *
752  * A state for selecting more images to add to a gallery.
753  *
754  * @class
755  * @augments wp.media.controller.Library
756  * @augments wp.media.controller.State
757  * @augments Backbone.Model
758  *
759  * @param {object}                     [attributes]                         The attributes hash passed to the state.
760  * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
761  * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
762  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
763  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
764  *                                                                          If one is not supplied, a collection of all images will be created.
765  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
766  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
767  * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
768  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
769  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
770  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
771  * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
772  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
773  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
774  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
775  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
776  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
777  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
778  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
779  */
780 var Selection = wp.media.model.Selection,
781         Library = wp.media.controller.Library,
782         l10n = wp.media.view.l10n,
783         GalleryAdd;
784
785 GalleryAdd = Library.extend({
786         defaults: _.defaults({
787                 id:            'gallery-library',
788                 title:         l10n.addToGalleryTitle,
789                 multiple:      'add',
790                 filterable:    'uploaded',
791                 menu:          'gallery',
792                 toolbar:       'gallery-add',
793                 priority:      100,
794                 syncSelection: false
795         }, Library.prototype.defaults ),
796
797         /**
798          * @since 3.5.0
799          */
800         initialize: function() {
801                 // If a library wasn't supplied, create a library of images.
802                 if ( ! this.get('library') ) {
803                         this.set( 'library', wp.media.query({ type: 'image' }) );
804                 }
805
806                 Library.prototype.initialize.apply( this, arguments );
807         },
808
809         /**
810          * @since 3.5.0
811          */
812         activate: function() {
813                 var library = this.get('library'),
814                         edit    = this.frame.state('gallery-edit').get('library');
815
816                 if ( this.editLibrary && this.editLibrary !== edit ) {
817                         library.unobserve( this.editLibrary );
818                 }
819
820                 // Accepts attachments that exist in the original library and
821                 // that do not exist in gallery's library.
822                 library.validator = function( attachment ) {
823                         return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
824                 };
825
826                 // Reset the library to ensure that all attachments are re-added
827                 // to the collection. Do so silently, as calling `observe` will
828                 // trigger the `reset` event.
829                 library.reset( library.mirroring.models, { silent: true });
830                 library.observe( edit );
831                 this.editLibrary = edit;
832
833                 Library.prototype.activate.apply( this, arguments );
834         }
835 });
836
837 module.exports = GalleryAdd;
838
839 },{}],9:[function(require,module,exports){
840 /**
841  * wp.media.controller.GalleryEdit
842  *
843  * A state for editing a gallery's images and settings.
844  *
845  * @class
846  * @augments wp.media.controller.Library
847  * @augments wp.media.controller.State
848  * @augments Backbone.Model
849  *
850  * @param {object}                     [attributes]                       The attributes hash passed to the state.
851  * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
852  * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
853  * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
854  *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
855  * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
856  * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
857  * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
858  * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
859  * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
860  * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
861  * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
862  * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
863  * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
864  * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
865  * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
866  * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
867  * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
868  *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
869  * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
870  *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
871  */
872 var Library = wp.media.controller.Library,
873         l10n = wp.media.view.l10n,
874         GalleryEdit;
875
876 GalleryEdit = Library.extend({
877         defaults: {
878                 id:               'gallery-edit',
879                 title:            l10n.editGalleryTitle,
880                 multiple:         false,
881                 searchable:       false,
882                 sortable:         true,
883                 date:             false,
884                 display:          false,
885                 content:          'browse',
886                 toolbar:          'gallery-edit',
887                 describe:         true,
888                 displaySettings:  true,
889                 dragInfo:         true,
890                 idealColumnWidth: 170,
891                 editing:          false,
892                 priority:         60,
893                 syncSelection:    false
894         },
895
896         /**
897          * @since 3.5.0
898          */
899         initialize: function() {
900                 // If we haven't been provided a `library`, create a `Selection`.
901                 if ( ! this.get('library') ) {
902                         this.set( 'library', new wp.media.model.Selection() );
903                 }
904
905                 // The single `Attachment` view to be used in the `Attachments` view.
906                 if ( ! this.get('AttachmentView') ) {
907                         this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
908                 }
909
910                 Library.prototype.initialize.apply( this, arguments );
911         },
912
913         /**
914          * @since 3.5.0
915          */
916         activate: function() {
917                 var library = this.get('library');
918
919                 // Limit the library to images only.
920                 library.props.set( 'type', 'image' );
921
922                 // Watch for uploaded attachments.
923                 this.get('library').observe( wp.Uploader.queue );
924
925                 this.frame.on( 'content:render:browse', this.gallerySettings, this );
926
927                 Library.prototype.activate.apply( this, arguments );
928         },
929
930         /**
931          * @since 3.5.0
932          */
933         deactivate: function() {
934                 // Stop watching for uploaded attachments.
935                 this.get('library').unobserve( wp.Uploader.queue );
936
937                 this.frame.off( 'content:render:browse', this.gallerySettings, this );
938
939                 Library.prototype.deactivate.apply( this, arguments );
940         },
941
942         /**
943          * @since 3.5.0
944          *
945          * @param browser
946          */
947         gallerySettings: function( browser ) {
948                 if ( ! this.get('displaySettings') ) {
949                         return;
950                 }
951
952                 var library = this.get('library');
953
954                 if ( ! library || ! browser ) {
955                         return;
956                 }
957
958                 library.gallery = library.gallery || new Backbone.Model();
959
960                 browser.sidebar.set({
961                         gallery: new wp.media.view.Settings.Gallery({
962                                 controller: this,
963                                 model:      library.gallery,
964                                 priority:   40
965                         })
966                 });
967
968                 browser.toolbar.set( 'reverse', {
969                         text:     l10n.reverseOrder,
970                         priority: 80,
971
972                         click: function() {
973                                 library.reset( library.toArray().reverse() );
974                         }
975                 });
976         }
977 });
978
979 module.exports = GalleryEdit;
980
981 },{}],10:[function(require,module,exports){
982 /**
983  * wp.media.controller.ImageDetails
984  *
985  * A state for editing the attachment display settings of an image that's been
986  * inserted into the editor.
987  *
988  * @class
989  * @augments wp.media.controller.State
990  * @augments Backbone.Model
991  *
992  * @param {object}                    [attributes]                       The attributes hash passed to the state.
993  * @param {string}                    [attributes.id=image-details]      Unique identifier.
994  * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
995  * @param {wp.media.model.Attachment} attributes.image                   The image's model.
996  * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
997  * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
998  * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
999  * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
1000  * @param {boolean}                   [attributes.editing=false]         Unused.
1001  * @param {int}                       [attributes.priority=60]           Unused.
1002  *
1003  * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
1004  *       however this may not do anything.
1005  */
1006 var State = wp.media.controller.State,
1007         Library = wp.media.controller.Library,
1008         l10n = wp.media.view.l10n,
1009         ImageDetails;
1010
1011 ImageDetails = State.extend({
1012         defaults: _.defaults({
1013                 id:       'image-details',
1014                 title:    l10n.imageDetailsTitle,
1015                 content:  'image-details',
1016                 menu:     false,
1017                 router:   false,
1018                 toolbar:  'image-details',
1019                 editing:  false,
1020                 priority: 60
1021         }, Library.prototype.defaults ),
1022
1023         /**
1024          * @since 3.9.0
1025          *
1026          * @param options Attributes
1027          */
1028         initialize: function( options ) {
1029                 this.image = options.image;
1030                 State.prototype.initialize.apply( this, arguments );
1031         },
1032
1033         /**
1034          * @since 3.9.0
1035          */
1036         activate: function() {
1037                 this.frame.modal.$el.addClass('image-details');
1038         }
1039 });
1040
1041 module.exports = ImageDetails;
1042
1043 },{}],11:[function(require,module,exports){
1044 /**
1045  * wp.media.controller.Library
1046  *
1047  * A state for choosing an attachment or group of attachments from the media library.
1048  *
1049  * @class
1050  * @augments wp.media.controller.State
1051  * @augments Backbone.Model
1052  * @mixes media.selectionSync
1053  *
1054  * @param {object}                          [attributes]                         The attributes hash passed to the state.
1055  * @param {string}                          [attributes.id=library]              Unique identifier.
1056  * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
1057  * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
1058  *                                                                               If one is not supplied, a collection of all attachments will be created.
1059  * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
1060  *                                                                               If the 'selection' attribute is a plain JS object,
1061  *                                                                               a Selection will be created using its values as the selection instance's `props` model.
1062  *                                                                               Otherwise, it will copy the library's `props` model.
1063  * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
1064  * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
1065  *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
1066  * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
1067  * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
1068  * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
1069  * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
1070  * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
1071  *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
1072  * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1073  * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1074  * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1075  * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1076  * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1077  */
1078 var l10n = wp.media.view.l10n,
1079         getUserSetting = window.getUserSetting,
1080         setUserSetting = window.setUserSetting,
1081         Library;
1082
1083 Library = wp.media.controller.State.extend({
1084         defaults: {
1085                 id:                 'library',
1086                 title:              l10n.mediaLibraryTitle,
1087                 multiple:           false,
1088                 content:            'upload',
1089                 menu:               'default',
1090                 router:             'browse',
1091                 toolbar:            'select',
1092                 searchable:         true,
1093                 filterable:         false,
1094                 sortable:           true,
1095                 autoSelect:         true,
1096                 describe:           false,
1097                 contentUserSetting: true,
1098                 syncSelection:      true
1099         },
1100
1101         /**
1102          * If a library isn't provided, query all media items.
1103          * If a selection instance isn't provided, create one.
1104          *
1105          * @since 3.5.0
1106          */
1107         initialize: function() {
1108                 var selection = this.get('selection'),
1109                         props;
1110
1111                 if ( ! this.get('library') ) {
1112                         this.set( 'library', wp.media.query() );
1113                 }
1114
1115                 if ( ! ( selection instanceof wp.media.model.Selection ) ) {
1116                         props = selection;
1117
1118                         if ( ! props ) {
1119                                 props = this.get('library').props.toJSON();
1120                                 props = _.omit( props, 'orderby', 'query' );
1121                         }
1122
1123                         this.set( 'selection', new wp.media.model.Selection( null, {
1124                                 multiple: this.get('multiple'),
1125                                 props: props
1126                         }) );
1127                 }
1128
1129                 this.resetDisplays();
1130         },
1131
1132         /**
1133          * @since 3.5.0
1134          */
1135         activate: function() {
1136                 this.syncSelection();
1137
1138                 wp.Uploader.queue.on( 'add', this.uploading, this );
1139
1140                 this.get('selection').on( 'add remove reset', this.refreshContent, this );
1141
1142                 if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
1143                         this.frame.on( 'content:activate', this.saveContentMode, this );
1144                         this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
1145                 }
1146         },
1147
1148         /**
1149          * @since 3.5.0
1150          */
1151         deactivate: function() {
1152                 this.recordSelection();
1153
1154                 this.frame.off( 'content:activate', this.saveContentMode, this );
1155
1156                 // Unbind all event handlers that use this state as the context
1157                 // from the selection.
1158                 this.get('selection').off( null, null, this );
1159
1160                 wp.Uploader.queue.off( null, null, this );
1161         },
1162
1163         /**
1164          * Reset the library to its initial state.
1165          *
1166          * @since 3.5.0
1167          */
1168         reset: function() {
1169                 this.get('selection').reset();
1170                 this.resetDisplays();
1171                 this.refreshContent();
1172         },
1173
1174         /**
1175          * Reset the attachment display settings defaults to the site options.
1176          *
1177          * If site options don't define them, fall back to a persistent user setting.
1178          *
1179          * @since 3.5.0
1180          */
1181         resetDisplays: function() {
1182                 var defaultProps = wp.media.view.settings.defaultProps;
1183                 this._displays = [];
1184                 this._defaultDisplaySettings = {
1185                         align: getUserSetting( 'align', defaultProps.align ) || 'none',
1186                         size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
1187                         link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
1188                 };
1189         },
1190
1191         /**
1192          * Create a model to represent display settings (alignment, etc.) for an attachment.
1193          *
1194          * @since 3.5.0
1195          *
1196          * @param {wp.media.model.Attachment} attachment
1197          * @returns {Backbone.Model}
1198          */
1199         display: function( attachment ) {
1200                 var displays = this._displays;
1201
1202                 if ( ! displays[ attachment.cid ] ) {
1203                         displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
1204                 }
1205                 return displays[ attachment.cid ];
1206         },
1207
1208         /**
1209          * Given an attachment, create attachment display settings properties.
1210          *
1211          * @since 3.6.0
1212          *
1213          * @param {wp.media.model.Attachment} attachment
1214          * @returns {Object}
1215          */
1216         defaultDisplaySettings: function( attachment ) {
1217                 var settings = this._defaultDisplaySettings;
1218                 if ( settings.canEmbed = this.canEmbed( attachment ) ) {
1219                         settings.link = 'embed';
1220                 }
1221                 return settings;
1222         },
1223
1224         /**
1225          * Whether an attachment can be embedded (audio or video).
1226          *
1227          * @since 3.6.0
1228          *
1229          * @param {wp.media.model.Attachment} attachment
1230          * @returns {Boolean}
1231          */
1232         canEmbed: function( attachment ) {
1233                 // If uploading, we know the filename but not the mime type.
1234                 if ( ! attachment.get('uploading') ) {
1235                         var type = attachment.get('type');
1236                         if ( type !== 'audio' && type !== 'video' ) {
1237                                 return false;
1238                         }
1239                 }
1240
1241                 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
1242         },
1243
1244
1245         /**
1246          * If the state is active, no items are selected, and the current
1247          * content mode is not an option in the state's router (provided
1248          * the state has a router), reset the content mode to the default.
1249          *
1250          * @since 3.5.0
1251          */
1252         refreshContent: function() {
1253                 var selection = this.get('selection'),
1254                         frame = this.frame,
1255                         router = frame.router.get(),
1256                         mode = frame.content.mode();
1257
1258                 if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
1259                         this.frame.content.render( this.get('content') );
1260                 }
1261         },
1262
1263         /**
1264          * Callback handler when an attachment is uploaded.
1265          *
1266          * Switch to the Media Library if uploaded from the 'Upload Files' tab.
1267          *
1268          * Adds any uploading attachments to the selection.
1269          *
1270          * If the state only supports one attachment to be selected and multiple
1271          * attachments are uploaded, the last attachment in the upload queue will
1272          * be selected.
1273          *
1274          * @since 3.5.0
1275          *
1276          * @param {wp.media.model.Attachment} attachment
1277          */
1278         uploading: function( attachment ) {
1279                 var content = this.frame.content;
1280
1281                 if ( 'upload' === content.mode() ) {
1282                         this.frame.content.mode('browse');
1283                 }
1284
1285                 if ( this.get( 'autoSelect' ) ) {
1286                         this.get('selection').add( attachment );
1287                         this.frame.trigger( 'library:selection:add' );
1288                 }
1289         },
1290
1291         /**
1292          * Persist the mode of the content region as a user setting.
1293          *
1294          * @since 3.5.0
1295          */
1296         saveContentMode: function() {
1297                 if ( 'browse' !== this.get('router') ) {
1298                         return;
1299                 }
1300
1301                 var mode = this.frame.content.mode(),
1302                         view = this.frame.router.get();
1303
1304                 if ( view && view.get( mode ) ) {
1305                         setUserSetting( 'libraryContent', mode );
1306                 }
1307         }
1308 });
1309
1310 // Make selectionSync available on any Media Library state.
1311 _.extend( Library.prototype, wp.media.selectionSync );
1312
1313 module.exports = Library;
1314
1315 },{}],12:[function(require,module,exports){
1316 /**
1317  * wp.media.controller.MediaLibrary
1318  *
1319  * @class
1320  * @augments wp.media.controller.Library
1321  * @augments wp.media.controller.State
1322  * @augments Backbone.Model
1323  */
1324 var Library = wp.media.controller.Library,
1325         MediaLibrary;
1326
1327 MediaLibrary = Library.extend({
1328         defaults: _.defaults({
1329                 // Attachments browser defaults. @see media.view.AttachmentsBrowser
1330                 filterable:      'uploaded',
1331
1332                 displaySettings: false,
1333                 priority:        80,
1334                 syncSelection:   false
1335         }, Library.prototype.defaults ),
1336
1337         /**
1338          * @since 3.9.0
1339          *
1340          * @param options
1341          */
1342         initialize: function( options ) {
1343                 this.media = options.media;
1344                 this.type = options.type;
1345                 this.set( 'library', wp.media.query({ type: this.type }) );
1346
1347                 Library.prototype.initialize.apply( this, arguments );
1348         },
1349
1350         /**
1351          * @since 3.9.0
1352          */
1353         activate: function() {
1354                 // @todo this should use this.frame.
1355                 if ( wp.media.frame.lastMime ) {
1356                         this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
1357                         delete wp.media.frame.lastMime;
1358                 }
1359                 Library.prototype.activate.apply( this, arguments );
1360         }
1361 });
1362
1363 module.exports = MediaLibrary;
1364
1365 },{}],13:[function(require,module,exports){
1366 /**
1367  * wp.media.controller.Region
1368  *
1369  * A region is a persistent application layout area.
1370  *
1371  * A region assumes one mode at any time, and can be switched to another.
1372  *
1373  * When mode changes, events are triggered on the region's parent view.
1374  * The parent view will listen to specific events and fill the region with an
1375  * appropriate view depending on mode. For example, a frame listens for the
1376  * 'browse' mode t be activated on the 'content' view and then fills the region
1377  * with an AttachmentsBrowser view.
1378  *
1379  * @class
1380  *
1381  * @param {object}        options          Options hash for the region.
1382  * @param {string}        options.id       Unique identifier for the region.
1383  * @param {Backbone.View} options.view     A parent view the region exists within.
1384  * @param {string}        options.selector jQuery selector for the region within the parent view.
1385  */
1386 var Region = function( options ) {
1387         _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
1388 };
1389
1390 // Use Backbone's self-propagating `extend` inheritance method.
1391 Region.extend = Backbone.Model.extend;
1392
1393 _.extend( Region.prototype, {
1394         /**
1395          * Activate a mode.
1396          *
1397          * @since 3.5.0
1398          *
1399          * @param {string} mode
1400          *
1401          * @fires this.view#{this.id}:activate:{this._mode}
1402          * @fires this.view#{this.id}:activate
1403          * @fires this.view#{this.id}:deactivate:{this._mode}
1404          * @fires this.view#{this.id}:deactivate
1405          *
1406          * @returns {wp.media.controller.Region} Returns itself to allow chaining.
1407          */
1408         mode: function( mode ) {
1409                 if ( ! mode ) {
1410                         return this._mode;
1411                 }
1412                 // Bail if we're trying to change to the current mode.
1413                 if ( mode === this._mode ) {
1414                         return this;
1415                 }
1416
1417                 /**
1418                  * Region mode deactivation event.
1419                  *
1420                  * @event this.view#{this.id}:deactivate:{this._mode}
1421                  * @event this.view#{this.id}:deactivate
1422                  */
1423                 this.trigger('deactivate');
1424
1425                 this._mode = mode;
1426                 this.render( mode );
1427
1428                 /**
1429                  * Region mode activation event.
1430                  *
1431                  * @event this.view#{this.id}:activate:{this._mode}
1432                  * @event this.view#{this.id}:activate
1433                  */
1434                 this.trigger('activate');
1435                 return this;
1436         },
1437         /**
1438          * Render a mode.
1439          *
1440          * @since 3.5.0
1441          *
1442          * @param {string} mode
1443          *
1444          * @fires this.view#{this.id}:create:{this._mode}
1445          * @fires this.view#{this.id}:create
1446          * @fires this.view#{this.id}:render:{this._mode}
1447          * @fires this.view#{this.id}:render
1448          *
1449          * @returns {wp.media.controller.Region} Returns itself to allow chaining
1450          */
1451         render: function( mode ) {
1452                 // If the mode isn't active, activate it.
1453                 if ( mode && mode !== this._mode ) {
1454                         return this.mode( mode );
1455                 }
1456
1457                 var set = { view: null },
1458                         view;
1459
1460                 /**
1461                  * Create region view event.
1462                  *
1463                  * Region view creation takes place in an event callback on the frame.
1464                  *
1465                  * @event this.view#{this.id}:create:{this._mode}
1466                  * @event this.view#{this.id}:create
1467                  */
1468                 this.trigger( 'create', set );
1469                 view = set.view;
1470
1471                 /**
1472                  * Render region view event.
1473                  *
1474                  * Region view creation takes place in an event callback on the frame.
1475                  *
1476                  * @event this.view#{this.id}:create:{this._mode}
1477                  * @event this.view#{this.id}:create
1478                  */
1479                 this.trigger( 'render', view );
1480                 if ( view ) {
1481                         this.set( view );
1482                 }
1483                 return this;
1484         },
1485
1486         /**
1487          * Get the region's view.
1488          *
1489          * @since 3.5.0
1490          *
1491          * @returns {wp.media.View}
1492          */
1493         get: function() {
1494                 return this.view.views.first( this.selector );
1495         },
1496
1497         /**
1498          * Set the region's view as a subview of the frame.
1499          *
1500          * @since 3.5.0
1501          *
1502          * @param {Array|Object} views
1503          * @param {Object} [options={}]
1504          * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
1505          */
1506         set: function( views, options ) {
1507                 if ( options ) {
1508                         options.add = false;
1509                 }
1510                 return this.view.views.set( this.selector, views, options );
1511         },
1512
1513         /**
1514          * Trigger regional view events on the frame.
1515          *
1516          * @since 3.5.0
1517          *
1518          * @param {string} event
1519          * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
1520          */
1521         trigger: function( event ) {
1522                 var base, args;
1523
1524                 if ( ! this._mode ) {
1525                         return;
1526                 }
1527
1528                 args = _.toArray( arguments );
1529                 base = this.id + ':' + event;
1530
1531                 // Trigger `{this.id}:{event}:{this._mode}` event on the frame.
1532                 args[0] = base + ':' + this._mode;
1533                 this.view.trigger.apply( this.view, args );
1534
1535                 // Trigger `{this.id}:{event}` event on the frame.
1536                 args[0] = base;
1537                 this.view.trigger.apply( this.view, args );
1538                 return this;
1539         }
1540 });
1541
1542 module.exports = Region;
1543
1544 },{}],14:[function(require,module,exports){
1545 /**
1546  * wp.media.controller.ReplaceImage
1547  *
1548  * A state for replacing an image.
1549  *
1550  * @class
1551  * @augments wp.media.controller.Library
1552  * @augments wp.media.controller.State
1553  * @augments Backbone.Model
1554  *
1555  * @param {object}                     [attributes]                         The attributes hash passed to the state.
1556  * @param {string}                     [attributes.id=replace-image]        Unique identifier.
1557  * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
1558  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
1559  *                                                                          If one is not supplied, a collection of all images will be created.
1560  * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
1561  * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
1562  *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
1563  * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
1564  * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
1565  * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
1566  * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
1567  * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
1568  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
1569  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
1570  * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1571  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1572  * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1573  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1574  * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1575  */
1576 var Library = wp.media.controller.Library,
1577         l10n = wp.media.view.l10n,
1578         ReplaceImage;
1579
1580 ReplaceImage = Library.extend({
1581         defaults: _.defaults({
1582                 id:            'replace-image',
1583                 title:         l10n.replaceImageTitle,
1584                 multiple:      false,
1585                 filterable:    'uploaded',
1586                 toolbar:       'replace',
1587                 menu:          false,
1588                 priority:      60,
1589                 syncSelection: true
1590         }, Library.prototype.defaults ),
1591
1592         /**
1593          * @since 3.9.0
1594          *
1595          * @param options
1596          */
1597         initialize: function( options ) {
1598                 var library, comparator;
1599
1600                 this.image = options.image;
1601                 // If we haven't been provided a `library`, create a `Selection`.
1602                 if ( ! this.get('library') ) {
1603                         this.set( 'library', wp.media.query({ type: 'image' }) );
1604                 }
1605
1606                 Library.prototype.initialize.apply( this, arguments );
1607
1608                 library    = this.get('library');
1609                 comparator = library.comparator;
1610
1611                 // Overload the library's comparator to push items that are not in
1612                 // the mirrored query to the front of the aggregate collection.
1613                 library.comparator = function( a, b ) {
1614                         var aInQuery = !! this.mirroring.get( a.cid ),
1615                                 bInQuery = !! this.mirroring.get( b.cid );
1616
1617                         if ( ! aInQuery && bInQuery ) {
1618                                 return -1;
1619                         } else if ( aInQuery && ! bInQuery ) {
1620                                 return 1;
1621                         } else {
1622                                 return comparator.apply( this, arguments );
1623                         }
1624                 };
1625
1626                 // Add all items in the selection to the library, so any featured
1627                 // images that are not initially loaded still appear.
1628                 library.observe( this.get('selection') );
1629         },
1630
1631         /**
1632          * @since 3.9.0
1633          */
1634         activate: function() {
1635                 this.updateSelection();
1636                 Library.prototype.activate.apply( this, arguments );
1637         },
1638
1639         /**
1640          * @since 3.9.0
1641          */
1642         updateSelection: function() {
1643                 var selection = this.get('selection'),
1644                         attachment = this.image.attachment;
1645
1646                 selection.reset( attachment ? [ attachment ] : [] );
1647         }
1648 });
1649
1650 module.exports = ReplaceImage;
1651
1652 },{}],15:[function(require,module,exports){
1653 /**
1654  * wp.media.controller.SiteIconCropper
1655  *
1656  * A state for cropping a Site Icon.
1657  *
1658  * @class
1659  * @augments wp.media.controller.Cropper
1660  * @augments wp.media.controller.State
1661  * @augments Backbone.Model
1662  */
1663 var Controller = wp.media.controller,
1664         SiteIconCropper;
1665
1666 SiteIconCropper = Controller.Cropper.extend({
1667         activate: function() {
1668                 this.frame.on( 'content:create:crop', this.createCropContent, this );
1669                 this.frame.on( 'close', this.removeCropper, this );
1670                 this.set('selection', new Backbone.Collection(this.frame._selection.single));
1671         },
1672
1673         createCropContent: function() {
1674                 this.cropperView = new wp.media.view.SiteIconCropper({
1675                         controller: this,
1676                         attachment: this.get('selection').first()
1677                 });
1678                 this.cropperView.on('image-loaded', this.createCropToolbar, this);
1679                 this.frame.content.set(this.cropperView);
1680
1681         },
1682
1683         doCrop: function( attachment ) {
1684                 var cropDetails = attachment.get( 'cropDetails' ),
1685                         control = this.get( 'control' );
1686
1687                 cropDetails.dst_width  = control.params.width;
1688                 cropDetails.dst_height = control.params.height;
1689
1690                 return wp.ajax.post( 'crop-image', {
1691                         nonce: attachment.get( 'nonces' ).edit,
1692                         id: attachment.get( 'id' ),
1693                         context: 'site-icon',
1694                         cropDetails: cropDetails
1695                 } );
1696         }
1697 });
1698
1699 module.exports = SiteIconCropper;
1700
1701 },{}],16:[function(require,module,exports){
1702 /**
1703  * wp.media.controller.StateMachine
1704  *
1705  * A state machine keeps track of state. It is in one state at a time,
1706  * and can change from one state to another.
1707  *
1708  * States are stored as models in a Backbone collection.
1709  *
1710  * @since 3.5.0
1711  *
1712  * @class
1713  * @augments Backbone.Model
1714  * @mixin
1715  * @mixes Backbone.Events
1716  *
1717  * @param {Array} states
1718  */
1719 var StateMachine = function( states ) {
1720         // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
1721         this.states = new Backbone.Collection( states );
1722 };
1723
1724 // Use Backbone's self-propagating `extend` inheritance method.
1725 StateMachine.extend = Backbone.Model.extend;
1726
1727 _.extend( StateMachine.prototype, Backbone.Events, {
1728         /**
1729          * Fetch a state.
1730          *
1731          * If no `id` is provided, returns the active state.
1732          *
1733          * Implicitly creates states.
1734          *
1735          * Ensure that the `states` collection exists so the `StateMachine`
1736          *   can be used as a mixin.
1737          *
1738          * @since 3.5.0
1739          *
1740          * @param {string} id
1741          * @returns {wp.media.controller.State} Returns a State model
1742          *   from the StateMachine collection
1743          */
1744         state: function( id ) {
1745                 this.states = this.states || new Backbone.Collection();
1746
1747                 // Default to the active state.
1748                 id = id || this._state;
1749
1750                 if ( id && ! this.states.get( id ) ) {
1751                         this.states.add({ id: id });
1752                 }
1753                 return this.states.get( id );
1754         },
1755
1756         /**
1757          * Sets the active state.
1758          *
1759          * Bail if we're trying to select the current state, if we haven't
1760          * created the `states` collection, or are trying to select a state
1761          * that does not exist.
1762          *
1763          * @since 3.5.0
1764          *
1765          * @param {string} id
1766          *
1767          * @fires wp.media.controller.State#deactivate
1768          * @fires wp.media.controller.State#activate
1769          *
1770          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
1771          */
1772         setState: function( id ) {
1773                 var previous = this.state();
1774
1775                 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
1776                         return this;
1777                 }
1778
1779                 if ( previous ) {
1780                         previous.trigger('deactivate');
1781                         this._lastState = previous.id;
1782                 }
1783
1784                 this._state = id;
1785                 this.state().trigger('activate');
1786
1787                 return this;
1788         },
1789
1790         /**
1791          * Returns the previous active state.
1792          *
1793          * Call the `state()` method with no parameters to retrieve the current
1794          * active state.
1795          *
1796          * @since 3.5.0
1797          *
1798          * @returns {wp.media.controller.State} Returns a State model
1799          *    from the StateMachine collection
1800          */
1801         lastState: function() {
1802                 if ( this._lastState ) {
1803                         return this.state( this._lastState );
1804                 }
1805         }
1806 });
1807
1808 // Map all event binding and triggering on a StateMachine to its `states` collection.
1809 _.each([ 'on', 'off', 'trigger' ], function( method ) {
1810         /**
1811          * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
1812          */
1813         StateMachine.prototype[ method ] = function() {
1814                 // Ensure that the `states` collection exists so the `StateMachine`
1815                 // can be used as a mixin.
1816                 this.states = this.states || new Backbone.Collection();
1817                 // Forward the method to the `states` collection.
1818                 this.states[ method ].apply( this.states, arguments );
1819                 return this;
1820         };
1821 });
1822
1823 module.exports = StateMachine;
1824
1825 },{}],17:[function(require,module,exports){
1826 /**
1827  * wp.media.controller.State
1828  *
1829  * A state is a step in a workflow that when set will trigger the controllers
1830  * for the regions to be updated as specified in the frame.
1831  *
1832  * A state has an event-driven lifecycle:
1833  *
1834  *     'ready'      triggers when a state is added to a state machine's collection.
1835  *     'activate'   triggers when a state is activated by a state machine.
1836  *     'deactivate' triggers when a state is deactivated by a state machine.
1837  *     'reset'      is not triggered automatically. It should be invoked by the
1838  *                  proper controller to reset the state to its default.
1839  *
1840  * @class
1841  * @augments Backbone.Model
1842  */
1843 var State = Backbone.Model.extend({
1844         /**
1845          * Constructor.
1846          *
1847          * @since 3.5.0
1848          */
1849         constructor: function() {
1850                 this.on( 'activate', this._preActivate, this );
1851                 this.on( 'activate', this.activate, this );
1852                 this.on( 'activate', this._postActivate, this );
1853                 this.on( 'deactivate', this._deactivate, this );
1854                 this.on( 'deactivate', this.deactivate, this );
1855                 this.on( 'reset', this.reset, this );
1856                 this.on( 'ready', this._ready, this );
1857                 this.on( 'ready', this.ready, this );
1858                 /**
1859                  * Call parent constructor with passed arguments
1860                  */
1861                 Backbone.Model.apply( this, arguments );
1862                 this.on( 'change:menu', this._updateMenu, this );
1863         },
1864         /**
1865          * Ready event callback.
1866          *
1867          * @abstract
1868          * @since 3.5.0
1869          */
1870         ready: function() {},
1871
1872         /**
1873          * Activate event callback.
1874          *
1875          * @abstract
1876          * @since 3.5.0
1877          */
1878         activate: function() {},
1879
1880         /**
1881          * Deactivate event callback.
1882          *
1883          * @abstract
1884          * @since 3.5.0
1885          */
1886         deactivate: function() {},
1887
1888         /**
1889          * Reset event callback.
1890          *
1891          * @abstract
1892          * @since 3.5.0
1893          */
1894         reset: function() {},
1895
1896         /**
1897          * @access private
1898          * @since 3.5.0
1899          */
1900         _ready: function() {
1901                 this._updateMenu();
1902         },
1903
1904         /**
1905          * @access private
1906          * @since 3.5.0
1907         */
1908         _preActivate: function() {
1909                 this.active = true;
1910         },
1911
1912         /**
1913          * @access private
1914          * @since 3.5.0
1915          */
1916         _postActivate: function() {
1917                 this.on( 'change:menu', this._menu, this );
1918                 this.on( 'change:titleMode', this._title, this );
1919                 this.on( 'change:content', this._content, this );
1920                 this.on( 'change:toolbar', this._toolbar, this );
1921
1922                 this.frame.on( 'title:render:default', this._renderTitle, this );
1923
1924                 this._title();
1925                 this._menu();
1926                 this._toolbar();
1927                 this._content();
1928                 this._router();
1929         },
1930
1931         /**
1932          * @access private
1933          * @since 3.5.0
1934          */
1935         _deactivate: function() {
1936                 this.active = false;
1937
1938                 this.frame.off( 'title:render:default', this._renderTitle, this );
1939
1940                 this.off( 'change:menu', this._menu, this );
1941                 this.off( 'change:titleMode', this._title, this );
1942                 this.off( 'change:content', this._content, this );
1943                 this.off( 'change:toolbar', this._toolbar, this );
1944         },
1945
1946         /**
1947          * @access private
1948          * @since 3.5.0
1949          */
1950         _title: function() {
1951                 this.frame.title.render( this.get('titleMode') || 'default' );
1952         },
1953
1954         /**
1955          * @access private
1956          * @since 3.5.0
1957          */
1958         _renderTitle: function( view ) {
1959                 view.$el.text( this.get('title') || '' );
1960         },
1961
1962         /**
1963          * @access private
1964          * @since 3.5.0
1965          */
1966         _router: function() {
1967                 var router = this.frame.router,
1968                         mode = this.get('router'),
1969                         view;
1970
1971                 this.frame.$el.toggleClass( 'hide-router', ! mode );
1972                 if ( ! mode ) {
1973                         return;
1974                 }
1975
1976                 this.frame.router.render( mode );
1977
1978                 view = router.get();
1979                 if ( view && view.select ) {
1980                         view.select( this.frame.content.mode() );
1981                 }
1982         },
1983
1984         /**
1985          * @access private
1986          * @since 3.5.0
1987          */
1988         _menu: function() {
1989                 var menu = this.frame.menu,
1990                         mode = this.get('menu'),
1991                         view;
1992
1993                 this.frame.$el.toggleClass( 'hide-menu', ! mode );
1994                 if ( ! mode ) {
1995                         return;
1996                 }
1997
1998                 menu.mode( mode );
1999
2000                 view = menu.get();
2001                 if ( view && view.select ) {
2002                         view.select( this.id );
2003                 }
2004         },
2005
2006         /**
2007          * @access private
2008          * @since 3.5.0
2009          */
2010         _updateMenu: function() {
2011                 var previous = this.previous('menu'),
2012                         menu = this.get('menu');
2013
2014                 if ( previous ) {
2015                         this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
2016                 }
2017
2018                 if ( menu ) {
2019                         this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
2020                 }
2021         },
2022
2023         /**
2024          * Create a view in the media menu for the state.
2025          *
2026          * @access private
2027          * @since 3.5.0
2028          *
2029          * @param {media.view.Menu} view The menu view.
2030          */
2031         _renderMenu: function( view ) {
2032                 var menuItem = this.get('menuItem'),
2033                         title = this.get('title'),
2034                         priority = this.get('priority');
2035
2036                 if ( ! menuItem && title ) {
2037                         menuItem = { text: title };
2038
2039                         if ( priority ) {
2040                                 menuItem.priority = priority;
2041                         }
2042                 }
2043
2044                 if ( ! menuItem ) {
2045                         return;
2046                 }
2047
2048                 view.set( this.id, menuItem );
2049         }
2050 });
2051
2052 _.each(['toolbar','content'], function( region ) {
2053         /**
2054          * @access private
2055          */
2056         State.prototype[ '_' + region ] = function() {
2057                 var mode = this.get( region );
2058                 if ( mode ) {
2059                         this.frame[ region ].render( mode );
2060                 }
2061         };
2062 });
2063
2064 module.exports = State;
2065
2066 },{}],18:[function(require,module,exports){
2067 /**
2068  * wp.media.selectionSync
2069  *
2070  * Sync an attachments selection in a state with another state.
2071  *
2072  * Allows for selecting multiple images in the Insert Media workflow, and then
2073  * switching to the Insert Gallery workflow while preserving the attachments selection.
2074  *
2075  * @mixin
2076  */
2077 var selectionSync = {
2078         /**
2079          * @since 3.5.0
2080          */
2081         syncSelection: function() {
2082                 var selection = this.get('selection'),
2083                         manager = this.frame._selection;
2084
2085                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2086                         return;
2087                 }
2088
2089                 // If the selection supports multiple items, validate the stored
2090                 // attachments based on the new selection's conditions. Record
2091                 // the attachments that are not included; we'll maintain a
2092                 // reference to those. Other attachments are considered in flux.
2093                 if ( selection.multiple ) {
2094                         selection.reset( [], { silent: true });
2095                         selection.validateAll( manager.attachments );
2096                         manager.difference = _.difference( manager.attachments.models, selection.models );
2097                 }
2098
2099                 // Sync the selection's single item with the master.
2100                 selection.single( manager.single );
2101         },
2102
2103         /**
2104          * Record the currently active attachments, which is a combination
2105          * of the selection's attachments and the set of selected
2106          * attachments that this specific selection considered invalid.
2107          * Reset the difference and record the single attachment.
2108          *
2109          * @since 3.5.0
2110          */
2111         recordSelection: function() {
2112                 var selection = this.get('selection'),
2113                         manager = this.frame._selection;
2114
2115                 if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2116                         return;
2117                 }
2118
2119                 if ( selection.multiple ) {
2120                         manager.attachments.reset( selection.toArray().concat( manager.difference ) );
2121                         manager.difference = [];
2122                 } else {
2123                         manager.attachments.add( selection.toArray() );
2124                 }
2125
2126                 manager.single = selection._single;
2127         }
2128 };
2129
2130 module.exports = selectionSync;
2131
2132 },{}],19:[function(require,module,exports){
2133 var media = wp.media,
2134         $ = jQuery,
2135         l10n;
2136
2137 media.isTouchDevice = ( 'ontouchend' in document );
2138
2139 // Link any localized strings.
2140 l10n = media.view.l10n = window._wpMediaViewsL10n || {};
2141
2142 // Link any settings.
2143 media.view.settings = l10n.settings || {};
2144 delete l10n.settings;
2145
2146 // Copy the `post` setting over to the model settings.
2147 media.model.settings.post = media.view.settings.post;
2148
2149 // Check if the browser supports CSS 3.0 transitions
2150 $.support.transition = (function(){
2151         var style = document.documentElement.style,
2152                 transitions = {
2153                         WebkitTransition: 'webkitTransitionEnd',
2154                         MozTransition:    'transitionend',
2155                         OTransition:      'oTransitionEnd otransitionend',
2156                         transition:       'transitionend'
2157                 }, transition;
2158
2159         transition = _.find( _.keys( transitions ), function( transition ) {
2160                 return ! _.isUndefined( style[ transition ] );
2161         });
2162
2163         return transition && {
2164                 end: transitions[ transition ]
2165         };
2166 }());
2167
2168 /**
2169  * A shared event bus used to provide events into
2170  * the media workflows that 3rd-party devs can use to hook
2171  * in.
2172  */
2173 media.events = _.extend( {}, Backbone.Events );
2174
2175 /**
2176  * Makes it easier to bind events using transitions.
2177  *
2178  * @param {string} selector
2179  * @param {Number} sensitivity
2180  * @returns {Promise}
2181  */
2182 media.transition = function( selector, sensitivity ) {
2183         var deferred = $.Deferred();
2184
2185         sensitivity = sensitivity || 2000;
2186
2187         if ( $.support.transition ) {
2188                 if ( ! (selector instanceof $) ) {
2189                         selector = $( selector );
2190                 }
2191
2192                 // Resolve the deferred when the first element finishes animating.
2193                 selector.first().one( $.support.transition.end, deferred.resolve );
2194
2195                 // Just in case the event doesn't trigger, fire a callback.
2196                 _.delay( deferred.resolve, sensitivity );
2197
2198         // Otherwise, execute on the spot.
2199         } else {
2200                 deferred.resolve();
2201         }
2202
2203         return deferred.promise();
2204 };
2205
2206 media.controller.Region = require( './controllers/region.js' );
2207 media.controller.StateMachine = require( './controllers/state-machine.js' );
2208 media.controller.State = require( './controllers/state.js' );
2209
2210 media.selectionSync = require( './utils/selection-sync.js' );
2211 media.controller.Library = require( './controllers/library.js' );
2212 media.controller.ImageDetails = require( './controllers/image-details.js' );
2213 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
2214 media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
2215 media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
2216 media.controller.CollectionAdd = require( './controllers/collection-add.js' );
2217 media.controller.FeaturedImage = require( './controllers/featured-image.js' );
2218 media.controller.ReplaceImage = require( './controllers/replace-image.js' );
2219 media.controller.EditImage = require( './controllers/edit-image.js' );
2220 media.controller.MediaLibrary = require( './controllers/media-library.js' );
2221 media.controller.Embed = require( './controllers/embed.js' );
2222 media.controller.Cropper = require( './controllers/cropper.js' );
2223 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
2224 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
2225
2226 media.View = require( './views/view.js' );
2227 media.view.Frame = require( './views/frame.js' );
2228 media.view.MediaFrame = require( './views/media-frame.js' );
2229 media.view.MediaFrame.Select = require( './views/frame/select.js' );
2230 media.view.MediaFrame.Post = require( './views/frame/post.js' );
2231 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
2232 media.view.Modal = require( './views/modal.js' );
2233 media.view.FocusManager = require( './views/focus-manager.js' );
2234 media.view.UploaderWindow = require( './views/uploader/window.js' );
2235 media.view.EditorUploader = require( './views/uploader/editor.js' );
2236 media.view.UploaderInline = require( './views/uploader/inline.js' );
2237 media.view.UploaderStatus = require( './views/uploader/status.js' );
2238 media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
2239 media.view.Toolbar = require( './views/toolbar.js' );
2240 media.view.Toolbar.Select = require( './views/toolbar/select.js' );
2241 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
2242 media.view.Button = require( './views/button.js' );
2243 media.view.ButtonGroup = require( './views/button-group.js' );
2244 media.view.PriorityList = require( './views/priority-list.js' );
2245 media.view.MenuItem = require( './views/menu-item.js' );
2246 media.view.Menu = require( './views/menu.js' );
2247 media.view.RouterItem = require( './views/router-item.js' );
2248 media.view.Router = require( './views/router.js' );
2249 media.view.Sidebar = require( './views/sidebar.js' );
2250 media.view.Attachment = require( './views/attachment.js' );
2251 media.view.Attachment.Library = require( './views/attachment/library.js' );
2252 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
2253 media.view.Attachments = require( './views/attachments.js' );
2254 media.view.Search = require( './views/search.js' );
2255 media.view.AttachmentFilters = require( './views/attachment-filters.js' );
2256 media.view.DateFilter = require( './views/attachment-filters/date.js' );
2257 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
2258 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
2259 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
2260 media.view.Selection = require( './views/selection.js' );
2261 media.view.Attachment.Selection = require( './views/attachment/selection.js' );
2262 media.view.Attachments.Selection = require( './views/attachments/selection.js' );
2263 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
2264 media.view.Settings = require( './views/settings.js' );
2265 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
2266 media.view.Settings.Gallery = require( './views/settings/gallery.js' );
2267 media.view.Settings.Playlist = require( './views/settings/playlist.js' );
2268 media.view.Attachment.Details = require( './views/attachment/details.js' );
2269 media.view.AttachmentCompat = require( './views/attachment-compat.js' );
2270 media.view.Iframe = require( './views/iframe.js' );
2271 media.view.Embed = require( './views/embed.js' );
2272 media.view.Label = require( './views/label.js' );
2273 media.view.EmbedUrl = require( './views/embed/url.js' );
2274 media.view.EmbedLink = require( './views/embed/link.js' );
2275 media.view.EmbedImage = require( './views/embed/image.js' );
2276 media.view.ImageDetails = require( './views/image-details.js' );
2277 media.view.Cropper = require( './views/cropper.js' );
2278 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
2279 media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
2280 media.view.EditImage = require( './views/edit-image.js' );
2281 media.view.Spinner = require( './views/spinner.js' );
2282
2283 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){
2284 /**
2285  * wp.media.view.AttachmentCompat
2286  *
2287  * A view to display fields added via the `attachment_fields_to_edit` filter.
2288  *
2289  * @class
2290  * @augments wp.media.View
2291  * @augments wp.Backbone.View
2292  * @augments Backbone.View
2293  */
2294 var View = wp.media.View,
2295         AttachmentCompat;
2296
2297 AttachmentCompat = View.extend({
2298         tagName:   'form',
2299         className: 'compat-item',
2300
2301         events: {
2302                 'submit':          'preventDefault',
2303                 'change input':    'save',
2304                 'change select':   'save',
2305                 'change textarea': 'save'
2306         },
2307
2308         initialize: function() {
2309                 this.listenTo( this.model, 'change:compat', this.render );
2310         },
2311         /**
2312          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
2313          */
2314         dispose: function() {
2315                 if ( this.$(':focus').length ) {
2316                         this.save();
2317                 }
2318                 /**
2319                  * call 'dispose' directly on the parent class
2320                  */
2321                 return View.prototype.dispose.apply( this, arguments );
2322         },
2323         /**
2324          * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
2325          */
2326         render: function() {
2327                 var compat = this.model.get('compat');
2328                 if ( ! compat || ! compat.item ) {
2329                         return;
2330                 }
2331
2332                 this.views.detach();
2333                 this.$el.html( compat.item );
2334                 this.views.render();
2335                 return this;
2336         },
2337         /**
2338          * @param {Object} event
2339          */
2340         preventDefault: function( event ) {
2341                 event.preventDefault();
2342         },
2343         /**
2344          * @param {Object} event
2345          */
2346         save: function( event ) {
2347                 var data = {};
2348
2349                 if ( event ) {
2350                         event.preventDefault();
2351                 }
2352
2353                 _.each( this.$el.serializeArray(), function( pair ) {
2354                         data[ pair.name ] = pair.value;
2355                 });
2356
2357                 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
2358                 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
2359         },
2360
2361         postSave: function() {
2362                 this.controller.trigger( 'attachment:compat:ready', ['ready'] );
2363         }
2364 });
2365
2366 module.exports = AttachmentCompat;
2367
2368 },{}],21:[function(require,module,exports){
2369 /**
2370  * wp.media.view.AttachmentFilters
2371  *
2372  * @class
2373  * @augments wp.media.View
2374  * @augments wp.Backbone.View
2375  * @augments Backbone.View
2376  */
2377 var $ = jQuery,
2378         AttachmentFilters;
2379
2380 AttachmentFilters = wp.media.View.extend({
2381         tagName:   'select',
2382         className: 'attachment-filters',
2383         id:        'media-attachment-filters',
2384
2385         events: {
2386                 change: 'change'
2387         },
2388
2389         keys: [],
2390
2391         initialize: function() {
2392                 this.createFilters();
2393                 _.extend( this.filters, this.options.filters );
2394
2395                 // Build `<option>` elements.
2396                 this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
2397                         return {
2398                                 el: $( '<option></option>' ).val( value ).html( filter.text )[0],
2399                                 priority: filter.priority || 50
2400                         };
2401                 }, this ).sortBy('priority').pluck('el').value() );
2402
2403                 this.listenTo( this.model, 'change', this.select );
2404                 this.select();
2405         },
2406
2407         /**
2408          * @abstract
2409          */
2410         createFilters: function() {
2411                 this.filters = {};
2412         },
2413
2414         /**
2415          * When the selected filter changes, update the Attachment Query properties to match.
2416          */
2417         change: function() {
2418                 var filter = this.filters[ this.el.value ];
2419                 if ( filter ) {
2420                         this.model.set( filter.props );
2421                 }
2422         },
2423
2424         select: function() {
2425                 var model = this.model,
2426                         value = 'all',
2427                         props = model.toJSON();
2428
2429                 _.find( this.filters, function( filter, id ) {
2430                         var equal = _.all( filter.props, function( prop, key ) {
2431                                 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
2432                         });
2433
2434                         if ( equal ) {
2435                                 return value = id;
2436                         }
2437                 });
2438
2439                 this.$el.val( value );
2440         }
2441 });
2442
2443 module.exports = AttachmentFilters;
2444
2445 },{}],22:[function(require,module,exports){
2446 /**
2447  * wp.media.view.AttachmentFilters.All
2448  *
2449  * @class
2450  * @augments wp.media.view.AttachmentFilters
2451  * @augments wp.media.View
2452  * @augments wp.Backbone.View
2453  * @augments Backbone.View
2454  */
2455 var l10n = wp.media.view.l10n,
2456         All;
2457
2458 All = wp.media.view.AttachmentFilters.extend({
2459         createFilters: function() {
2460                 var filters = {};
2461
2462                 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
2463                         filters[ key ] = {
2464                                 text: text,
2465                                 props: {
2466                                         status:  null,
2467                                         type:    key,
2468                                         uploadedTo: null,
2469                                         orderby: 'date',
2470                                         order:   'DESC'
2471                                 }
2472                         };
2473                 });
2474
2475                 filters.all = {
2476                         text:  l10n.allMediaItems,
2477                         props: {
2478                                 status:  null,
2479                                 type:    null,
2480                                 uploadedTo: null,
2481                                 orderby: 'date',
2482                                 order:   'DESC'
2483                         },
2484                         priority: 10
2485                 };
2486
2487                 if ( wp.media.view.settings.post.id ) {
2488                         filters.uploaded = {
2489                                 text:  l10n.uploadedToThisPost,
2490                                 props: {
2491                                         status:  null,
2492                                         type:    null,
2493                                         uploadedTo: wp.media.view.settings.post.id,
2494                                         orderby: 'menuOrder',
2495                                         order:   'ASC'
2496                                 },
2497                                 priority: 20
2498                         };
2499                 }
2500
2501                 filters.unattached = {
2502                         text:  l10n.unattached,
2503                         props: {
2504                                 status:     null,
2505                                 uploadedTo: 0,
2506                                 type:       null,
2507                                 orderby:    'menuOrder',
2508                                 order:      'ASC'
2509                         },
2510                         priority: 50
2511                 };
2512
2513                 if ( wp.media.view.settings.mediaTrash &&
2514                         this.controller.isModeActive( 'grid' ) ) {
2515
2516                         filters.trash = {
2517                                 text:  l10n.trash,
2518                                 props: {
2519                                         uploadedTo: null,
2520                                         status:     'trash',
2521                                         type:       null,
2522                                         orderby:    'date',
2523                                         order:      'DESC'
2524                                 },
2525                                 priority: 50
2526                         };
2527                 }
2528
2529                 this.filters = filters;
2530         }
2531 });
2532
2533 module.exports = All;
2534
2535 },{}],23:[function(require,module,exports){
2536 /**
2537  * A filter dropdown for month/dates.
2538  *
2539  * @class
2540  * @augments wp.media.view.AttachmentFilters
2541  * @augments wp.media.View
2542  * @augments wp.Backbone.View
2543  * @augments Backbone.View
2544  */
2545 var l10n = wp.media.view.l10n,
2546         DateFilter;
2547
2548 DateFilter = wp.media.view.AttachmentFilters.extend({
2549         id: 'media-attachment-date-filters',
2550
2551         createFilters: function() {
2552                 var filters = {};
2553                 _.each( wp.media.view.settings.months || {}, function( value, index ) {
2554                         filters[ index ] = {
2555                                 text: value.text,
2556                                 props: {
2557                                         year: value.year,
2558                                         monthnum: value.month
2559                                 }
2560                         };
2561                 });
2562                 filters.all = {
2563                         text:  l10n.allDates,
2564                         props: {
2565                                 monthnum: false,
2566                                 year:  false
2567                         },
2568                         priority: 10
2569                 };
2570                 this.filters = filters;
2571         }
2572 });
2573
2574 module.exports = DateFilter;
2575
2576 },{}],24:[function(require,module,exports){
2577 /**
2578  * wp.media.view.AttachmentFilters.Uploaded
2579  *
2580  * @class
2581  * @augments wp.media.view.AttachmentFilters
2582  * @augments wp.media.View
2583  * @augments wp.Backbone.View
2584  * @augments Backbone.View
2585  */
2586 var l10n = wp.media.view.l10n,
2587         Uploaded;
2588
2589 Uploaded = wp.media.view.AttachmentFilters.extend({
2590         createFilters: function() {
2591                 var type = this.model.get('type'),
2592                         types = wp.media.view.settings.mimeTypes,
2593                         text;
2594
2595                 if ( types && type ) {
2596                         text = types[ type ];
2597                 }
2598
2599                 this.filters = {
2600                         all: {
2601                                 text:  text || l10n.allMediaItems,
2602                                 props: {
2603                                         uploadedTo: null,
2604                                         orderby: 'date',
2605                                         order:   'DESC'
2606                                 },
2607                                 priority: 10
2608                         },
2609
2610                         uploaded: {
2611                                 text:  l10n.uploadedToThisPost,
2612                                 props: {
2613                                         uploadedTo: wp.media.view.settings.post.id,
2614                                         orderby: 'menuOrder',
2615                                         order:   'ASC'
2616                                 },
2617                                 priority: 20
2618                         },
2619
2620                         unattached: {
2621                                 text:  l10n.unattached,
2622                                 props: {
2623                                         uploadedTo: 0,
2624                                         orderby: 'menuOrder',
2625                                         order:   'ASC'
2626                                 },
2627                                 priority: 50
2628                         }
2629                 };
2630         }
2631 });
2632
2633 module.exports = Uploaded;
2634
2635 },{}],25:[function(require,module,exports){
2636 /**
2637  * wp.media.view.Attachment
2638  *
2639  * @class
2640  * @augments wp.media.View
2641  * @augments wp.Backbone.View
2642  * @augments Backbone.View
2643  */
2644 var View = wp.media.View,
2645         $ = jQuery,
2646         Attachment;
2647
2648 Attachment = View.extend({
2649         tagName:   'li',
2650         className: 'attachment',
2651         template:  wp.template('attachment'),
2652
2653         attributes: function() {
2654                 return {
2655                         'tabIndex':     0,
2656                         'role':         'checkbox',
2657                         'aria-label':   this.model.get( 'title' ),
2658                         'aria-checked': false,
2659                         'data-id':      this.model.get( 'id' )
2660                 };
2661         },
2662
2663         events: {
2664                 'click .js--select-attachment':   'toggleSelectionHandler',
2665                 'change [data-setting]':          'updateSetting',
2666                 'change [data-setting] input':    'updateSetting',
2667                 'change [data-setting] select':   'updateSetting',
2668                 'change [data-setting] textarea': 'updateSetting',
2669                 'click .attachment-close':        'removeFromLibrary',
2670                 'click .check':                   'checkClickHandler',
2671                 'keydown':                        'toggleSelectionHandler'
2672         },
2673
2674         buttons: {},
2675
2676         initialize: function() {
2677                 var selection = this.options.selection,
2678                         options = _.defaults( this.options, {
2679                                 rerenderOnModelChange: true
2680                         } );
2681
2682                 if ( options.rerenderOnModelChange ) {
2683                         this.listenTo( this.model, 'change', this.render );
2684                 } else {
2685                         this.listenTo( this.model, 'change:percent', this.progress );
2686                 }
2687                 this.listenTo( this.model, 'change:title', this._syncTitle );
2688                 this.listenTo( this.model, 'change:caption', this._syncCaption );
2689                 this.listenTo( this.model, 'change:artist', this._syncArtist );
2690                 this.listenTo( this.model, 'change:album', this._syncAlbum );
2691
2692                 // Update the selection.
2693                 this.listenTo( this.model, 'add', this.select );
2694                 this.listenTo( this.model, 'remove', this.deselect );
2695                 if ( selection ) {
2696                         selection.on( 'reset', this.updateSelect, this );
2697                         // Update the model's details view.
2698                         this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
2699                         this.details( this.model, this.controller.state().get('selection') );
2700                 }
2701
2702                 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
2703         },
2704         /**
2705          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
2706          */
2707         dispose: function() {
2708                 var selection = this.options.selection;
2709
2710                 // Make sure all settings are saved before removing the view.
2711                 this.updateAll();
2712
2713                 if ( selection ) {
2714                         selection.off( null, null, this );
2715                 }
2716                 /**
2717                  * call 'dispose' directly on the parent class
2718                  */
2719                 View.prototype.dispose.apply( this, arguments );
2720                 return this;
2721         },
2722         /**
2723          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
2724          */
2725         render: function() {
2726                 var options = _.defaults( this.model.toJSON(), {
2727                                 orientation:   'landscape',
2728                                 uploading:     false,
2729                                 type:          '',
2730                                 subtype:       '',
2731                                 icon:          '',
2732                                 filename:      '',
2733                                 caption:       '',
2734                                 title:         '',
2735                                 dateFormatted: '',
2736                                 width:         '',
2737                                 height:        '',
2738                                 compat:        false,
2739                                 alt:           '',
2740                                 description:   ''
2741                         }, this.options );
2742
2743                 options.buttons  = this.buttons;
2744                 options.describe = this.controller.state().get('describe');
2745
2746                 if ( 'image' === options.type ) {
2747                         options.size = this.imageSize();
2748                 }
2749
2750                 options.can = {};
2751                 if ( options.nonces ) {
2752                         options.can.remove = !! options.nonces['delete'];
2753                         options.can.save = !! options.nonces.update;
2754                 }
2755
2756                 if ( this.controller.state().get('allowLocalEdits') ) {
2757                         options.allowLocalEdits = true;
2758                 }
2759
2760                 if ( options.uploading && ! options.percent ) {
2761                         options.percent = 0;
2762                 }
2763
2764                 this.views.detach();
2765                 this.$el.html( this.template( options ) );
2766
2767                 this.$el.toggleClass( 'uploading', options.uploading );
2768
2769                 if ( options.uploading ) {
2770                         this.$bar = this.$('.media-progress-bar div');
2771                 } else {
2772                         delete this.$bar;
2773                 }
2774
2775                 // Check if the model is selected.
2776                 this.updateSelect();
2777
2778                 // Update the save status.
2779                 this.updateSave();
2780
2781                 this.views.render();
2782
2783                 return this;
2784         },
2785
2786         progress: function() {
2787                 if ( this.$bar && this.$bar.length ) {
2788                         this.$bar.width( this.model.get('percent') + '%' );
2789                 }
2790         },
2791
2792         /**
2793          * @param {Object} event
2794          */
2795         toggleSelectionHandler: function( event ) {
2796                 var method;
2797
2798                 // Don't do anything inside inputs and on the attachment check and remove buttons.
2799                 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
2800                         return;
2801                 }
2802
2803                 // Catch arrow events
2804                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
2805                         this.controller.trigger( 'attachment:keydown:arrow', event );
2806                         return;
2807                 }
2808
2809                 // Catch enter and space events
2810                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
2811                         return;
2812                 }
2813
2814                 event.preventDefault();
2815
2816                 // In the grid view, bubble up an edit:attachment event to the controller.
2817                 if ( this.controller.isModeActive( 'grid' ) ) {
2818                         if ( this.controller.isModeActive( 'edit' ) ) {
2819                                 // Pass the current target to restore focus when closing
2820                                 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
2821                                 return;
2822                         }
2823
2824                         if ( this.controller.isModeActive( 'select' ) ) {
2825                                 method = 'toggle';
2826                         }
2827                 }
2828
2829                 if ( event.shiftKey ) {
2830                         method = 'between';
2831                 } else if ( event.ctrlKey || event.metaKey ) {
2832                         method = 'toggle';
2833                 }
2834
2835                 this.toggleSelection({
2836                         method: method
2837                 });
2838
2839                 this.controller.trigger( 'selection:toggle' );
2840         },
2841         /**
2842          * @param {Object} options
2843          */
2844         toggleSelection: function( options ) {
2845                 var collection = this.collection,
2846                         selection = this.options.selection,
2847                         model = this.model,
2848                         method = options && options.method,
2849                         single, models, singleIndex, modelIndex;
2850
2851                 if ( ! selection ) {
2852                         return;
2853                 }
2854
2855                 single = selection.single();
2856                 method = _.isUndefined( method ) ? selection.multiple : method;
2857
2858                 // If the `method` is set to `between`, select all models that
2859                 // exist between the current and the selected model.
2860                 if ( 'between' === method && single && selection.multiple ) {
2861                         // If the models are the same, short-circuit.
2862                         if ( single === model ) {
2863                                 return;
2864                         }
2865
2866                         singleIndex = collection.indexOf( single );
2867                         modelIndex  = collection.indexOf( this.model );
2868
2869                         if ( singleIndex < modelIndex ) {
2870                                 models = collection.models.slice( singleIndex, modelIndex + 1 );
2871                         } else {
2872                                 models = collection.models.slice( modelIndex, singleIndex + 1 );
2873                         }
2874
2875                         selection.add( models );
2876                         selection.single( model );
2877                         return;
2878
2879                 // If the `method` is set to `toggle`, just flip the selection
2880                 // status, regardless of whether the model is the single model.
2881                 } else if ( 'toggle' === method ) {
2882                         selection[ this.selected() ? 'remove' : 'add' ]( model );
2883                         selection.single( model );
2884                         return;
2885                 } else if ( 'add' === method ) {
2886                         selection.add( model );
2887                         selection.single( model );
2888                         return;
2889                 }
2890
2891                 // Fixes bug that loses focus when selecting a featured image
2892                 if ( ! method ) {
2893                         method = 'add';
2894                 }
2895
2896                 if ( method !== 'add' ) {
2897                         method = 'reset';
2898                 }
2899
2900                 if ( this.selected() ) {
2901                         // If the model is the single model, remove it.
2902                         // If it is not the same as the single model,
2903                         // it now becomes the single model.
2904                         selection[ single === model ? 'remove' : 'single' ]( model );
2905                 } else {
2906                         // If the model is not selected, run the `method` on the
2907                         // selection. By default, we `reset` the selection, but the
2908                         // `method` can be set to `add` the model to the selection.
2909                         selection[ method ]( model );
2910                         selection.single( model );
2911                 }
2912         },
2913
2914         updateSelect: function() {
2915                 this[ this.selected() ? 'select' : 'deselect' ]();
2916         },
2917         /**
2918          * @returns {unresolved|Boolean}
2919          */
2920         selected: function() {
2921                 var selection = this.options.selection;
2922                 if ( selection ) {
2923                         return !! selection.get( this.model.cid );
2924                 }
2925         },
2926         /**
2927          * @param {Backbone.Model} model
2928          * @param {Backbone.Collection} collection
2929          */
2930         select: function( model, collection ) {
2931                 var selection = this.options.selection,
2932                         controller = this.controller;
2933
2934                 // Check if a selection exists and if it's the collection provided.
2935                 // If they're not the same collection, bail; we're in another
2936                 // selection's event loop.
2937                 if ( ! selection || ( collection && collection !== selection ) ) {
2938                         return;
2939                 }
2940
2941                 // Bail if the model is already selected.
2942                 if ( this.$el.hasClass( 'selected' ) ) {
2943                         return;
2944                 }
2945
2946                 // Add 'selected' class to model, set aria-checked to true.
2947                 this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
2948                 //  Make the checkbox tabable, except in media grid (bulk select mode).
2949                 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
2950                         this.$( '.check' ).attr( 'tabindex', '0' );
2951                 }
2952         },
2953         /**
2954          * @param {Backbone.Model} model
2955          * @param {Backbone.Collection} collection
2956          */
2957         deselect: function( model, collection ) {
2958                 var selection = this.options.selection;
2959
2960                 // Check if a selection exists and if it's the collection provided.
2961                 // If they're not the same collection, bail; we're in another
2962                 // selection's event loop.
2963                 if ( ! selection || ( collection && collection !== selection ) ) {
2964                         return;
2965                 }
2966                 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
2967                         .find( '.check' ).attr( 'tabindex', '-1' );
2968         },
2969         /**
2970          * @param {Backbone.Model} model
2971          * @param {Backbone.Collection} collection
2972          */
2973         details: function( model, collection ) {
2974                 var selection = this.options.selection,
2975                         details;
2976
2977                 if ( selection !== collection ) {
2978                         return;
2979                 }
2980
2981                 details = selection.single();
2982                 this.$el.toggleClass( 'details', details === this.model );
2983         },
2984         /**
2985          * @param {string} size
2986          * @returns {Object}
2987          */
2988         imageSize: function( size ) {
2989                 var sizes = this.model.get('sizes'), matched = false;
2990
2991                 size = size || 'medium';
2992
2993                 // Use the provided image size if possible.
2994                 if ( sizes ) {
2995                         if ( sizes[ size ] ) {
2996                                 matched = sizes[ size ];
2997                         } else if ( sizes.large ) {
2998                                 matched = sizes.large;
2999                         } else if ( sizes.thumbnail ) {
3000                                 matched = sizes.thumbnail;
3001                         } else if ( sizes.full ) {
3002                                 matched = sizes.full;
3003                         }
3004
3005                         if ( matched ) {
3006                                 return _.clone( matched );
3007                         }
3008                 }
3009
3010                 return {
3011                         url:         this.model.get('url'),
3012                         width:       this.model.get('width'),
3013                         height:      this.model.get('height'),
3014                         orientation: this.model.get('orientation')
3015                 };
3016         },
3017         /**
3018          * @param {Object} event
3019          */
3020         updateSetting: function( event ) {
3021                 var $setting = $( event.target ).closest('[data-setting]'),
3022                         setting, value;
3023
3024                 if ( ! $setting.length ) {
3025                         return;
3026                 }
3027
3028                 setting = $setting.data('setting');
3029                 value   = event.target.value;
3030
3031                 if ( this.model.get( setting ) !== value ) {
3032                         this.save( setting, value );
3033                 }
3034         },
3035
3036         /**
3037          * Pass all the arguments to the model's save method.
3038          *
3039          * Records the aggregate status of all save requests and updates the
3040          * view's classes accordingly.
3041          */
3042         save: function() {
3043                 var view = this,
3044                         save = this._save = this._save || { status: 'ready' },
3045                         request = this.model.save.apply( this.model, arguments ),
3046                         requests = save.requests ? $.when( request, save.requests ) : request;
3047
3048                 // If we're waiting to remove 'Saved.', stop.
3049                 if ( save.savedTimer ) {
3050                         clearTimeout( save.savedTimer );
3051                 }
3052
3053                 this.updateSave('waiting');
3054                 save.requests = requests;
3055                 requests.always( function() {
3056                         // If we've performed another request since this one, bail.
3057                         if ( save.requests !== requests ) {
3058                                 return;
3059                         }
3060
3061                         view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
3062                         save.savedTimer = setTimeout( function() {
3063                                 view.updateSave('ready');
3064                                 delete save.savedTimer;
3065                         }, 2000 );
3066                 });
3067         },
3068         /**
3069          * @param {string} status
3070          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
3071          */
3072         updateSave: function( status ) {
3073                 var save = this._save = this._save || { status: 'ready' };
3074
3075                 if ( status && status !== save.status ) {
3076                         this.$el.removeClass( 'save-' + save.status );
3077                         save.status = status;
3078                 }
3079
3080                 this.$el.addClass( 'save-' + save.status );
3081                 return this;
3082         },
3083
3084         updateAll: function() {
3085                 var $settings = this.$('[data-setting]'),
3086                         model = this.model,
3087                         changed;
3088
3089                 changed = _.chain( $settings ).map( function( el ) {
3090                         var $input = $('input, textarea, select, [value]', el ),
3091                                 setting, value;
3092
3093                         if ( ! $input.length ) {
3094                                 return;
3095                         }
3096
3097                         setting = $(el).data('setting');
3098                         value = $input.val();
3099
3100                         // Record the value if it changed.
3101                         if ( model.get( setting ) !== value ) {
3102                                 return [ setting, value ];
3103                         }
3104                 }).compact().object().value();
3105
3106                 if ( ! _.isEmpty( changed ) ) {
3107                         model.save( changed );
3108                 }
3109         },
3110         /**
3111          * @param {Object} event
3112          */
3113         removeFromLibrary: function( event ) {
3114                 // Catch enter and space events
3115                 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
3116                         return;
3117                 }
3118
3119                 // Stop propagation so the model isn't selected.
3120                 event.stopPropagation();
3121
3122                 this.collection.remove( this.model );
3123         },
3124
3125         /**
3126          * Add the model if it isn't in the selection, if it is in the selection,
3127          * remove it.
3128          *
3129          * @param  {[type]} event [description]
3130          * @return {[type]}       [description]
3131          */
3132         checkClickHandler: function ( event ) {
3133                 var selection = this.options.selection;
3134                 if ( ! selection ) {
3135                         return;
3136                 }
3137                 event.stopPropagation();
3138                 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
3139                         selection.remove( this.model );
3140                         // Move focus back to the attachment tile (from the check).
3141                         this.$el.focus();
3142                 } else {
3143                         selection.add( this.model );
3144                 }
3145         }
3146 });
3147
3148 // Ensure settings remain in sync between attachment views.
3149 _.each({
3150         caption: '_syncCaption',
3151         title:   '_syncTitle',
3152         artist:  '_syncArtist',
3153         album:   '_syncAlbum'
3154 }, function( method, setting ) {
3155         /**
3156          * @param {Backbone.Model} model
3157          * @param {string} value
3158          * @returns {wp.media.view.Attachment} Returns itself to allow chaining
3159          */
3160         Attachment.prototype[ method ] = function( model, value ) {
3161                 var $setting = this.$('[data-setting="' + setting + '"]');
3162
3163                 if ( ! $setting.length ) {
3164                         return this;
3165                 }
3166
3167                 // If the updated value is in sync with the value in the DOM, there
3168                 // is no need to re-render. If we're currently editing the value,
3169                 // it will automatically be in sync, suppressing the re-render for
3170                 // the view we're editing, while updating any others.
3171                 if ( value === $setting.find('input, textarea, select, [value]').val() ) {
3172                         return this;
3173                 }
3174
3175                 return this.render();
3176         };
3177 });
3178
3179 module.exports = Attachment;
3180
3181 },{}],26:[function(require,module,exports){
3182 /**
3183  * wp.media.view.Attachment.Details
3184  *
3185  * @class
3186  * @augments wp.media.view.Attachment
3187  * @augments wp.media.View
3188  * @augments wp.Backbone.View
3189  * @augments Backbone.View
3190  */
3191 var Attachment = wp.media.view.Attachment,
3192         l10n = wp.media.view.l10n,
3193         Details;
3194
3195 Details = Attachment.extend({
3196         tagName:   'div',
3197         className: 'attachment-details',
3198         template:  wp.template('attachment-details'),
3199
3200         attributes: function() {
3201                 return {
3202                         'tabIndex':     0,
3203                         'data-id':      this.model.get( 'id' )
3204                 };
3205         },
3206
3207         events: {
3208                 'change [data-setting]':          'updateSetting',
3209                 'change [data-setting] input':    'updateSetting',
3210                 'change [data-setting] select':   'updateSetting',
3211                 'change [data-setting] textarea': 'updateSetting',
3212                 'click .delete-attachment':       'deleteAttachment',
3213                 'click .trash-attachment':        'trashAttachment',
3214                 'click .untrash-attachment':      'untrashAttachment',
3215                 'click .edit-attachment':         'editAttachment',
3216                 'keydown':                        'toggleSelectionHandler'
3217         },
3218
3219         initialize: function() {
3220                 this.options = _.defaults( this.options, {
3221                         rerenderOnModelChange: false
3222                 });
3223
3224                 this.on( 'ready', this.initialFocus );
3225                 // Call 'initialize' directly on the parent class.
3226                 Attachment.prototype.initialize.apply( this, arguments );
3227         },
3228
3229         initialFocus: function() {
3230                 if ( ! wp.media.isTouchDevice ) {
3231                         /*
3232                         Previously focused the first ':input' (the readonly URL text field).
3233                         Since the first ':input' is now a button (delete/trash): when pressing
3234                         spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
3235                         as soon as focus is moved. Explicitly target the first text field for now.
3236                         @todo change initial focus logic, also for accessibility.
3237                         */
3238                         this.$( 'input[type="text"]' ).eq( 0 ).focus();
3239                 }
3240         },
3241         /**
3242          * @param {Object} event
3243          */
3244         deleteAttachment: function( event ) {
3245                 event.preventDefault();
3246
3247                 if ( window.confirm( l10n.warnDelete ) ) {
3248                         this.model.destroy();
3249                         // Keep focus inside media modal
3250                         // after image is deleted
3251                         this.controller.modal.focusManager.focus();
3252                 }
3253         },
3254         /**
3255          * @param {Object} event
3256          */
3257         trashAttachment: function( event ) {
3258                 var library = this.controller.library;
3259                 event.preventDefault();
3260
3261                 if ( wp.media.view.settings.mediaTrash &&
3262                         'edit-metadata' === this.controller.content.mode() ) {
3263
3264                         this.model.set( 'status', 'trash' );
3265                         this.model.save().done( function() {
3266                                 library._requery( true );
3267                         } );
3268                 }  else {
3269                         this.model.destroy();
3270                 }
3271         },
3272         /**
3273          * @param {Object} event
3274          */
3275         untrashAttachment: function( event ) {
3276                 var library = this.controller.library;
3277                 event.preventDefault();
3278
3279                 this.model.set( 'status', 'inherit' );
3280                 this.model.save().done( function() {
3281                         library._requery( true );
3282                 } );
3283         },
3284         /**
3285          * @param {Object} event
3286          */
3287         editAttachment: function( event ) {
3288                 var editState = this.controller.states.get( 'edit-image' );
3289                 if ( window.imageEdit && editState ) {
3290                         event.preventDefault();
3291
3292                         editState.set( 'image', this.model );
3293                         this.controller.setState( 'edit-image' );
3294                 } else {
3295                         this.$el.addClass('needs-refresh');
3296                 }
3297         },
3298         /**
3299          * When reverse tabbing(shift+tab) out of the right details panel, deliver
3300          * the focus to the item in the list that was being edited.
3301          *
3302          * @param {Object} event
3303          */
3304         toggleSelectionHandler: function( event ) {
3305                 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
3306                         this.controller.trigger( 'attachment:details:shift-tab', event );
3307                         return false;
3308                 }
3309
3310                 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
3311                         this.controller.trigger( 'attachment:keydown:arrow', event );
3312                         return;
3313                 }
3314         }
3315 });
3316
3317 module.exports = Details;
3318
3319 },{}],27:[function(require,module,exports){
3320 /**
3321  * wp.media.view.Attachment.EditLibrary
3322  *
3323  * @class
3324  * @augments wp.media.view.Attachment
3325  * @augments wp.media.View
3326  * @augments wp.Backbone.View
3327  * @augments Backbone.View
3328  */
3329 var EditLibrary = wp.media.view.Attachment.extend({
3330         buttons: {
3331                 close: true
3332         }
3333 });
3334
3335 module.exports = EditLibrary;
3336
3337 },{}],28:[function(require,module,exports){
3338 /**
3339  * wp.media.view.Attachments.EditSelection
3340  *
3341  * @class
3342  * @augments wp.media.view.Attachment.Selection
3343  * @augments wp.media.view.Attachment
3344  * @augments wp.media.View
3345  * @augments wp.Backbone.View
3346  * @augments Backbone.View
3347  */
3348 var EditSelection = wp.media.view.Attachment.Selection.extend({
3349         buttons: {
3350                 close: true
3351         }
3352 });
3353
3354 module.exports = EditSelection;
3355
3356 },{}],29:[function(require,module,exports){
3357 /**
3358  * wp.media.view.Attachment.Library
3359  *
3360  * @class
3361  * @augments wp.media.view.Attachment
3362  * @augments wp.media.View
3363  * @augments wp.Backbone.View
3364  * @augments Backbone.View
3365  */
3366 var Library = wp.media.view.Attachment.extend({
3367         buttons: {
3368                 check: true
3369         }
3370 });
3371
3372 module.exports = Library;
3373
3374 },{}],30:[function(require,module,exports){
3375 /**
3376  * wp.media.view.Attachment.Selection
3377  *
3378  * @class
3379  * @augments wp.media.view.Attachment
3380  * @augments wp.media.View
3381  * @augments wp.Backbone.View
3382  * @augments Backbone.View
3383  */
3384 var Selection = wp.media.view.Attachment.extend({
3385         className: 'attachment selection',
3386
3387         // On click, just select the model, instead of removing the model from
3388         // the selection.
3389         toggleSelection: function() {
3390                 this.options.selection.single( this.model );
3391         }
3392 });
3393
3394 module.exports = Selection;
3395
3396 },{}],31:[function(require,module,exports){
3397 /**
3398  * wp.media.view.Attachments
3399  *
3400  * @class
3401  * @augments wp.media.View
3402  * @augments wp.Backbone.View
3403  * @augments Backbone.View
3404  */
3405 var View = wp.media.View,
3406         $ = jQuery,
3407         Attachments;
3408
3409 Attachments = View.extend({
3410         tagName:   'ul',
3411         className: 'attachments',
3412
3413         attributes: {
3414                 tabIndex: -1
3415         },
3416
3417         initialize: function() {
3418                 this.el.id = _.uniqueId('__attachments-view-');
3419
3420                 _.defaults( this.options, {
3421                         refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
3422                         refreshThreshold:   3,
3423                         AttachmentView:     wp.media.view.Attachment,
3424                         sortable:           false,
3425                         resize:             true,
3426                         idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
3427                 });
3428
3429                 this._viewsByCid = {};
3430                 this.$window = $( window );
3431                 this.resizeEvent = 'resize.media-modal-columns';
3432
3433                 this.collection.on( 'add', function( attachment ) {
3434                         this.views.add( this.createAttachmentView( attachment ), {
3435                                 at: this.collection.indexOf( attachment )
3436                         });
3437                 }, this );
3438
3439                 this.collection.on( 'remove', function( attachment ) {
3440                         var view = this._viewsByCid[ attachment.cid ];
3441                         delete this._viewsByCid[ attachment.cid ];
3442
3443                  &n