PageSourceSearch

https://www.libriincantina.it/wp-includes/js/media-views.js?ver=7.1.2

js libriincantina.it collected 2026-10-02 07:14:10 UTC 273,089 bytes, 10,578 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.