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