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