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