PageRenderTime 382ms CodeModel.GetById 20ms app.highlight 312ms RepoModel.GetById 1ms app.codeStats 2ms

/HotelSpaWP/wp-includes/js/media-views.js

https://bitbucket.org/Trulsh/personal-bootstrap-projects
JavaScript | 8542 lines | 5137 code | 1223 blank | 2182 comment | 606 complexity | b44bc57736b0f9071a8d5b4e1a1c38c0 MD5 | raw file
   1(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
   2/**
   3 * wp.media.controller.CollectionAdd
   4 *
   5 * A state for adding attachments to a collection (e.g. video playlist).
   6 *
   7 * @class
   8 * @augments wp.media.controller.Library
   9 * @augments wp.media.controller.State
  10 * @augments Backbone.Model
  11 *
  12 * @param {object}                     [attributes]                         The attributes hash passed to the state.
  13 * @param {string}                     [attributes.id=library]      Unique identifier.
  14 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
  15 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
  16 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
  17 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
  18 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
  19 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
  20 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
  21 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
  22 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
  23 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
  24 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
  25 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
  26 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
  27 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
  28 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
  29 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
  30 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
  31 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
  32 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
  33 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
  34 */
  35var Selection = wp.media.model.Selection,
  36	Library = wp.media.controller.Library,
  37	CollectionAdd;
  38
  39CollectionAdd = Library.extend({
  40	defaults: _.defaults( {
  41		// Selection defaults. @see media.model.Selection
  42		multiple:      'add',
  43		// Attachments browser defaults. @see media.view.AttachmentsBrowser
  44		filterable:    'uploaded',
  45
  46		priority:      100,
  47		syncSelection: false
  48	}, Library.prototype.defaults ),
  49
  50	/**
  51	 * @since 3.9.0
  52	 */
  53	initialize: function() {
  54		var collectionType = this.get('collectionType');
  55
  56		if ( 'video' === this.get( 'type' ) ) {
  57			collectionType = 'video-' + collectionType;
  58		}
  59
  60		this.set( 'id', collectionType + '-library' );
  61		this.set( 'toolbar', collectionType + '-add' );
  62		this.set( 'menu', collectionType );
  63
  64		// If we haven't been provided a `library`, create a `Selection`.
  65		if ( ! this.get('library') ) {
  66			this.set( 'library', wp.media.query({ type: this.get('type') }) );
  67		}
  68		Library.prototype.initialize.apply( this, arguments );
  69	},
  70
  71	/**
  72	 * @since 3.9.0
  73	 */
  74	activate: function() {
  75		var library = this.get('library'),
  76			editLibrary = this.get('editLibrary'),
  77			edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
  78
  79		if ( editLibrary && editLibrary !== edit ) {
  80			library.unobserve( editLibrary );
  81		}
  82
  83		// Accepts attachments that exist in the original library and
  84		// that do not exist in gallery's library.
  85		library.validator = function( attachment ) {
  86			return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
  87		};
  88
  89		// Reset the library to ensure that all attachments are re-added
  90		// to the collection. Do so silently, as calling `observe` will
  91		// trigger the `reset` event.
  92		library.reset( library.mirroring.models, { silent: true });
  93		library.observe( edit );
  94		this.set('editLibrary', edit);
  95
  96		Library.prototype.activate.apply( this, arguments );
  97	}
  98});
  99
 100module.exports = CollectionAdd;
 101
 102},{}],2:[function(require,module,exports){
 103/**
 104 * wp.media.controller.CollectionEdit
 105 *
 106 * A state for editing a collection, which is used by audio and video playlists,
 107 * and can be used for other collections.
 108 *
 109 * @class
 110 * @augments wp.media.controller.Library
 111 * @augments wp.media.controller.State
 112 * @augments Backbone.Model
 113 *
 114 * @param {object}                     [attributes]                      The attributes hash passed to the state.
 115 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
 116 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
 117 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
 118 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
 119 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
 120 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
 121 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
 122 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
 123 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
 124 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
 125 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
 126 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
 127 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
 128 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
 129 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
 130 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
 131 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
 132 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
 133 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
 134 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
 135 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
 136 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
 137 */
 138var Library = wp.media.controller.Library,
 139	l10n = wp.media.view.l10n,
 140	$ = jQuery,
 141	CollectionEdit;
 142
 143CollectionEdit = Library.extend({
 144	defaults: {
 145		multiple:         false,
 146		sortable:         true,
 147		date:             false,
 148		searchable:       false,
 149		content:          'browse',
 150		describe:         true,
 151		dragInfo:         true,
 152		idealColumnWidth: 170,
 153		editing:          false,
 154		priority:         60,
 155		SettingsView:     false,
 156		syncSelection:    false
 157	},
 158
 159	/**
 160	 * @since 3.9.0
 161	 */
 162	initialize: function() {
 163		var collectionType = this.get('collectionType');
 164
 165		if ( 'video' === this.get( 'type' ) ) {
 166			collectionType = 'video-' + collectionType;
 167		}
 168
 169		this.set( 'id', collectionType + '-edit' );
 170		this.set( 'toolbar', collectionType + '-edit' );
 171
 172		// If we haven't been provided a `library`, create a `Selection`.
 173		if ( ! this.get('library') ) {
 174			this.set( 'library', new wp.media.model.Selection() );
 175		}
 176		// The single `Attachment` view to be used in the `Attachments` view.
 177		if ( ! this.get('AttachmentView') ) {
 178			this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
 179		}
 180		Library.prototype.initialize.apply( this, arguments );
 181	},
 182
 183	/**
 184	 * @since 3.9.0
 185	 */
 186	activate: function() {
 187		var library = this.get('library');
 188
 189		// Limit the library to images only.
 190		library.props.set( 'type', this.get( 'type' ) );
 191
 192		// Watch for uploaded attachments.
 193		this.get('library').observe( wp.Uploader.queue );
 194
 195		this.frame.on( 'content:render:browse', this.renderSettings, this );
 196
 197		Library.prototype.activate.apply( this, arguments );
 198	},
 199
 200	/**
 201	 * @since 3.9.0
 202	 */
 203	deactivate: function() {
 204		// Stop watching for uploaded attachments.
 205		this.get('library').unobserve( wp.Uploader.queue );
 206
 207		this.frame.off( 'content:render:browse', this.renderSettings, this );
 208
 209		Library.prototype.deactivate.apply( this, arguments );
 210	},
 211
 212	/**
 213	 * Render the collection embed settings view in the browser sidebar.
 214	 *
 215	 * @todo This is against the pattern elsewhere in media. Typically the frame
 216	 *       is responsible for adding region mode callbacks. Explain.
 217	 *
 218	 * @since 3.9.0
 219	 *
 220	 * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
 221	 */
 222	renderSettings: function( attachmentsBrowserView ) {
 223		var library = this.get('library'),
 224			collectionType = this.get('collectionType'),
 225			dragInfoText = this.get('dragInfoText'),
 226			SettingsView = this.get('SettingsView'),
 227			obj = {};
 228
 229		if ( ! library || ! attachmentsBrowserView ) {
 230			return;
 231		}
 232
 233		library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
 234
 235		obj[ collectionType ] = new SettingsView({
 236			controller: this,
 237			model:      library[ collectionType ],
 238			priority:   40
 239		});
 240
 241		attachmentsBrowserView.sidebar.set( obj );
 242
 243		if ( dragInfoText ) {
 244			attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
 245				el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
 246				priority: -40
 247			}) );
 248		}
 249
 250		// Add the 'Reverse order' button to the toolbar.
 251		attachmentsBrowserView.toolbar.set( 'reverse', {
 252			text:     l10n.reverseOrder,
 253			priority: 80,
 254
 255			click: function() {
 256				library.reset( library.toArray().reverse() );
 257			}
 258		});
 259	}
 260});
 261
 262module.exports = CollectionEdit;
 263
 264},{}],3:[function(require,module,exports){
 265/**
 266 * wp.media.controller.Cropper
 267 *
 268 * A state for cropping an image.
 269 *
 270 * @class
 271 * @augments wp.media.controller.State
 272 * @augments Backbone.Model
 273 */
 274var l10n = wp.media.view.l10n,
 275	Cropper;
 276
 277Cropper = wp.media.controller.State.extend({
 278	defaults: {
 279		id:          'cropper',
 280		title:       l10n.cropImage,
 281		// Region mode defaults.
 282		toolbar:     'crop',
 283		content:     'crop',
 284		router:      false,
 285		canSkipCrop: false,
 286
 287		// Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
 288		doCropArgs: {}
 289	},
 290
 291	activate: function() {
 292		this.frame.on( 'content:create:crop', this.createCropContent, this );
 293		this.frame.on( 'close', this.removeCropper, this );
 294		this.set('selection', new Backbone.Collection(this.frame._selection.single));
 295	},
 296
 297	deactivate: function() {
 298		this.frame.toolbar.mode('browse');
 299	},
 300
 301	createCropContent: function() {
 302		this.cropperView = new wp.media.view.Cropper({
 303			controller: this,
 304			attachment: this.get('selection').first()
 305		});
 306		this.cropperView.on('image-loaded', this.createCropToolbar, this);
 307		this.frame.content.set(this.cropperView);
 308
 309	},
 310	removeCropper: function() {
 311		this.imgSelect.cancelSelection();
 312		this.imgSelect.setOptions({remove: true});
 313		this.imgSelect.update();
 314		this.cropperView.remove();
 315	},
 316	createCropToolbar: function() {
 317		var canSkipCrop, toolbarOptions;
 318
 319		canSkipCrop = this.get('canSkipCrop') || false;
 320
 321		toolbarOptions = {
 322			controller: this.frame,
 323			items: {
 324				insert: {
 325					style:    'primary',
 326					text:     l10n.cropImage,
 327					priority: 80,
 328					requires: { library: false, selection: false },
 329
 330					click: function() {
 331						var controller = this.controller,
 332							selection;
 333
 334						selection = controller.state().get('selection').first();
 335						selection.set({cropDetails: controller.state().imgSelect.getSelection()});
 336
 337						this.$el.text(l10n.cropping);
 338						this.$el.attr('disabled', true);
 339
 340						controller.state().doCrop( selection ).done( function( croppedImage ) {
 341							controller.trigger('cropped', croppedImage );
 342							controller.close();
 343						}).fail( function() {
 344							controller.trigger('content:error:crop');
 345						});
 346					}
 347				}
 348			}
 349		};
 350
 351		if ( canSkipCrop ) {
 352			_.extend( toolbarOptions.items, {
 353				skip: {
 354					style:      'secondary',
 355					text:       l10n.skipCropping,
 356					priority:   70,
 357					requires:   { library: false, selection: false },
 358					click:      function() {
 359						var selection = this.controller.state().get('selection').first();
 360						this.controller.state().cropperView.remove();
 361						this.controller.trigger('skippedcrop', selection);
 362						this.controller.close();
 363					}
 364				}
 365			});
 366		}
 367
 368		this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
 369	},
 370
 371	doCrop: function( attachment ) {
 372		return wp.ajax.post( 'custom-header-crop', _.extend(
 373			{},
 374			this.defaults.doCropArgs,
 375			{
 376				nonce: attachment.get( 'nonces' ).edit,
 377				id: attachment.get( 'id' ),
 378				cropDetails: attachment.get( 'cropDetails' )
 379			}
 380		) );
 381	}
 382});
 383
 384module.exports = Cropper;
 385
 386},{}],4:[function(require,module,exports){
 387/**
 388 * wp.media.controller.CustomizeImageCropper
 389 *
 390 * A state for cropping an image.
 391 *
 392 * @class
 393 * @augments wp.media.controller.Cropper
 394 * @augments wp.media.controller.State
 395 * @augments Backbone.Model
 396 */
 397var Controller = wp.media.controller,
 398	CustomizeImageCropper;
 399
 400CustomizeImageCropper = Controller.Cropper.extend({
 401	doCrop: function( attachment ) {
 402		var cropDetails = attachment.get( 'cropDetails' ),
 403			control = this.get( 'control' ),
 404			ratio = cropDetails.width / cropDetails.height;
 405
 406		// Use crop measurements when flexible in both directions.
 407		if ( control.params.flex_width && control.params.flex_height ) {
 408			cropDetails.dst_width  = cropDetails.width;
 409			cropDetails.dst_height = cropDetails.height;
 410
 411		// Constrain flexible side based on image ratio and size of the fixed side.
 412		} else {
 413			cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
 414			cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
 415		}
 416
 417		return wp.ajax.post( 'crop-image', {
 418			wp_customize: 'on',
 419			nonce: attachment.get( 'nonces' ).edit,
 420			id: attachment.get( 'id' ),
 421			context: control.id,
 422			cropDetails: cropDetails
 423		} );
 424	}
 425});
 426
 427module.exports = CustomizeImageCropper;
 428
 429},{}],5:[function(require,module,exports){
 430/**
 431 * wp.media.controller.EditImage
 432 *
 433 * A state for editing (cropping, etc.) an image.
 434 *
 435 * @class
 436 * @augments wp.media.controller.State
 437 * @augments Backbone.Model
 438 *
 439 * @param {object}                    attributes                      The attributes hash passed to the state.
 440 * @param {wp.media.model.Attachment} attributes.model                The attachment.
 441 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
 442 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
 443 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
 444 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
 445 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
 446 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
 447 */
 448var l10n = wp.media.view.l10n,
 449	EditImage;
 450
 451EditImage = wp.media.controller.State.extend({
 452	defaults: {
 453		id:      'edit-image',
 454		title:   l10n.editImage,
 455		menu:    false,
 456		toolbar: 'edit-image',
 457		content: 'edit-image',
 458		url:     ''
 459	},
 460
 461	/**
 462	 * @since 3.9.0
 463	 */
 464	activate: function() {
 465		this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
 466	},
 467
 468	/**
 469	 * @since 3.9.0
 470	 */
 471	deactivate: function() {
 472		this.frame.off( 'toolbar:render:edit-image' );
 473	},
 474
 475	/**
 476	 * @since 3.9.0
 477	 */
 478	toolbar: function() {
 479		var frame = this.frame,
 480			lastState = frame.lastState(),
 481			previous = lastState && lastState.id;
 482
 483		frame.toolbar.set( new wp.media.view.Toolbar({
 484			controller: frame,
 485			items: {
 486				back: {
 487					style: 'primary',
 488					text:     l10n.back,
 489					priority: 20,
 490					click:    function() {
 491						if ( previous ) {
 492							frame.setState( previous );
 493						} else {
 494							frame.close();
 495						}
 496					}
 497				}
 498			}
 499		}) );
 500	}
 501});
 502
 503module.exports = EditImage;
 504
 505},{}],6:[function(require,module,exports){
 506/**
 507 * wp.media.controller.Embed
 508 *
 509 * A state for embedding media from a URL.
 510 *
 511 * @class
 512 * @augments wp.media.controller.State
 513 * @augments Backbone.Model
 514 *
 515 * @param {object} attributes                         The attributes hash passed to the state.
 516 * @param {string} [attributes.id=embed]              Unique identifier.
 517 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
 518 * @param {string} [attributes.content=embed]         Initial mode for the content region.
 519 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
 520 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
 521 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
 522 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
 523 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
 524 * @param {string} [attributes.url]                   The embed URL.
 525 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
 526 */
 527var l10n = wp.media.view.l10n,
 528	$ = Backbone.$,
 529	Embed;
 530
 531Embed = wp.media.controller.State.extend({
 532	defaults: {
 533		id:       'embed',
 534		title:    l10n.insertFromUrlTitle,
 535		content:  'embed',
 536		menu:     'default',
 537		toolbar:  'main-embed',
 538		priority: 120,
 539		type:     'link',
 540		url:      '',
 541		metadata: {}
 542	},
 543
 544	// The amount of time used when debouncing the scan.
 545	sensitivity: 400,
 546
 547	initialize: function(options) {
 548		this.metadata = options.metadata;
 549		this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
 550		this.props = new Backbone.Model( this.metadata || { url: '' });
 551		this.props.on( 'change:url', this.debouncedScan, this );
 552		this.props.on( 'change:url', this.refresh, this );
 553		this.on( 'scan', this.scanImage, this );
 554	},
 555
 556	/**
 557	 * Trigger a scan of the embedded URL's content for metadata required to embed.
 558	 *
 559	 * @fires wp.media.controller.Embed#scan
 560	 */
 561	scan: function() {
 562		var scanners,
 563			embed = this,
 564			attributes = {
 565				type: 'link',
 566				scanners: []
 567			};
 568
 569		// Scan is triggered with the list of `attributes` to set on the
 570		// state, useful for the 'type' attribute and 'scanners' attribute,
 571		// an array of promise objects for asynchronous scan operations.
 572		if ( this.props.get('url') ) {
 573			this.trigger( 'scan', attributes );
 574		}
 575
 576		if ( attributes.scanners.length ) {
 577			scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
 578			scanners.always( function() {
 579				if ( embed.get('scanners') === scanners ) {
 580					embed.set( 'loading', false );
 581				}
 582			});
 583		} else {
 584			attributes.scanners = null;
 585		}
 586
 587		attributes.loading = !! attributes.scanners;
 588		this.set( attributes );
 589	},
 590	/**
 591	 * Try scanning the embed as an image to discover its dimensions.
 592	 *
 593	 * @param {Object} attributes
 594	 */
 595	scanImage: function( attributes ) {
 596		var frame = this.frame,
 597			state = this,
 598			url = this.props.get('url'),
 599			image = new Image(),
 600			deferred = $.Deferred();
 601
 602		attributes.scanners.push( deferred.promise() );
 603
 604		// Try to load the image and find its width/height.
 605		image.onload = function() {
 606			deferred.resolve();
 607
 608			if ( state !== frame.state() || url !== state.props.get('url') ) {
 609				return;
 610			}
 611
 612			state.set({
 613				type: 'image'
 614			});
 615
 616			state.props.set({
 617				width:  image.width,
 618				height: image.height
 619			});
 620		};
 621
 622		image.onerror = deferred.reject;
 623		image.src = url;
 624	},
 625
 626	refresh: function() {
 627		this.frame.toolbar.get().refresh();
 628	},
 629
 630	reset: function() {
 631		this.props.clear().set({ url: '' });
 632
 633		if ( this.active ) {
 634			this.refresh();
 635		}
 636	}
 637});
 638
 639module.exports = Embed;
 640
 641},{}],7:[function(require,module,exports){
 642/**
 643 * wp.media.controller.FeaturedImage
 644 *
 645 * A state for selecting a featured image for a post.
 646 *
 647 * @class
 648 * @augments wp.media.controller.Library
 649 * @augments wp.media.controller.State
 650 * @augments Backbone.Model
 651 *
 652 * @param {object}                     [attributes]                          The attributes hash passed to the state.
 653 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
 654 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
 655 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
 656 *                                                                           If one is not supplied, a collection of all images will be created.
 657 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
 658 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
 659 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
 660 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
 661 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
 662 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
 663 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
 664 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
 665 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
 666 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
 667 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
 668 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
 669 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
 670 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
 671 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
 672 */
 673var Attachment = wp.media.model.Attachment,
 674	Library = wp.media.controller.Library,
 675	l10n = wp.media.view.l10n,
 676	FeaturedImage;
 677
 678FeaturedImage = Library.extend({
 679	defaults: _.defaults({
 680		id:            'featured-image',
 681		title:         l10n.setFeaturedImageTitle,
 682		multiple:      false,
 683		filterable:    'uploaded',
 684		toolbar:       'featured-image',
 685		priority:      60,
 686		syncSelection: true
 687	}, Library.prototype.defaults ),
 688
 689	/**
 690	 * @since 3.5.0
 691	 */
 692	initialize: function() {
 693		var library, comparator;
 694
 695		// If we haven't been provided a `library`, create a `Selection`.
 696		if ( ! this.get('library') ) {
 697			this.set( 'library', wp.media.query({ type: 'image' }) );
 698		}
 699
 700		Library.prototype.initialize.apply( this, arguments );
 701
 702		library    = this.get('library');
 703		comparator = library.comparator;
 704
 705		// Overload the library's comparator to push items that are not in
 706		// the mirrored query to the front of the aggregate collection.
 707		library.comparator = function( a, b ) {
 708			var aInQuery = !! this.mirroring.get( a.cid ),
 709				bInQuery = !! this.mirroring.get( b.cid );
 710
 711			if ( ! aInQuery && bInQuery ) {
 712				return -1;
 713			} else if ( aInQuery && ! bInQuery ) {
 714				return 1;
 715			} else {
 716				return comparator.apply( this, arguments );
 717			}
 718		};
 719
 720		// Add all items in the selection to the library, so any featured
 721		// images that are not initially loaded still appear.
 722		library.observe( this.get('selection') );
 723	},
 724
 725	/**
 726	 * @since 3.5.0
 727	 */
 728	activate: function() {
 729		this.updateSelection();
 730		this.frame.on( 'open', this.updateSelection, this );
 731
 732		Library.prototype.activate.apply( this, arguments );
 733	},
 734
 735	/**
 736	 * @since 3.5.0
 737	 */
 738	deactivate: function() {
 739		this.frame.off( 'open', this.updateSelection, this );
 740
 741		Library.prototype.deactivate.apply( this, arguments );
 742	},
 743
 744	/**
 745	 * @since 3.5.0
 746	 */
 747	updateSelection: function() {
 748		var selection = this.get('selection'),
 749			id = wp.media.view.settings.post.featuredImageId,
 750			attachment;
 751
 752		if ( '' !== id && -1 !== id ) {
 753			attachment = Attachment.get( id );
 754			attachment.fetch();
 755		}
 756
 757		selection.reset( attachment ? [ attachment ] : [] );
 758	}
 759});
 760
 761module.exports = FeaturedImage;
 762
 763},{}],8:[function(require,module,exports){
 764/**
 765 * wp.media.controller.GalleryAdd
 766 *
 767 * A state for selecting more images to add to a gallery.
 768 *
 769 * @class
 770 * @augments wp.media.controller.Library
 771 * @augments wp.media.controller.State
 772 * @augments Backbone.Model
 773 *
 774 * @param {object}                     [attributes]                         The attributes hash passed to the state.
 775 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
 776 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
 777 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
 778 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
 779 *                                                                          If one is not supplied, a collection of all images will be created.
 780 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
 781 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
 782 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
 783 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
 784 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
 785 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
 786 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
 787 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
 788 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
 789 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
 790 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
 791 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
 792 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
 793 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
 794 */
 795var Selection = wp.media.model.Selection,
 796	Library = wp.media.controller.Library,
 797	l10n = wp.media.view.l10n,
 798	GalleryAdd;
 799
 800GalleryAdd = Library.extend({
 801	defaults: _.defaults({
 802		id:            'gallery-library',
 803		title:         l10n.addToGalleryTitle,
 804		multiple:      'add',
 805		filterable:    'uploaded',
 806		menu:          'gallery',
 807		toolbar:       'gallery-add',
 808		priority:      100,
 809		syncSelection: false
 810	}, Library.prototype.defaults ),
 811
 812	/**
 813	 * @since 3.5.0
 814	 */
 815	initialize: function() {
 816		// If a library wasn't supplied, create a library of images.
 817		if ( ! this.get('library') ) {
 818			this.set( 'library', wp.media.query({ type: 'image' }) );
 819		}
 820
 821		Library.prototype.initialize.apply( this, arguments );
 822	},
 823
 824	/**
 825	 * @since 3.5.0
 826	 */
 827	activate: function() {
 828		var library = this.get('library'),
 829			edit    = this.frame.state('gallery-edit').get('library');
 830
 831		if ( this.editLibrary && this.editLibrary !== edit ) {
 832			library.unobserve( this.editLibrary );
 833		}
 834
 835		// Accepts attachments that exist in the original library and
 836		// that do not exist in gallery's library.
 837		library.validator = function( attachment ) {
 838			return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
 839		};
 840
 841		// Reset the library to ensure that all attachments are re-added
 842		// to the collection. Do so silently, as calling `observe` will
 843		// trigger the `reset` event.
 844		library.reset( library.mirroring.models, { silent: true });
 845		library.observe( edit );
 846		this.editLibrary = edit;
 847
 848		Library.prototype.activate.apply( this, arguments );
 849	}
 850});
 851
 852module.exports = GalleryAdd;
 853
 854},{}],9:[function(require,module,exports){
 855/**
 856 * wp.media.controller.GalleryEdit
 857 *
 858 * A state for editing a gallery's images and settings.
 859 *
 860 * @class
 861 * @augments wp.media.controller.Library
 862 * @augments wp.media.controller.State
 863 * @augments Backbone.Model
 864 *
 865 * @param {object}                     [attributes]                       The attributes hash passed to the state.
 866 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
 867 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
 868 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
 869 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
 870 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
 871 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
 872 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
 873 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
 874 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
 875 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
 876 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
 877 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
 878 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
 879 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
 880 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
 881 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
 882 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
 883 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
 884 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
 885 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
 886 */
 887var Library = wp.media.controller.Library,
 888	l10n = wp.media.view.l10n,
 889	GalleryEdit;
 890
 891GalleryEdit = Library.extend({
 892	defaults: {
 893		id:               'gallery-edit',
 894		title:            l10n.editGalleryTitle,
 895		multiple:         false,
 896		searchable:       false,
 897		sortable:         true,
 898		date:             false,
 899		display:          false,
 900		content:          'browse',
 901		toolbar:          'gallery-edit',
 902		describe:         true,
 903		displaySettings:  true,
 904		dragInfo:         true,
 905		idealColumnWidth: 170,
 906		editing:          false,
 907		priority:         60,
 908		syncSelection:    false
 909	},
 910
 911	/**
 912	 * @since 3.5.0
 913	 */
 914	initialize: function() {
 915		// If we haven't been provided a `library`, create a `Selection`.
 916		if ( ! this.get('library') ) {
 917			this.set( 'library', new wp.media.model.Selection() );
 918		}
 919
 920		// The single `Attachment` view to be used in the `Attachments` view.
 921		if ( ! this.get('AttachmentView') ) {
 922			this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
 923		}
 924
 925		Library.prototype.initialize.apply( this, arguments );
 926	},
 927
 928	/**
 929	 * @since 3.5.0
 930	 */
 931	activate: function() {
 932		var library = this.get('library');
 933
 934		// Limit the library to images only.
 935		library.props.set( 'type', 'image' );
 936
 937		// Watch for uploaded attachments.
 938		this.get('library').observe( wp.Uploader.queue );
 939
 940		this.frame.on( 'content:render:browse', this.gallerySettings, this );
 941
 942		Library.prototype.activate.apply( this, arguments );
 943	},
 944
 945	/**
 946	 * @since 3.5.0
 947	 */
 948	deactivate: function() {
 949		// Stop watching for uploaded attachments.
 950		this.get('library').unobserve( wp.Uploader.queue );
 951
 952		this.frame.off( 'content:render:browse', this.gallerySettings, this );
 953
 954		Library.prototype.deactivate.apply( this, arguments );
 955	},
 956
 957	/**
 958	 * @since 3.5.0
 959	 *
 960	 * @param browser
 961	 */
 962	gallerySettings: function( browser ) {
 963		if ( ! this.get('displaySettings') ) {
 964			return;
 965		}
 966
 967		var library = this.get('library');
 968
 969		if ( ! library || ! browser ) {
 970			return;
 971		}
 972
 973		library.gallery = library.gallery || new Backbone.Model();
 974
 975		browser.sidebar.set({
 976			gallery: new wp.media.view.Settings.Gallery({
 977				controller: this,
 978				model:      library.gallery,
 979				priority:   40
 980			})
 981		});
 982
 983		browser.toolbar.set( 'reverse', {
 984			text:     l10n.reverseOrder,
 985			priority: 80,
 986
 987			click: function() {
 988				library.reset( library.toArray().reverse() );
 989			}
 990		});
 991	}
 992});
 993
 994module.exports = GalleryEdit;
 995
 996},{}],10:[function(require,module,exports){
 997/**
 998 * wp.media.controller.ImageDetails
 999 *
1000 * A state for editing the attachment display settings of an image that's been
1001 * inserted into the editor.
1002 *
1003 * @class
1004 * @augments wp.media.controller.State
1005 * @augments Backbone.Model
1006 *
1007 * @param {object}                    [attributes]                       The attributes hash passed to the state.
1008 * @param {string}                    [attributes.id=image-details]      Unique identifier.
1009 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
1010 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
1011 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
1012 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
1013 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
1014 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
1015 * @param {boolean}                   [attributes.editing=false]         Unused.
1016 * @param {int}                       [attributes.priority=60]           Unused.
1017 *
1018 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
1019 *       however this may not do anything.
1020 */
1021var State = wp.media.controller.State,
1022	Library = wp.media.controller.Library,
1023	l10n = wp.media.view.l10n,
1024	ImageDetails;
1025
1026ImageDetails = State.extend({
1027	defaults: _.defaults({
1028		id:       'image-details',
1029		title:    l10n.imageDetailsTitle,
1030		content:  'image-details',
1031		menu:     false,
1032		router:   false,
1033		toolbar:  'image-details',
1034		editing:  false,
1035		priority: 60
1036	}, Library.prototype.defaults ),
1037
1038	/**
1039	 * @since 3.9.0
1040	 *
1041	 * @param options Attributes
1042	 */
1043	initialize: function( options ) {
1044		this.image = options.image;
1045		State.prototype.initialize.apply( this, arguments );
1046	},
1047
1048	/**
1049	 * @since 3.9.0
1050	 */
1051	activate: function() {
1052		this.frame.modal.$el.addClass('image-details');
1053	}
1054});
1055
1056module.exports = ImageDetails;
1057
1058},{}],11:[function(require,module,exports){
1059/**
1060 * wp.media.controller.Library
1061 *
1062 * A state for choosing an attachment or group of attachments from the media library.
1063 *
1064 * @class
1065 * @augments wp.media.controller.State
1066 * @augments Backbone.Model
1067 * @mixes media.selectionSync
1068 *
1069 * @param {object}                          [attributes]                         The attributes hash passed to the state.
1070 * @param {string}                          [attributes.id=library]              Unique identifier.
1071 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
1072 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
1073 *                                                                               If one is not supplied, a collection of all attachments will be created.
1074 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
1075 *                                                                               If the 'selection' attribute is a plain JS object,
1076 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
1077 *                                                                               Otherwise, it will copy the library's `props` model.
1078 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
1079 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
1080 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
1081 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
1082 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
1083 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
1084 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
1085 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
1086 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
1087 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1088 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1089 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1090 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1091 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1092 */
1093var l10n = wp.media.view.l10n,
1094	getUserSetting = window.getUserSetting,
1095	setUserSetting = window.setUserSetting,
1096	Library;
1097
1098Library = wp.media.controller.State.extend({
1099	defaults: {
1100		id:                 'library',
1101		title:              l10n.mediaLibraryTitle,
1102		multiple:           false,
1103		content:            'upload',
1104		menu:               'default',
1105		router:             'browse',
1106		toolbar:            'select',
1107		searchable:         true,
1108		filterable:         false,
1109		sortable:           true,
1110		autoSelect:         true,
1111		describe:           false,
1112		contentUserSetting: true,
1113		syncSelection:      true
1114	},
1115
1116	/**
1117	 * If a library isn't provided, query all media items.
1118	 * If a selection instance isn't provided, create one.
1119	 *
1120	 * @since 3.5.0
1121	 */
1122	initialize: function() {
1123		var selection = this.get('selection'),
1124			props;
1125
1126		if ( ! this.get('library') ) {
1127			this.set( 'library', wp.media.query() );
1128		}
1129
1130		if ( ! ( selection instanceof wp.media.model.Selection ) ) {
1131			props = selection;
1132
1133			if ( ! props ) {
1134				props = this.get('library').props.toJSON();
1135				props = _.omit( props, 'orderby', 'query' );
1136			}
1137
1138			this.set( 'selection', new wp.media.model.Selection( null, {
1139				multiple: this.get('multiple'),
1140				props: props
1141			}) );
1142		}
1143
1144		this.resetDisplays();
1145	},
1146
1147	/**
1148	 * @since 3.5.0
1149	 */
1150	activate: function() {
1151		this.syncSelection();
1152
1153		wp.Uploader.queue.on( 'add', this.uploading, this );
1154
1155		this.get('selection').on( 'add remove reset', this.refreshContent, this );
1156
1157		if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
1158			this.frame.on( 'content:activate', this.saveContentMode, this );
1159			this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
1160		}
1161	},
1162
1163	/**
1164	 * @since 3.5.0
1165	 */
1166	deactivate: function() {
1167		this.recordSelection();
1168
1169		this.frame.off( 'content:activate', this.saveContentMode, this );
1170
1171		// Unbind all event handlers that use this state as the context
1172		// from the selection.
1173		this.get('selection').off( null, null, this );
1174
1175		wp.Uploader.queue.off( null, null, this );
1176	},
1177
1178	/**
1179	 * Reset the library to its initial state.
1180	 *
1181	 * @since 3.5.0
1182	 */
1183	reset: function() {
1184		this.get('selection').reset();
1185		this.resetDisplays();
1186		this.refreshContent();
1187	},
1188
1189	/**
1190	 * Reset the attachment display settings defaults to the site options.
1191	 *
1192	 * If site options don't define them, fall back to a persistent user setting.
1193	 *
1194	 * @since 3.5.0
1195	 */
1196	resetDisplays: function() {
1197		var defaultProps = wp.media.view.settings.defaultProps;
1198		this._displays = [];
1199		this._defaultDisplaySettings = {
1200			align: getUserSetting( 'align', defaultProps.align ) || 'none',
1201			size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
1202			link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
1203		};
1204	},
1205
1206	/**
1207	 * Create a model to represent display settings (alignment, etc.) for an attachment.
1208	 *
1209	 * @since 3.5.0
1210	 *
1211	 * @param {wp.media.model.Attachment} attachment
1212	 * @returns {Backbone.Model}
1213	 */
1214	display: function( attachment ) {
1215		var displays = this._displays;
1216
1217		if ( ! displays[ attachment.cid ] ) {
1218			displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
1219		}
1220		return displays[ attachment.cid ];
1221	},
1222
1223	/**
1224	 * Given an attachment, create attachment display settings properties.
1225	 *
1226	 * @since 3.6.0
1227	 *
1228	 * @param {wp.media.model.Attachment} attachment
1229	 * @returns {Object}
1230	 */
1231	defaultDisplaySettings: function( attachment ) {
1232		var settings = _.clone( this._defaultDisplaySettings );
1233
1234		if ( settings.canEmbed = this.canEmbed( attachment ) ) {
1235			settings.link = 'embed';
1236		} else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
1237			settings.link = 'file';
1238		}
1239
1240		return settings;
1241	},
1242
1243	/**
1244	 * Whether an attachment is image.
1245	 *
1246	 * @since 4.4.1
1247	 *
1248	 * @param {wp.media.model.Attachment} attachment
1249	 * @returns {Boolean}
1250	 */
1251	isImageAttachment: function( attachment ) {
1252		// If uploading, we know the filename but not the mime type.
1253		if ( attachment.get('uploading') ) {
1254			return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
1255		}
1256
1257		return attachment.get('type') === 'image';
1258	},
1259
1260	/**
1261	 * Whether an attachment can be embedded (audio or video).
1262	 *
1263	 * @since 3.6.0
1264	 *
1265	 * @param {wp.media.model.Attachment} attachment
1266	 * @returns {Boolean}
1267	 */
1268	canEmbed: function( attachment ) {
1269		// If uploading, we know the filename but not the mime type.
1270		if ( ! attachment.get('uploading') ) {
1271			var type = attachment.get('type');
1272			if ( type !== 'audio' && type !== 'video' ) {
1273				return false;
1274			}
1275		}
1276
1277		return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
1278	},
1279
1280
1281	/**
1282	 * If the state is active, no items are selected, and the current
1283	 * content mode is not an option in the state's router (provided
1284	 * the state has a router), reset the content mode to the default.
1285	 *
1286	 * @since 3.5.0
1287	 */
1288	refreshContent: function() {
1289		var selection = this.get('selection'),
1290			frame = this.frame,
1291			router = frame.router.get(),
1292			mode = frame.content.mode();
1293
1294		if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
1295			this.frame.content.render( this.get('content') );
1296		}
1297	},
1298
1299	/**
1300	 * Callback handler when an attachment is uploaded.
1301	 *
1302	 * Switch to the Media Library if uploaded from the 'Upload Files' tab.
1303	 *
1304	 * Adds any uploading attachments to the selection.
1305	 *
1306	 * If the state only supports one attachment to be selected and multiple
1307	 * attachments are uploaded, the last attachment in the upload queue will
1308	 * be selected.
1309	 *
1310	 * @since 3.5.0
1311	 *
1312	 * @param {wp.media.model.Attachment} attachment
1313	 */
1314	uploading: function( attachment ) {
1315		var content = this.frame.content;
1316
1317		if ( 'upload' === content.mode() ) {
1318			this.frame.content.mode('browse');
1319		}
1320
1321		if ( this.get( 'autoSelect' ) ) {
1322			this.get('selection').add( attachment );
1323			this.frame.trigger( 'library:selection:add' );
1324		}
1325	},
1326
1327	/**
1328	 * Persist the mode of the content region as a user setting.
1329	 *
1330	 * @since 3.5.0
1331	 */
1332	saveContentMode: function() {
1333		if ( 'browse' !== this.get('router') ) {
1334			return;
1335		}
1336
1337		var mode = this.frame.content.mode(),
1338			view = this.frame.router.get();
1339
1340		if ( view && view.get( mode ) ) {
1341			setUserSetting( 'libraryContent', mode );
1342		}
1343	}
1344});
1345
1346// Make selectionSync available on any Media Library state.
1347_.extend( Library.prototype, wp.media.selectionSync );
1348
1349module.exports = Library;
1350
1351},{}],12:[function(require,module,exports){
1352/**
1353 * wp.media.controller.MediaLibrary
1354 *
1355 * @class
1356 * @augments wp.media.controller.Library
1357 * @augments wp.media.controller.State
1358 * @augments Backbone.Model
1359 */
1360var Library = wp.media.controller.Library,
1361	MediaLibrary;
1362
1363MediaLibrary = Library.extend({
1364	defaults: _.defaults({
1365		// Attachments browser defaults. @see media.view.AttachmentsBrowser
1366		filterable:      'uploaded',
1367
1368		displaySettings: false,
1369		priority:        80,
1370		syncSelection:   false
1371	}, Library.prototype.defaults ),
1372
1373	/**
1374	 * @since 3.9.0
1375	 *
1376	 * @param options
1377	 */
1378	initialize: function( options ) {
1379		this.media = options.media;
1380		this.type = options.type;
1381		this.set( 'library', wp.media.query({ type: this.type }) );
1382
1383		Library.prototype.initialize.apply( this, arguments );
1384	},
1385
1386	/**
1387	 * @since 3.9.0
1388	 */
1389	activate: function() {
1390		// @todo this should use this.frame.
1391		if ( wp.media.frame.lastMime ) {
1392			this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
1393			delete wp.media.frame.lastMime;
1394		}
1395		Library.prototype.activate.apply( this, arguments );
1396	}
1397});
1398
1399module.exports = MediaLibrary;
1400
1401},{}],13:[function(require,module,exports){
1402/**
1403 * wp.media.controller.Region
1404 *
1405 * A region is a persistent application layout area.
1406 *
1407 * A region assumes one mode at any time, and can be switched to another.
1408 *
1409 * When mode changes, events are triggered on the region's parent view.
1410 * The parent view will listen to specific events and fill the region with an
1411 * appropriate view depending on mode. For example, a frame listens for the
1412 * 'browse' mode t be activated on the 'content' view and then fills the region
1413 * with an AttachmentsBrowser view.
1414 *
1415 * @class
1416 *
1417 * @param {object}        options          Options hash for the region.
1418 * @param {string}        options.id       Unique identifier for the region.
1419 * @param {Backbone.View} options.view     A parent view the region exists within.
1420 * @param {string}        options.selector jQuery selector for the region within the parent view.
1421 */
1422var Region = function( options ) {
1423	_.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
1424};
1425
1426// Use Backbone's self-propagating `extend` inheritance method.
1427Region.extend = Backbone.Model.extend;
1428
1429_.extend( Region.prototype, {
1430	/**
1431	 * Activate a mode.
1432	 *
1433	 * @since 3.5.0
1434	 *
1435	 * @param {string} mode
1436	 *
1437	 * @fires this.view#{this.id}:activate:{this._mode}
1438	 * @fires this.view#{this.id}:activate
1439	 * @fires this.view#{this.id}:deactivate:{this._mode}
1440	 * @fires this.view#{this.id}:deactivate
1441	 *
1442	 * @returns {wp.media.controller.Region} Returns itself to allow chaining.
1443	 */
1444	mode: function( mode ) {
1445		if ( ! mode ) {
1446			return this._mode;
1447		}
1448		// Bail if we're trying to change to the current mode.
1449		if ( mode === this._mode ) {
1450			return this;
1451		}
1452
1453		/**
1454		 * Region mode deactivation event.
1455		 *
1456		 * @event this.view#{this.id}:deactivate:{this._mode}
1457		 * @event this.view#{this.id}:deactivate
1458		 */
1459		this.trigger('deactivate');
1460
1461		this._mode = mode;
1462		this.render( mode );
1463
1464		/**
1465		 * Region mode activation event.
1466		 *
1467		 * @event this.view#{this.id}:activate:{this._mode}
1468		 * @event this.view#{this.id}:activate
1469		 */
1470		this.trigger('activate');
1471		return this;
1472	},
1473	/**
1474	 * Render a mode.
1475	 *
1476	 * @since 3.5.0
1477	 *
1478	 * @param {string} mode
1479	 *
1480	 * @fires this.view#{this.id}:create:{this._mode}
1481	 * @fires this.view#{this.id}:create
1482	 * @fires this.view#{this.id}:render:{this._mode}
1483	 * @fires this.view#{this.id}:render
1484	 *
1485	 * @returns {wp.media.controller.Region} Returns itself to allow chaining
1486	 */
1487	render: function( mode ) {
1488		// If the mode isn't active, activate it.
1489		if ( mode && mode !== this._mode ) {
1490			return this.mode( mode );
1491		}
1492
1493		var set = { view: null },
1494			view;
1495
1496		/**
1497		 * Create region view event.
1498		 *
1499		 * Region view creation takes place in an event callback on the frame.
1500		 *
1501		 * @event this.view#{this.id}:create:{this._mode}
1502		 * @event this.view#{this.id}:create
1503		 */
1504		this.trigger( 'create', set );
1505		view = set.view;
1506
1507		/**
1508		 * Render region view event.
1509		 *
1510		 * Region view creation takes place in an event callback on the frame.
1511		 *
1512		 * @event this.view#{this.id}:create:{this._mode}
1513		 * @event this.view#{this.id}:create
1514		 */
1515		this.trigger( 'render', view );
1516		if ( view ) {
1517			this.set( view );
1518		}
1519		return this;
1520	},
1521
1522	/**
1523	 * Get the region's view.
1524	 *
1525	 * @since 3.5.0
1526	 *
1527	 * @returns {wp.media.View}
1528	 */
1529	get: function() {
1530		return this.view.views.first( this.selector );
1531	},
1532
1533	/**
1534	 * Set the region's view as a subview of the frame.
1535	 *
1536	 * @since 3.5.0
1537	 *
1538	 * @param {Array|Object} views
1539	 * @param {Object} [options={}]
1540	 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
1541	 */
1542	set: function( views, options ) {
1543		if ( options ) {
1544			options.add = false;
1545		}
1546		return this.view.views.set( this.selector, views, options );
1547	},
1548
1549	/**
1550	 * Trigger regional view events on the frame.
1551	 *
1552	 * @since 3.5.0
1553	 *
1554	 * @param {string} event
1555	 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
1556	 */
1557	trigger: function( event ) {
1558		var base, args;
1559
1560		if ( ! this._mode ) {
1561			return;
1562		}
1563
1564		args = _.toArray( arguments );
1565		base = this.id + ':' + event;
1566
1567		// Trigger `{this.id}:{event}:{this._mode}` event on the frame.
1568		args[0] = base + ':' + this._mode;
1569		this.view.trigger.apply( this.view, args );
1570
1571		// Trigger `{this.id}:{event}` event on the frame.
1572		args[0] = base;
1573		this.view.trigger.apply( this.view, args );
1574		return this;
1575	}
1576});
1577
1578module.exports = Region;
1579
1580},{}],14:[function(require,module,exports){
1581/**
1582 * wp.media.controller.ReplaceImage
1583 *
1584 * A state for replacing an image.
1585 *
1586 * @class
1587 * @augments wp.media.controller.Library
1588 * @augments wp.media.controller.State
1589 * @augments Backbone.Model
1590 *
1591 * @param {object}                     [attributes]                         The attributes hash passed to the state.
1592 * @param {string}                     [attributes.id=replace-image]        Unique identifier.
1593 * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
1594 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
1595 *                                                                          If one is not supplied, a collection of all images will be created.
1596 * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
1597 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
1598 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
1599 * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
1600 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
1601 * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
1602 * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
1603 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
1604 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
1605 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
1606 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1607 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1608 * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1609 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1610 * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1611 */
1612var Library = wp.media.controller.Library,
1613	l10n = wp.media.view.l10n,
1614	ReplaceImage;
1615
1616ReplaceImage = Library.extend({
1617	defaults: _.defaults({
1618		id:            'replace-image',
1619		title:         l10n.replaceImageTitle,
1620		multiple:      false,
1621		filterable:    'uploaded',
1622		toolbar:       'replace',
1623		menu:          false,
1624		priority:      60,
1625		syncSelection: true
1626	}, Library.prototype.defaults ),
1627
1628	/**
1629	 * @since 3.9.0
1630	 *
1631	 * @param options
1632	 */
1633	initialize: function( options ) {
1634		var library, comparator;
1635
1636		this.image = options.image;
1637		// If we haven't been provided a `library`, create a `Selection`.
1638		if ( ! this.get('library') ) {
1639			this.set( 'library', wp.media.query({ type: 'image' }) );
1640		}
1641
1642		Library.prototype.initialize.apply( this, arguments );
1643
1644		library    = this.get('library');
1645		comparator = library.comparator;
1646
1647		// Overload the library's comparator to push items that are not in
1648		// the mirrored query to the front of the aggregate collection.
1649		library.comparator = function( a, b ) {
1650			var aInQuery = !! this.mirroring.get( a.cid ),
1651				bInQuery = !! this.mirroring.get( b.cid );
1652
1653			if ( ! aInQuery && bInQuery ) {
1654				return -1;
1655			} else if ( aInQuery && ! bInQuery ) {
1656				return 1;
1657			} else {
1658				return comparator.apply( this, arguments );
1659			}
1660		};
1661
1662		// Add all items in the selection to the library, so any featured
1663		// images that are not initially loaded still appear.
1664		library.observe( this.get('selection') );
1665	},
1666
1667	/**
1668	 * @since 3.9.0
1669	 */
1670	activate: function() {
1671		this.updateSelection();
1672		Library.prototype.activate.apply( this, arguments );
1673	},
1674
1675	/**
1676	 * @since 3.9.0
1677	 */
1678	updateSelection: function() {
1679		var selection = this.get('selection'),
1680			attachment = this.image.attachment;
1681
1682		selection.reset( attachment ? [ attachment ] : [] );
1683	}
1684});
1685
1686module.exports = ReplaceImage;
1687
1688},{}],15:[function(require,module,exports){
1689/**
1690 * wp.media.controller.SiteIconCropper
1691 *
1692 * A state for cropping a Site Icon.
1693 *
1694 * @class
1695 * @augments wp.media.controller.Cropper
1696 * @augments wp.media.controller.State
1697 * @augments Backbone.Model
1698 */
1699var Controller = wp.media.controller,
1700	SiteIconCropper;
1701
1702SiteIconCropper = Controller.Cropper.extend({
1703	activate: function() {
1704		this.frame.on( 'content:create:crop', this.createCropContent, this );
1705		this.frame.on( 'close', this.removeCropper, this );
1706		this.set('selection', new Backbone.Collection(this.frame._selection.single));
1707	},
1708
1709	createCropContent: function() {
1710		this.cropperView = new wp.media.view.SiteIconCropper({
1711			controller: this,
1712			attachment: this.get('selection').first()
1713		});
1714		this.cropperView.on('image-loaded', this.createCropToolbar, this);
1715		this.frame.content.set(this.cropperView);
1716
1717	},
1718
1719	doCrop: function( attachment ) {
1720		var cropDetails = attachment.get( 'cropDetails' ),
1721			control = this.get( 'control' );
1722
1723		cropDetails.dst_width  = control.params.width;
1724		cropDetails.dst_height = control.params.height;
1725
1726		return wp.ajax.post( 'crop-image', {
1727			nonce: attachment.get( 'nonces' ).edit,
1728			id: attachment.get( 'id' ),
1729			context: 'site-icon',
1730			cropDetails: cropDetails
1731		} );
1732	}
1733});
1734
1735module.exports = SiteIconCropper;
1736
1737},{}],16:[function(require,module,exports){
1738/**
1739 * wp.media.controller.StateMachine
1740 *
1741 * A state machine keeps track of state. It is in one state at a time,
1742 * and can change from one state to another.
1743 *
1744 * States are stored as models in a Backbone collection.
1745 *
1746 * @since 3.5.0
1747 *
1748 * @class
1749 * @augments Backbone.Model
1750 * @mixin
1751 * @mixes Backbone.Events
1752 *
1753 * @param {Array} states
1754 */
1755var StateMachine = function( states ) {
1756	// @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
1757	this.states = new Backbone.Collection( states );
1758};
1759
1760// Use Backbone's self-propagating `extend` inheritance method.
1761StateMachine.extend = Backbone.Model.extend;
1762
1763_.extend( StateMachine.prototype, Backbone.Events, {
1764	/**
1765	 * Fetch a state.
1766	 *
1767	 * If no `id` is provided, returns the active state.
1768	 *
1769	 * Implicitly creates states.
1770	 *
1771	 * Ensure that the `states` collection exists so the `StateMachine`
1772	 *   can be used as a mixin.
1773	 *
1774	 * @since 3.5.0
1775	 *
1776	 * @param {string} id
1777	 * @returns {wp.media.controller.State} Returns a State model
1778	 *   from the StateMachine collection
1779	 */
1780	state: function( id ) {
1781		this.states = this.states || new Backbone.Collection();
1782
1783		// Default to the active state.
1784		id = id || this._state;
1785
1786		if ( id && ! this.states.get( id ) ) {
1787			this.states.add({ id: id });
1788		}
1789		return this.states.get( id );
1790	},
1791
1792	/**
1793	 * Sets the active state.
1794	 *
1795	 * Bail if we're trying to select the current state, if we haven't
1796	 * created the `states` collection, or are trying to select a state
1797	 * that does not exist.
1798	 *
1799	 * @since 3.5.0
1800	 *
1801	 * @param {string} id
1802	 *
1803	 * @fires wp.media.controller.State#deactivate
1804	 * @fires wp.media.controller.State#activate
1805	 *
1806	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
1807	 */
1808	setState: function( id ) {
1809		var previous = this.state();
1810
1811		if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
1812			return this;
1813		}
1814
1815		if ( previous ) {
1816			previous.trigger('deactivate');
1817			this._lastState = previous.id;
1818		}
1819
1820		this._state = id;
1821		this.state().trigger('activate');
1822
1823		return this;
1824	},
1825
1826	/**
1827	 * Returns the previous active state.
1828	 *
1829	 * Call the `state()` method with no parameters to retrieve the current
1830	 * active state.
1831	 *
1832	 * @since 3.5.0
1833	 *
1834	 * @returns {wp.media.controller.State} Returns a State model
1835	 *    from the StateMachine collection
1836	 */
1837	lastState: function() {
1838		if ( this._lastState ) {
1839			return this.state( this._lastState );
1840		}
1841	}
1842});
1843
1844// Map all event binding and triggering on a StateMachine to its `states` collection.
1845_.each([ 'on', 'off', 'trigger' ], function( method ) {
1846	/**
1847	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
1848	 */
1849	StateMachine.prototype[ method ] = function() {
1850		// Ensure that the `states` collection exists so the `StateMachine`
1851		// can be used as a mixin.
1852		this.states = this.states || new Backbone.Collection();
1853		// Forward the method to the `states` collection.
1854		this.states[ method ].apply( this.states, arguments );
1855		return this;
1856	};
1857});
1858
1859module.exports = StateMachine;
1860
1861},{}],17:[function(require,module,exports){
1862/**
1863 * wp.media.controller.State
1864 *
1865 * A state is a step in a workflow that when set will trigger the controllers
1866 * for the regions to be updated as specified in the frame.
1867 *
1868 * A state has an event-driven lifecycle:
1869 *
1870 *     'ready'      triggers when a state is added to a state machine's collection.
1871 *     'activate'   triggers when a state is activated by a state machine.
1872 *     'deactivate' triggers when a state is deactivated by a state machine.
1873 *     'reset'      is not triggered automatically. It should be invoked by the
1874 *                  proper controller to reset the state to its default.
1875 *
1876 * @class
1877 * @augments Backbone.Model
1878 */
1879var State = Backbone.Model.extend({
1880	/**
1881	 * Constructor.
1882	 *
1883	 * @since 3.5.0
1884	 */
1885	constructor: function() {
1886		this.on( 'activate', this._preActivate, this );
1887		this.on( 'activate', this.activate, this );
1888		this.on( 'activate', this._postActivate, this );
1889		this.on( 'deactivate', this._deactivate, this );
1890		this.on( 'deactivate', this.deactivate, this );
1891		this.on( 'reset', this.reset, this );
1892		this.on( 'ready', this._ready, this );
1893		this.on( 'ready', this.ready, this );
1894		/**
1895		 * Call parent constructor with passed arguments
1896		 */
1897		Backbone.Model.apply( this, arguments );
1898		this.on( 'change:menu', this._updateMenu, this );
1899	},
1900	/**
1901	 * Ready event callback.
1902	 *
1903	 * @abstract
1904	 * @since 3.5.0
1905	 */
1906	ready: function() {},
1907
1908	/**
1909	 * Activate event callback.
1910	 *
1911	 * @abstract
1912	 * @since 3.5.0
1913	 */
1914	activate: function() {},
1915
1916	/**
1917	 * Deactivate event callback.
1918	 *
1919	 * @abstract
1920	 * @since 3.5.0
1921	 */
1922	deactivate: function() {},
1923
1924	/**
1925	 * Reset event callback.
1926	 *
1927	 * @abstract
1928	 * @since 3.5.0
1929	 */
1930	reset: function() {},
1931
1932	/**
1933	 * @access private
1934	 * @since 3.5.0
1935	 */
1936	_ready: function() {
1937		this._updateMenu();
1938	},
1939
1940	/**
1941	 * @access private
1942	 * @since 3.5.0
1943	*/
1944	_preActivate: function() {
1945		this.active = true;
1946	},
1947
1948	/**
1949	 * @access private
1950	 * @since 3.5.0
1951	 */
1952	_postActivate: function() {
1953		this.on( 'change:menu', this._menu, this );
1954		this.on( 'change:titleMode', this._title, this );
1955		this.on( 'change:content', this._content, this );
1956		this.on( 'change:toolbar', this._toolbar, this );
1957
1958		this.frame.on( 'title:render:default', this._renderTitle, this );
1959
1960		this._title();
1961		this._menu();
1962		this._toolbar();
1963		this._content();
1964		this._router();
1965	},
1966
1967	/**
1968	 * @access private
1969	 * @since 3.5.0
1970	 */
1971	_deactivate: function() {
1972		this.active = false;
1973
1974		this.frame.off( 'title:render:default', this._renderTitle, this );
1975
1976		this.off( 'change:menu', this._menu, this );
1977		this.off( 'change:titleMode', this._title, this );
1978		this.off( 'change:content', this._content, this );
1979		this.off( 'change:toolbar', this._toolbar, this );
1980	},
1981
1982	/**
1983	 * @access private
1984	 * @since 3.5.0
1985	 */
1986	_title: function() {
1987		this.frame.title.render( this.get('titleMode') || 'default' );
1988	},
1989
1990	/**
1991	 * @access private
1992	 * @since 3.5.0
1993	 */
1994	_renderTitle: function( view ) {
1995		view.$el.text( this.get('title') || '' );
1996	},
1997
1998	/**
1999	 * @access private
2000	 * @since 3.5.0
2001	 */
2002	_router: function() {
2003		var router = this.frame.router,
2004			mode = this.get('router'),
2005			view;
2006
2007		this.frame.$el.toggleClass( 'hide-router', ! mode );
2008		if ( ! mode ) {
2009			return;
2010		}
2011
2012		this.frame.router.render( mode );
2013
2014		view = router.get();
2015		if ( view && view.select ) {
2016			view.select( this.frame.content.mode() );
2017		}
2018	},
2019
2020	/**
2021	 * @access private
2022	 * @since 3.5.0
2023	 */
2024	_menu: function() {
2025		var menu = this.frame.menu,
2026			mode = this.get('menu'),
2027			view;
2028
2029		this.frame.$el.toggleClass( 'hide-menu', ! mode );
2030		if ( ! mode ) {
2031			return;
2032		}
2033
2034		menu.mode( mode );
2035
2036		view = menu.get();
2037		if ( view && view.select ) {
2038			view.select( this.id );
2039		}
2040	},
2041
2042	/**
2043	 * @access private
2044	 * @since 3.5.0
2045	 */
2046	_updateMenu: function() {
2047		var previous = this.previous('menu'),
2048			menu = this.get('menu');
2049
2050		if ( previous ) {
2051			this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
2052		}
2053
2054		if ( menu ) {
2055			this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
2056		}
2057	},
2058
2059	/**
2060	 * Create a view in the media menu for the state.
2061	 *
2062	 * @access private
2063	 * @since 3.5.0
2064	 *
2065	 * @param {media.view.Menu} view The menu view.
2066	 */
2067	_renderMenu: function( view ) {
2068		var menuItem = this.get('menuItem'),
2069			title = this.get('title'),
2070			priority = this.get('priority');
2071
2072		if ( ! menuItem && title ) {
2073			menuItem = { text: title };
2074
2075			if ( priority ) {
2076				menuItem.priority = priority;
2077			}
2078		}
2079
2080		if ( ! menuItem ) {
2081			return;
2082		}
2083
2084		view.set( this.id, menuItem );
2085	}
2086});
2087
2088_.each(['toolbar','content'], function( region ) {
2089	/**
2090	 * @access private
2091	 */
2092	State.prototype[ '_' + region ] = function() {
2093		var mode = this.get( region );
2094		if ( mode ) {
2095			this.frame[ region ].render( mode );
2096		}
2097	};
2098});
2099
2100module.exports = State;
2101
2102},{}],18:[function(require,module,exports){
2103/**
2104 * wp.media.selectionSync
2105 *
2106 * Sync an attachments selection in a state with another state.
2107 *
2108 * Allows for selecting multiple images in the Insert Media workflow, and then
2109 * switching to the Insert Gallery workflow while preserving the attachments selection.
2110 *
2111 * @mixin
2112 */
2113var selectionSync = {
2114	/**
2115	 * @since 3.5.0
2116	 */
2117	syncSelection: function() {
2118		var selection = this.get('selection'),
2119			manager = this.frame._selection;
2120
2121		if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2122			return;
2123		}
2124
2125		// If the selection supports multiple items, validate the stored
2126		// attachments based on the new selection's conditions. Record
2127		// the attachments that are not included; we'll maintain a
2128		// reference to those. Other attachments are considered in flux.
2129		if ( selection.multiple ) {
2130			selection.reset( [], { silent: true });
2131			selection.validateAll( manager.attachments );
2132			manager.difference = _.difference( manager.attachments.models, selection.models );
2133		}
2134
2135		// Sync the selection's single item with the master.
2136		selection.single( manager.single );
2137	},
2138
2139	/**
2140	 * Record the currently active attachments, which is a combination
2141	 * of the selection's attachments and the set of selected
2142	 * attachments that this specific selection considered invalid.
2143	 * Reset the difference and record the single attachment.
2144	 *
2145	 * @since 3.5.0
2146	 */
2147	recordSelection: function() {
2148		var selection = this.get('selection'),
2149			manager = this.frame._selection;
2150
2151		if ( ! this.get('syncSelection') || ! manager || ! selection ) {
2152			return;
2153		}
2154
2155		if ( selection.multiple ) {
2156			manager.attachments.reset( selection.toArray().concat( manager.difference ) );
2157			manager.difference = [];
2158		} else {
2159			manager.attachments.add( selection.toArray() );
2160		}
2161
2162		manager.single = selection._single;
2163	}
2164};
2165
2166module.exports = selectionSync;
2167
2168},{}],19:[function(require,module,exports){
2169var media = wp.media,
2170	$ = jQuery,
2171	l10n;
2172
2173media.isTouchDevice = ( 'ontouchend' in document );
2174
2175// Link any localized strings.
2176l10n = media.view.l10n = window._wpMediaViewsL10n || {};
2177
2178// Link any settings.
2179media.view.settings = l10n.settings || {};
2180delete l10n.settings;
2181
2182// Copy the `post` setting over to the model settings.
2183media.model.settings.post = media.view.settings.post;
2184
2185// Check if the browser supports CSS 3.0 transitions
2186$.support.transition = (function(){
2187	var style = document.documentElement.style,
2188		transitions = {
2189			WebkitTransition: 'webkitTransitionEnd',
2190			MozTransition:    'transitionend',
2191			OTransition:      'oTransitionEnd otransitionend',
2192			transition:       'transitionend'
2193		}, transition;
2194
2195	transition = _.find( _.keys( transitions ), function( transition ) {
2196		return ! _.isUndefined( style[ transition ] );
2197	});
2198
2199	return transition && {
2200		end: transitions[ transition ]
2201	};
2202}());
2203
2204/**
2205 * A shared event bus used to provide events into
2206 * the media workflows that 3rd-party devs can use to hook
2207 * in.
2208 */
2209media.events = _.extend( {}, Backbone.Events );
2210
2211/**
2212 * Makes it easier to bind events using transitions.
2213 *
2214 * @param {string} selector
2215 * @param {Number} sensitivity
2216 * @returns {Promise}
2217 */
2218media.transition = function( selector, sensitivity ) {
2219	var deferred = $.Deferred();
2220
2221	sensitivity = sensitivity || 2000;
2222
2223	if ( $.support.transition ) {
2224		if ( ! (selector instanceof $) ) {
2225			selector = $( selector );
2226		}
2227
2228		// Resolve the deferred when the first element finishes animating.
2229		selector.first().one( $.support.transition.end, deferred.resolve );
2230
2231		// Just in case the event doesn't trigger, fire a callback.
2232		_.delay( deferred.resolve, sensitivity );
2233
2234	// Otherwise, execute on the spot.
2235	} else {
2236		deferred.resolve();
2237	}
2238
2239	return deferred.promise();
2240};
2241
2242media.controller.Region = require( './controllers/region.js' );
2243media.controller.StateMachine = require( './controllers/state-machine.js' );
2244media.controller.State = require( './controllers/state.js' );
2245
2246media.selectionSync = require( './utils/selection-sync.js' );
2247media.controller.Library = require( './controllers/library.js' );
2248media.controller.ImageDetails = require( './controllers/image-details.js' );
2249media.controller.GalleryEdit = require( './controllers/gallery-edit.js' );
2250media.controller.GalleryAdd = require( './controllers/gallery-add.js' );
2251media.controller.CollectionEdit = require( './controllers/collection-edit.js' );
2252media.controller.CollectionAdd = require( './controllers/collection-add.js' );
2253media.controller.FeaturedImage = require( './controllers/featured-image.js' );
2254media.controller.ReplaceImage = require( './controllers/replace-image.js' );
2255media.controller.EditImage = require( './controllers/edit-image.js' );
2256media.controller.MediaLibrary = require( './controllers/media-library.js' );
2257media.controller.Embed = require( './controllers/embed.js' );
2258media.controller.Cropper = require( './controllers/cropper.js' );
2259media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' );
2260media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' );
2261
2262media.View = require( './views/view.js' );
2263media.view.Frame = require( './views/frame.js' );
2264media.view.MediaFrame = require( './views/media-frame.js' );
2265media.view.MediaFrame.Select = require( './views/frame/select.js' );
2266media.view.MediaFrame.Post = require( './views/frame/post.js' );
2267media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' );
2268media.view.Modal = require( './views/modal.js' );
2269media.view.FocusManager = require( './views/focus-manager.js' );
2270media.view.UploaderWindow = require( './views/uploader/window.js' );
2271media.view.EditorUploader = require( './views/uploader/editor.js' );
2272media.view.UploaderInline = require( './views/uploader/inline.js' );
2273media.view.UploaderStatus = require( './views/uploader/status.js' );
2274media.view.UploaderStatusError = require( './views/uploader/status-error.js' );
2275media.view.Toolbar = require( './views/toolbar.js' );
2276media.view.Toolbar.Select = require( './views/toolbar/select.js' );
2277media.view.Toolbar.Embed = require( './views/toolbar/embed.js' );
2278media.view.Button = require( './views/button.js' );
2279media.view.ButtonGroup = require( './views/button-group.js' );
2280media.view.PriorityList = require( './views/priority-list.js' );
2281media.view.MenuItem = require( './views/menu-item.js' );
2282media.view.Menu = require( './views/menu.js' );
2283media.view.RouterItem = require( './views/router-item.js' );
2284media.view.Router = require( './views/router.js' );
2285media.view.Sidebar = require( './views/sidebar.js' );
2286media.view.Attachment = require( './views/attachment.js' );
2287media.view.Attachment.Library = require( './views/attachment/library.js' );
2288media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' );
2289media.view.Attachments = require( './views/attachments.js' );
2290media.view.Search = require( './views/search.js' );
2291media.view.AttachmentFilters = require( './views/attachment-filters.js' );
2292media.view.DateFilter = require( './views/attachment-filters/date.js' );
2293media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' );
2294media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' );
2295media.view.AttachmentsBrowser = require( './views/attachments/browser.js' );
2296media.view.Selection = require( './views/selection.js' );
2297media.view.Attachment.Selection = require( './views/attachment/selection.js' );
2298media.view.Attachments.Selection = require( './views/attachments/selection.js' );
2299media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' );
2300media.view.Settings = require( './views/settings.js' );
2301media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' );
2302media.view.Settings.Gallery = require( './views/settings/gallery.js' );
2303media.view.Settings.Playlist = require( './views/settings/playlist.js' );
2304media.view.Attachment.Details = require( './views/attachment/details.js' );
2305media.view.AttachmentCompat = require( './views/attachment-compat.js' );
2306media.view.Iframe = require( './views/iframe.js' );
2307media.view.Embed = require( './views/embed.js' );
2308media.view.Label = require( './views/label.js' );
2309media.view.EmbedUrl = require( './views/embed/url.js' );
2310media.view.EmbedLink = require( './views/embed/link.js' );
2311media.view.EmbedImage = require( './views/embed/image.js' );
2312media.view.ImageDetails = require( './views/image-details.js' );
2313media.view.Cropper = require( './views/cropper.js' );
2314media.view.SiteIconCropper = require( './views/site-icon-cropper.js' );
2315media.view.SiteIconPreview = require( './views/site-icon-preview.js' );
2316media.view.EditImage = require( './views/edit-image.js' );
2317media.view.Spinner = require( './views/spinner.js' );
2318
2319},{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){
2320/**
2321 * wp.media.view.AttachmentCompat
2322 *
2323 * A view to display fields added via the `attachment_fields_to_edit` filter.
2324 *
2325 * @class
2326 * @augments wp.media.View
2327 * @augments wp.Backbone.View
2328 * @augments Backbone.View
2329 */
2330var View = wp.media.View,
2331	AttachmentCompat;
2332
2333AttachmentCompat = View.extend({
2334	tagName:   'form',
2335	className: 'compat-item',
2336
2337	events: {
2338		'submit':          'preventDefault',
2339		'change input':    'save',
2340		'change select':   'save',
2341		'change textarea': 'save'
2342	},
2343
2344	initialize: function() {
2345		this.listenTo( this.model, 'change:compat', this.render );
2346	},
2347	/**
2348	 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
2349	 */
2350	dispose: function() {
2351		if ( this.$(':focus').length ) {
2352			this.save();
2353		}
2354		/**
2355		 * call 'dispose' directly on the parent class
2356		 */
2357		return View.prototype.dispose.apply( this, arguments );
2358	},
2359	/**
2360	 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
2361	 */
2362	render: function() {
2363		var compat = this.model.get('compat');
2364		if ( ! compat || ! compat.item ) {
2365			return;
2366		}
2367
2368		this.views.detach();
2369		this.$el.html( compat.item );
2370		this.views.render();
2371		return this;
2372	},
2373	/**
2374	 * @param {Object} event
2375	 */
2376	preventDefault: function( event ) {
2377		event.preventDefault();
2378	},
2379	/**
2380	 * @param {Object} event
2381	 */
2382	save: function( event ) {
2383		var data = {};
2384
2385		if ( event ) {
2386			event.preventDefault();
2387		}
2388
2389		_.each( this.$el.serializeArray(), function( pair ) {
2390			data[ pair.name ] = pair.value;
2391		});
2392
2393		this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
2394		this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
2395	},
2396
2397	postSave: function() {
2398		this.controller.trigger( 'attachment:compat:ready', ['ready'] );
2399	}
2400});
2401
2402module.exports = AttachmentCompat;
2403
2404},{}],21:[function(require,module,exports){
2405/**
2406 * wp.media.view.AttachmentFilters
2407 *
2408 * @class
2409 * @augments wp.media.View
2410 * @augments wp.Backbone.View
2411 * @augments Backbone.View
2412 */
2413var $ = jQuery,
2414	AttachmentFilters;
2415
2416AttachmentFilters = wp.media.View.extend({
2417	tagName:   'select',
2418	className: 'attachment-filters',
2419	id:        'media-attachment-filters',
2420
2421	events: {
2422		change: 'change'
2423	},
2424
2425	keys: [],
2426
2427	initialize: function() {
2428		this.createFilters();
2429		_.extend( this.filters, this.options.filters );
2430
2431		// Build `<option>` elements.
2432		this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
2433			return {
2434				el: $( '<option></option>' ).val( value ).html( filter.text )[0],
2435				priority: filter.priority || 50
2436			};
2437		}, this ).sortBy('priority').pluck('el').value() );
2438
2439		this.listenTo( this.model, 'change', this.select );
2440		this.select();
2441	},
2442
2443	/**
2444	 * @abstract
2445	 */
2446	createFilters: function() {
2447		this.filters = {};
2448	},
2449
2450	/**
2451	 * When the selected filter changes, update the Attachment Query properties to match.
2452	 */
2453	change: function() {
2454		var filter = this.filters[ this.el.value ];
2455		if ( filter ) {
2456			this.model.set( filter.props );
2457		}
2458	},
2459
2460	select: function() {
2461		var model = this.model,
2462			value = 'all',
2463			props = model.toJSON();
2464
2465		_.find( this.filters, function( filter, id ) {
2466			var equal = _.all( filter.props, function( prop, key ) {
2467				return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
2468			});
2469
2470			if ( equal ) {
2471				return value = id;
2472			}
2473		});
2474
2475		this.$el.val( value );
2476	}
2477});
2478
2479module.exports = AttachmentFilters;
2480
2481},{}],22:[function(require,module,exports){
2482/**
2483 * wp.media.view.AttachmentFilters.All
2484 *
2485 * @class
2486 * @augments wp.media.view.AttachmentFilters
2487 * @augments wp.media.View
2488 * @augments wp.Backbone.View
2489 * @augments Backbone.View
2490 */
2491var l10n = wp.media.view.l10n,
2492	All;
2493
2494All = wp.media.view.AttachmentFilters.extend({
2495	createFilters: function() {
2496		var filters = {};
2497
2498		_.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
2499			filters[ key ] = {
2500				text: text,
2501				props: {
2502					status:  null,
2503					type:    key,
2504					uploadedTo: null,
2505					orderby: 'date',
2506					order:   'DESC'
2507				}
2508			};
2509		});
2510
2511		filters.all = {
2512			text:  l10n.allMediaItems,
2513			props: {
2514				status:  null,
2515				type:    null,
2516				uploadedTo: null,
2517				orderby: 'date',
2518				order:   'DESC'
2519			},
2520			priority: 10
2521		};
2522
2523		if ( wp.media.view.settings.post.id ) {
2524			filters.uploaded = {
2525				text:  l10n.uploadedToThisPost,
2526				props: {
2527					status:  null,
2528					type:    null,
2529					uploadedTo: wp.media.view.settings.post.id,
2530					orderby: 'menuOrder',
2531					order:   'ASC'
2532				},
2533				priority: 20
2534			};
2535		}
2536
2537		filters.unattached = {
2538			text:  l10n.unattached,
2539			props: {
2540				status:     null,
2541				uploadedTo: 0,
2542				type:       null,
2543				orderby:    'menuOrder',
2544				order:      'ASC'
2545			},
2546			priority: 50
2547		};
2548
2549		if ( wp.media.view.settings.mediaTrash &&
2550			this.controller.isModeActive( 'grid' ) ) {
2551
2552			filters.trash = {
2553				text:  l10n.trash,
2554				props: {
2555					uploadedTo: null,
2556					status:     'trash',
2557					type:       null,
2558					orderby:    'date',
2559					order:      'DESC'
2560				},
2561				priority: 50
2562			};
2563		}
2564
2565		this.filters = filters;
2566	}
2567});
2568
2569module.exports = All;
2570
2571},{}],23:[function(require,module,exports){
2572/**
2573 * A filter dropdown for month/dates.
2574 *
2575 * @class
2576 * @augments wp.media.view.AttachmentFilters
2577 * @augments wp.media.View
2578 * @augments wp.Backbone.View
2579 * @augments Backbone.View
2580 */
2581var l10n = wp.media.view.l10n,
2582	DateFilter;
2583
2584DateFilter = wp.media.view.AttachmentFilters.extend({
2585	id: 'media-attachment-date-filters',
2586
2587	createFilters: function() {
2588		var filters = {};
2589		_.each( wp.media.view.settings.months || {}, function( value, index ) {
2590			filters[ index ] = {
2591				text: value.text,
2592				props: {
2593					year: value.year,
2594					monthnum: value.month
2595				}
2596			};
2597		});
2598		filters.all = {
2599			text:  l10n.allDates,
2600			props: {
2601				monthnum: false,
2602				year:  false
2603			},
2604			priority: 10
2605		};
2606		this.filters = filters;
2607	}
2608});
2609
2610module.exports = DateFilter;
2611
2612},{}],24:[function(require,module,exports){
2613/**
2614 * wp.media.view.AttachmentFilters.Uploaded
2615 *
2616 * @class
2617 * @augments wp.media.view.AttachmentFilters
2618 * @augments wp.media.View
2619 * @augments wp.Backbone.View
2620 * @augments Backbone.View
2621 */
2622var l10n = wp.media.view.l10n,
2623	Uploaded;
2624
2625Uploaded = wp.media.view.AttachmentFilters.extend({
2626	createFilters: function() {
2627		var type = this.model.get('type'),
2628			types = wp.media.view.settings.mimeTypes,
2629			text;
2630
2631		if ( types && type ) {
2632			text = types[ type ];
2633		}
2634
2635		this.filters = {
2636			all: {
2637				text:  text || l10n.allMediaItems,
2638				props: {
2639					uploadedTo: null,
2640					orderby: 'date',
2641					order:   'DESC'
2642				},
2643				priority: 10
2644			},
2645
2646			uploaded: {
2647				text:  l10n.uploadedToThisPost,
2648				props: {
2649					uploadedTo: wp.media.view.settings.post.id,
2650					orderby: 'menuOrder',
2651					order:   'ASC'
2652				},
2653				priority: 20
2654			},
2655
2656			unattached: {
2657				text:  l10n.unattached,
2658				props: {
2659					uploadedTo: 0,
2660					orderby: 'menuOrder',
2661					order:   'ASC'
2662				},
2663				priority: 50
2664			}
2665		};
2666	}
2667});
2668
2669module.exports = Uploaded;
2670
2671},{}],25:[function(require,module,exports){
2672/**
2673 * wp.media.view.Attachment
2674 *
2675 * @class
2676 * @augments wp.media.View
2677 * @augments wp.Backbone.View
2678 * @augments Backbone.View
2679 */
2680var View = wp.media.View,
2681	$ = jQuery,
2682	Attachment;
2683
2684Attachment = View.extend({
2685	tagName:   'li',
2686	className: 'attachment',
2687	template:  wp.template('attachment'),
2688
2689	attributes: function() {
2690		return {
2691			'tabIndex':     0,
2692			'role':         'checkbox',
2693			'aria-label':   this.model.get( 'title' ),
2694			'aria-checked': false,
2695			'data-id':      this.model.get( 'id' )
2696		};
2697	},
2698
2699	events: {
2700		'click .js--select-attachment':   'toggleSelectionHandler',
2701		'change [data-setting]':          'updateSetting',
2702		'change [data-setting] input':    'updateSetting',
2703		'change [data-setting] select':   'updateSetting',
2704		'change [data-setting] textarea': 'updateSetting',
2705		'click .attachment-close':        'removeFromLibrary',
2706		'click .check':                   'checkClickHandler',
2707		'keydown':                        'toggleSelectionHandler'
2708	},
2709
2710	buttons: {},
2711
2712	initialize: function() {
2713		var selection = this.options.selection,
2714			options = _.defaults( this.options, {
2715				rerenderOnModelChange: true
2716			} );
2717
2718		if ( options.rerenderOnModelChange ) {
2719			this.listenTo( this.model, 'change', this.render );
2720		} else {
2721			this.listenTo( this.model, 'change:percent', this.progress );
2722		}
2723		this.listenTo( this.model, 'change:title', this._syncTitle );
2724		this.listenTo( this.model, 'change:caption', this._syncCaption );
2725		this.listenTo( this.model, 'change:artist', this._syncArtist );
2726		this.listenTo( this.model, 'change:album', this._syncAlbum );
2727
2728		// Update the selection.
2729		this.listenTo( this.model, 'add', this.select );
2730		this.listenTo( this.model, 'remove', this.deselect );
2731		if ( selection ) {
2732			selection.on( 'reset', this.updateSelect, this );
2733			// Update the model's details view.
2734			this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
2735			this.details( this.model, this.controller.state().get('selection') );
2736		}
2737
2738		this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
2739	},
2740	/**
2741	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
2742	 */
2743	dispose: function() {
2744		var selection = this.options.selection;
2745
2746		// Make sure all settings are saved before removing the view.
2747		this.updateAll();
2748
2749		if ( selection ) {
2750			selection.off( null, null, this );
2751		}
2752		/**
2753		 * call 'dispose' directly on the parent class
2754		 */
2755		View.prototype.dispose.apply( this, arguments );
2756		return this;
2757	},
2758	/**
2759	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
2760	 */
2761	render: function() {
2762		var options = _.defaults( this.model.toJSON(), {
2763				orientation:   'landscape',
2764				uploading:     false,
2765				type:          '',
2766				subtype:       '',
2767				icon:          '',
2768				filename:      '',
2769				caption:       '',
2770				title:         '',
2771				dateFormatted: '',
2772				width:         '',
2773				height:        '',
2774				compat:        false,
2775				alt:           '',
2776				description:   ''
2777			}, this.options );
2778
2779		options.buttons  = this.buttons;
2780		options.describe = this.controller.state().get('describe');
2781
2782		if ( 'image' === options.type ) {
2783			options.size = this.imageSize();
2784		}
2785
2786		options.can = {};
2787		if ( options.nonces ) {
2788			options.can.remove = !! options.nonces['delete'];
2789			options.can.save = !! options.nonces.update;
2790		}
2791
2792		if ( this.controller.state().get('allowLocalEdits') ) {
2793			options.allowLocalEdits = true;
2794		}
2795
2796		if ( options.uploading && ! options.percent ) {
2797			options.percent = 0;
2798		}
2799
2800		this.views.detach();
2801		this.$el.html( this.template( options ) );
2802
2803		this.$el.toggleClass( 'uploading', options.uploading );
2804
2805		if ( options.uploading ) {
2806			this.$bar = this.$('.media-progress-bar div');
2807		} else {
2808			delete this.$bar;
2809		}
2810
2811		// Check if the model is selected.
2812		this.updateSelect();
2813
2814		// Update the save status.
2815		this.updateSave();
2816
2817		this.views.render();
2818
2819		return this;
2820	},
2821
2822	progress: function() {
2823		if ( this.$bar && this.$bar.length ) {
2824			this.$bar.width( this.model.get('percent') + '%' );
2825		}
2826	},
2827
2828	/**
2829	 * @param {Object} event
2830	 */
2831	toggleSelectionHandler: function( event ) {
2832		var method;
2833
2834		// Don't do anything inside inputs and on the attachment check and remove buttons.
2835		if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
2836			return;
2837		}
2838
2839		// Catch arrow events
2840		if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
2841			this.controller.trigger( 'attachment:keydown:arrow', event );
2842			return;
2843		}
2844
2845		// Catch enter and space events
2846		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
2847			return;
2848		}
2849
2850		event.preventDefault();
2851
2852		// In the grid view, bubble up an edit:attachment event to the controller.
2853		if ( this.controller.isModeActive( 'grid' ) ) {
2854			if ( this.controller.isModeActive( 'edit' ) ) {
2855				// Pass the current target to restore focus when closing
2856				this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
2857				return;
2858			}
2859
2860			if ( this.controller.isModeActive( 'select' ) ) {
2861				method = 'toggle';
2862			}
2863		}
2864
2865		if ( event.shiftKey ) {
2866			method = 'between';
2867		} else if ( event.ctrlKey || event.metaKey ) {
2868			method = 'toggle';
2869		}
2870
2871		this.toggleSelection({
2872			method: method
2873		});
2874
2875		this.controller.trigger( 'selection:toggle' );
2876	},
2877	/**
2878	 * @param {Object} options
2879	 */
2880	toggleSelection: function( options ) {
2881		var collection = this.collection,
2882			selection = this.options.selection,
2883			model = this.model,
2884			method = options && options.method,
2885			single, models, singleIndex, modelIndex;
2886
2887		if ( ! selection ) {
2888			return;
2889		}
2890
2891		single = selection.single();
2892		method = _.isUndefined( method ) ? selection.multiple : method;
2893
2894		// If the `method` is set to `between`, select all models that
2895		// exist between the current and the selected model.
2896		if ( 'between' === method && single && selection.multiple ) {
2897			// If the models are the same, short-circuit.
2898			if ( single === model ) {
2899				return;
2900			}
2901
2902			singleIndex = collection.indexOf( single );
2903			modelIndex  = collection.indexOf( this.model );
2904
2905			if ( singleIndex < modelIndex ) {
2906				models = collection.models.slice( singleIndex, modelIndex + 1 );
2907			} else {
2908				models = collection.models.slice( modelIndex, singleIndex + 1 );
2909			}
2910
2911			selection.add( models );
2912			selection.single( model );
2913			return;
2914
2915		// If the `method` is set to `toggle`, just flip the selection
2916		// status, regardless of whether the model is the single model.
2917		} else if ( 'toggle' === method ) {
2918			selection[ this.selected() ? 'remove' : 'add' ]( model );
2919			selection.single( model );
2920			return;
2921		} else if ( 'add' === method ) {
2922			selection.add( model );
2923			selection.single( model );
2924			return;
2925		}
2926
2927		// Fixes bug that loses focus when selecting a featured image
2928		if ( ! method ) {
2929			method = 'add';
2930		}
2931
2932		if ( method !== 'add' ) {
2933			method = 'reset';
2934		}
2935
2936		if ( this.selected() ) {
2937			// If the model is the single model, remove it.
2938			// If it is not the same as the single model,
2939			// it now becomes the single model.
2940			selection[ single === model ? 'remove' : 'single' ]( model );
2941		} else {
2942			// If the model is not selected, run the `method` on the
2943			// selection. By default, we `reset` the selection, but the
2944			// `method` can be set to `add` the model to the selection.
2945			selection[ method ]( model );
2946			selection.single( model );
2947		}
2948	},
2949
2950	updateSelect: function() {
2951		this[ this.selected() ? 'select' : 'deselect' ]();
2952	},
2953	/**
2954	 * @returns {unresolved|Boolean}
2955	 */
2956	selected: function() {
2957		var selection = this.options.selection;
2958		if ( selection ) {
2959			return !! selection.get( this.model.cid );
2960		}
2961	},
2962	/**
2963	 * @param {Backbone.Model} model
2964	 * @param {Backbone.Collection} collection
2965	 */
2966	select: function( model, collection ) {
2967		var selection = this.options.selection,
2968			controller = this.controller;
2969
2970		// Check if a selection exists and if it's the collection provided.
2971		// If they're not the same collection, bail; we're in another
2972		// selection's event loop.
2973		if ( ! selection || ( collection && collection !== selection ) ) {
2974			return;
2975		}
2976
2977		// Bail if the model is already selected.
2978		if ( this.$el.hasClass( 'selected' ) ) {
2979			return;
2980		}
2981
2982		// Add 'selected' class to model, set aria-checked to true.
2983		this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
2984		//  Make the checkbox tabable, except in media grid (bulk select mode).
2985		if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
2986			this.$( '.check' ).attr( 'tabindex', '0' );
2987		}
2988	},
2989	/**
2990	 * @param {Backbone.Model} model
2991	 * @param {Backbone.Collection} collection
2992	 */
2993	deselect: function( model, collection ) {
2994		var selection = this.options.selection;
2995
2996		// Check if a selection exists and if it's the collection provided.
2997		// If they're not the same collection, bail; we're in another
2998		// selection's event loop.
2999		if ( ! selection || ( collection && collection !== selection ) ) {
3000			return;
3001		}
3002		this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
3003			.find( '.check' ).attr( 'tabindex', '-1' );
3004	},
3005	/**
3006	 * @param {Backbone.Model} model
3007	 * @param {Backbone.Collection} collection
3008	 */
3009	details: function( model, collection ) {
3010		var selection = this.options.selection,
3011			details;
3012
3013		if ( selection !== collection ) {
3014			return;
3015		}
3016
3017		details = selection.single();
3018		this.$el.toggleClass( 'details', details === this.model );
3019	},
3020	/**
3021	 * @param {string} size
3022	 * @returns {Object}
3023	 */
3024	imageSize: function( size ) {
3025		var sizes = this.model.get('sizes'), matched = false;
3026
3027		size = size || 'medium';
3028
3029		// Use the provided image size if possible.
3030		if ( sizes ) {
3031			if ( sizes[ size ] ) {
3032				matched = sizes[ size ];
3033			} else if ( sizes.large ) {
3034				matched = sizes.large;
3035			} else if ( sizes.thumbnail ) {
3036				matched = sizes.thumbnail;
3037			} else if ( sizes.full ) {
3038				matched = sizes.full;
3039			}
3040
3041			if ( matched ) {
3042				return _.clone( matched );
3043			}
3044		}
3045
3046		return {
3047			url:         this.model.get('url'),
3048			width:       this.model.get('width'),
3049			height:      this.model.get('height'),
3050			orientation: this.model.get('orientation')
3051		};
3052	},
3053	/**
3054	 * @param {Object} event
3055	 */
3056	updateSetting: function( event ) {
3057		var $setting = $( event.target ).closest('[data-setting]'),
3058			setting, value;
3059
3060		if ( ! $setting.length ) {
3061			return;
3062		}
3063
3064		setting = $setting.data('setting');
3065		value   = event.target.value;
3066
3067		if ( this.model.get( setting ) !== value ) {
3068			this.save( setting, value );
3069		}
3070	},
3071
3072	/**
3073	 * Pass all the arguments to the model's save method.
3074	 *
3075	 * Records the aggregate status of all save requests and updates the
3076	 * view's classes accordingly.
3077	 */
3078	save: function() {
3079		var view = this,
3080			save = this._save = this._save || { status: 'ready' },
3081			request = this.model.save.apply( this.model, arguments ),
3082			requests = save.requests ? $.when( request, save.requests ) : request;
3083
3084		// If we're waiting to remove 'Saved.', stop.
3085		if ( save.savedTimer ) {
3086			clearTimeout( save.savedTimer );
3087		}
3088
3089		this.updateSave('waiting');
3090		save.requests = requests;
3091		requests.always( function() {
3092			// If we've performed another request since this one, bail.
3093			if ( save.requests !== requests ) {
3094				return;
3095			}
3096
3097			view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
3098			save.savedTimer = setTimeout( function() {
3099				view.updateSave('ready');
3100				delete save.savedTimer;
3101			}, 2000 );
3102		});
3103	},
3104	/**
3105	 * @param {string} status
3106	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
3107	 */
3108	updateSave: function( status ) {
3109		var save = this._save = this._save || { status: 'ready' };
3110
3111		if ( status && status !== save.status ) {
3112			this.$el.removeClass( 'save-' + save.status );
3113			save.status = status;
3114		}
3115
3116		this.$el.addClass( 'save-' + save.status );
3117		return this;
3118	},
3119
3120	updateAll: function() {
3121		var $settings = this.$('[data-setting]'),
3122			model = this.model,
3123			changed;
3124
3125		changed = _.chain( $settings ).map( function( el ) {
3126			var $input = $('input, textarea, select, [value]', el ),
3127				setting, value;
3128
3129			if ( ! $input.length ) {
3130				return;
3131			}
3132
3133			setting = $(el).data('setting');
3134			value = $input.val();
3135
3136			// Record the value if it changed.
3137			if ( model.get( setting ) !== value ) {
3138				return [ setting, value ];
3139			}
3140		}).compact().object().value();
3141
3142		if ( ! _.isEmpty( changed ) ) {
3143			model.save( changed );
3144		}
3145	},
3146	/**
3147	 * @param {Object} event
3148	 */
3149	removeFromLibrary: function( event ) {
3150		// Catch enter and space events
3151		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
3152			return;
3153		}
3154
3155		// Stop propagation so the model isn't selected.
3156		event.stopPropagation();
3157
3158		this.collection.remove( this.model );
3159	},
3160
3161	/**
3162	 * Add the model if it isn't in the selection, if it is in the selection,
3163	 * remove it.
3164	 *
3165	 * @param  {[type]} event [description]
3166	 * @return {[type]}       [description]
3167	 */
3168	checkClickHandler: function ( event ) {
3169		var selection = this.options.selection;
3170		if ( ! selection ) {
3171			return;
3172		}
3173		event.stopPropagation();
3174		if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
3175			selection.remove( this.model );
3176			// Move focus back to the attachment tile (from the check).
3177			this.$el.focus();
3178		} else {
3179			selection.add( this.model );
3180		}
3181	}
3182});
3183
3184// Ensure settings remain in sync between attachment views.
3185_.each({
3186	caption: '_syncCaption',
3187	title:   '_syncTitle',
3188	artist:  '_syncArtist',
3189	album:   '_syncAlbum'
3190}, function( method, setting ) {
3191	/**
3192	 * @param {Backbone.Model} model
3193	 * @param {string} value
3194	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
3195	 */
3196	Attachment.prototype[ method ] = function( model, value ) {
3197		var $setting = this.$('[data-setting="' + setting + '"]');
3198
3199		if ( ! $setting.length ) {
3200			return this;
3201		}
3202
3203		// If the updated value is in sync with the value in the DOM, there
3204		// is no need to re-render. If we're currently editing the value,
3205		// it will automatically be in sync, suppressing the re-render for
3206		// the view we're editing, while updating any others.
3207		if ( value === $setting.find('input, textarea, select, [value]').val() ) {
3208			return this;
3209		}
3210
3211		return this.render();
3212	};
3213});
3214
3215module.exports = Attachment;
3216
3217},{}],26:[function(require,module,exports){
3218/**
3219 * wp.media.view.Attachment.Details
3220 *
3221 * @class
3222 * @augments wp.media.view.Attachment
3223 * @augments wp.media.View
3224 * @augments wp.Backbone.View
3225 * @augments Backbone.View
3226 */
3227var Attachment = wp.media.view.Attachment,
3228	l10n = wp.media.view.l10n,
3229	Details;
3230
3231Details = Attachment.extend({
3232	tagName:   'div',
3233	className: 'attachment-details',
3234	template:  wp.template('attachment-details'),
3235
3236	attributes: function() {
3237		return {
3238			'tabIndex':     0,
3239			'data-id':      this.model.get( 'id' )
3240		};
3241	},
3242
3243	events: {
3244		'change [data-setting]':          'updateSetting',
3245		'change [data-setting] input':    'updateSetting',
3246		'change [data-setting] select':   'updateSetting',
3247		'change [data-setting] textarea': 'updateSetting',
3248		'click .delete-attachment':       'deleteAttachment',
3249		'click .trash-attachment':        'trashAttachment',
3250		'click .untrash-attachment':      'untrashAttachment',
3251		'click .edit-attachment':         'editAttachment',
3252		'keydown':                        'toggleSelectionHandler'
3253	},
3254
3255	initialize: function() {
3256		this.options = _.defaults( this.options, {
3257			rerenderOnModelChange: false
3258		});
3259
3260		this.on( 'ready', this.initialFocus );
3261		// Call 'initialize' directly on the parent class.
3262		Attachment.prototype.initialize.apply( this, arguments );
3263	},
3264
3265	initialFocus: function() {
3266		if ( ! wp.media.isTouchDevice ) {
3267			/*
3268			Previously focused the first ':input' (the readonly URL text field).
3269			Since the first ':input' is now a button (delete/trash): when pressing
3270			spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
3271			as soon as focus is moved. Explicitly target the first text field for now.
3272			@todo change initial focus logic, also for accessibility.
3273			*/
3274			this.$( 'input[type="text"]' ).eq( 0 ).focus();
3275		}
3276	},
3277	/**
3278	 * @param {Object} event
3279	 */
3280	deleteAttachment: function( event ) {
3281		event.preventDefault();
3282
3283		if ( window.confirm( l10n.warnDelete ) ) {
3284			this.model.destroy();
3285			// Keep focus inside media modal
3286			// after image is deleted
3287			this.controller.modal.focusManager.focus();
3288		}
3289	},
3290	/**
3291	 * @param {Object} event
3292	 */
3293	trashAttachment: function( event ) {
3294		var library = this.controller.library;
3295		event.preventDefault();
3296
3297		if ( wp.media.view.settings.mediaTrash &&
3298			'edit-metadata' === this.controller.content.mode() ) {
3299
3300			this.model.set( 'status', 'trash' );
3301			this.model.save().done( function() {
3302				library._requery( true );
3303			} );
3304		}  else {
3305			this.model.destroy();
3306		}
3307	},
3308	/**
3309	 * @param {Object} event
3310	 */
3311	untrashAttachment: function( event ) {
3312		var library = this.controller.library;
3313		event.preventDefault();
3314
3315		this.model.set( 'status', 'inherit' );
3316		this.model.save().done( function() {
3317			library._requery( true );
3318		} );
3319	},
3320	/**
3321	 * @param {Object} event
3322	 */
3323	editAttachment: function( event ) {
3324		var editState = this.controller.states.get( 'edit-image' );
3325		if ( window.imageEdit && editState ) {
3326			event.preventDefault();
3327
3328			editState.set( 'image', this.model );
3329			this.controller.setState( 'edit-image' );
3330		} else {
3331			this.$el.addClass('needs-refresh');
3332		}
3333	},
3334	/**
3335	 * When reverse tabbing(shift+tab) out of the right details panel, deliver
3336	 * the focus to the item in the list that was being edited.
3337	 *
3338	 * @param {Object} event
3339	 */
3340	toggleSelectionHandler: function( event ) {
3341		if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
3342			this.controller.trigger( 'attachment:details:shift-tab', event );
3343			return false;
3344		}
3345
3346		if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
3347			this.controller.trigger( 'attachment:keydown:arrow', event );
3348			return;
3349		}
3350	}
3351});
3352
3353module.exports = Details;
3354
3355},{}],27:[function(require,module,exports){
3356/**
3357 * wp.media.view.Attachment.EditLibrary
3358 *
3359 * @class
3360 * @augments wp.media.view.Attachment
3361 * @augments wp.media.View
3362 * @augments wp.Backbone.View
3363 * @augments Backbone.View
3364 */
3365var EditLibrary = wp.media.view.Attachment.extend({
3366	buttons: {
3367		close: true
3368	}
3369});
3370
3371module.exports = EditLibrary;
3372
3373},{}],28:[function(require,module,exports){
3374/**
3375 * wp.media.view.Attachments.EditSelection
3376 *
3377 * @class
3378 * @augments wp.media.view.Attachment.Selection
3379 * @augments wp.media.view.Attachment
3380 * @augments wp.media.View
3381 * @augments wp.Backbone.View
3382 * @augments Backbone.View
3383 */
3384var EditSelection = wp.media.view.Attachment.Selection.extend({
3385	buttons: {
3386		close: true
3387	}
3388});
3389
3390module.exports = EditSelection;
3391
3392},{}],29:[function(require,module,exports){
3393/**
3394 * wp.media.view.Attachment.Library
3395 *
3396 * @class
3397 * @augments wp.media.view.Attachment
3398 * @augments wp.media.View
3399 * @augments wp.Backbone.View
3400 * @augments Backbone.View
3401 */
3402var Library = wp.media.view.Attachment.extend({
3403	buttons: {
3404		check: true
3405	}
3406});
3407
3408module.exports = Library;
3409
3410},{}],30:[function(require,module,exports){
3411/**
3412 * wp.media.view.Attachment.Selection
3413 *
3414 * @class
3415 * @augments wp.media.view.Attachment
3416 * @augments wp.media.View
3417 * @augments wp.Backbone.View
3418 * @augments Backbone.View
3419 */
3420var Selection = wp.media.view.Attachment.extend({
3421	className: 'attachment selection',
3422
3423	// On click, just select the model, instead of removing the model from
3424	// the selection.
3425	toggleSelection: function() {
3426		this.options.selection.single( this.model );
3427	}
3428});
3429
3430module.exports = Selection;
3431
3432},{}],31:[function(require,module,exports){
3433/**
3434 * wp.media.view.Attachments
3435 *
3436 * @class
3437 * @augments wp.media.View
3438 * @augments wp.Backbone.View
3439 * @augments Backbone.View
3440 */
3441var View = wp.media.View,
3442	$ = jQuery,
3443	Attachments;
3444
3445Attachments = View.extend({
3446	tagName:   'ul',
3447	className: 'attachments',
3448
3449	attributes: {
3450		tabIndex: -1
3451	},
3452
3453	initialize: function() {
3454		this.el.id = _.uniqueId('__attachments-view-');
3455
3456		_.defaults( this.options, {
3457			refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
3458			refreshThreshold:   3,
3459			AttachmentView:     wp.media.view.Attachment,
3460			sortable:           false,
3461			resize:             true,
3462			idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
3463		});
3464
3465		this._viewsByCid = {};
3466		this.$window = $( window );
3467		this.resizeEvent = 'resize.media-modal-columns';
3468
3469		this.collection.on( 'add', function( attachment ) {
3470			this.views.add( this.createAttachmentView( attachment ), {
3471				at: this.collection.indexOf( attachment )
3472			});
3473		}, this );
3474
3475		this.collection.on( 'remove', function( attachment ) {
3476			var view = this._viewsByCid[ attachment.cid ];
3477			delete this._viewsByCid[ attachment.cid ];
3478
3479			if ( view ) {
3480				view.remove();
3481			}
3482		}, this );
3483
3484		this.collection.on( 'reset', this.render, this );
3485
3486		this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
3487
3488		// Throttle the scroll handler and bind this.
3489		this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
3490
3491		this.options.scrollElement = this.options.scrollElement || this.el;
3492		$( this.options.scrollElement ).on( 'scroll', this.scroll );
3493
3494		this.initSortable();
3495
3496		_.bindAll( this, 'setColumns' );
3497
3498		if ( this.options.resize ) {
3499			this.on( 'ready', this.bindEvents );
3500			this.controller.on( 'open', this.setColumns );
3501
3502			// Call this.setColumns() after this view has been rendered in the DOM so
3503			// attachments get proper width applied.
3504			_.defer( this.setColumns, this );
3505		}
3506	},
3507
3508	bindEvents: function() {
3509		this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
3510	},
3511
3512	attachmentFocus: function() {
3513		this.$( 'li:first' ).focus();
3514	},
3515
3516	restoreFocus: function() {
3517		this.$( 'li.selected:first' ).focus();
3518	},
3519
3520	arrowEvent: function( event ) {
3521		var attachments = this.$el.children( 'li' ),
3522			perRow = this.columns,
3523			index = attachments.filter( ':focus' ).index(),
3524			row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
3525
3526		if ( index === -1 ) {
3527			return;
3528		}
3529
3530		// Left arrow
3531		if ( 37 === event.keyCode ) {
3532			if ( 0 === index ) {
3533				return;
3534			}
3535			attachments.eq( index - 1 ).focus();
3536		}
3537
3538		// Up arrow
3539		if ( 38 === event.keyCode ) {
3540			if ( 1 === row ) {
3541				return;
3542			}
3543			attachments.eq( index - perRow ).focus();
3544		}
3545
3546		// Right arrow
3547		if ( 39 === event.keyCode ) {
3548			if ( attachments.length === index ) {
3549				return;
3550			}
3551			attachments.eq( index + 1 ).focus();
3552		}
3553
3554		// Down arrow
3555		if ( 40 === event.keyCode ) {
3556			if ( Math.ceil( attachments.length / perRow ) === row ) {
3557				return;
3558			}
3559			attachments.eq( index + perRow ).focus();
3560		}
3561	},
3562
3563	dispose: function() {
3564		this.collection.props.off( null, null, this );
3565		if ( this.options.resize ) {
3566			this.$window.off( this.resizeEvent );
3567		}
3568
3569		/**
3570		 * call 'dispose' directly on the parent class
3571		 */
3572		View.prototype.dispose.apply( this, arguments );
3573	},
3574
3575	setColumns: function() {
3576		var prev = this.columns,
3577			width = this.$el.width();
3578
3579		if ( width ) {
3580			this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
3581
3582			if ( ! prev || prev !== this.columns ) {
3583				this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
3584			}
3585		}
3586	},
3587
3588	initSortable: function() {
3589		var collection = this.collection;
3590
3591		if ( ! this.options.sortable || ! $.fn.sortable ) {
3592			return;
3593		}
3594
3595		this.$el.sortable( _.extend({
3596			// If the `collection` has a `comparator`, disable sorting.
3597			disabled: !! collection.comparator,
3598
3599			// Change the position of the attachment as soon as the
3600			// mouse pointer overlaps a thumbnail.
3601			tolerance: 'pointer',
3602
3603			// Record the initial `index` of the dragged model.
3604			start: function( event, ui ) {
3605				ui.item.data('sortableIndexStart', ui.item.index());
3606			},
3607
3608			// Update the model's index in the collection.
3609			// Do so silently, as the view is already accurate.
3610			update: function( event, ui ) {
3611				var model = collection.at( ui.item.data('sortableIndexStart') ),
3612					comparator = collection.comparator;
3613
3614				// Temporarily disable the comparator to prevent `add`
3615				// from re-sorting.
3616				delete collection.comparator;
3617
3618				// Silently shift the model to its new index.
3619				collection.remove( model, {
3620					silent: true
3621				});
3622				collection.add( model, {
3623					silent: true,
3624					at:     ui.item.index()
3625				});
3626
3627				// Restore the comparator.
3628				collection.comparator = comparator;
3629
3630				// Fire the `reset` event to ensure other collections sync.
3631				collection.trigger( 'reset', collection );
3632
3633				// If the collection is sorted by menu order,
3634				// update the menu order.
3635				collection.saveMenuOrder();
3636			}
3637		}, this.options.sortable ) );
3638
3639		// If the `orderby` property is changed on the `collection`,
3640		// check to see if we have a `comparator`. If so, disable sorting.
3641		collection.props.on( 'change:orderby', function() {
3642			this.$el.sortable( 'option', 'disabled', !! collection.comparator );
3643		}, this );
3644
3645		this.collection.props.on( 'change:orderby', this.refreshSortable, this );
3646		this.refreshSortable();
3647	},
3648
3649	refreshSortable: function() {
3650		if ( ! this.options.sortable || ! $.fn.sortable ) {
3651			return;
3652		}
3653
3654		// If the `collection` has a `comparator`, disable sorting.
3655		var collection = this.collection,
3656			orderby = collection.props.get('orderby'),
3657			enabled = 'menuOrder' === orderby || ! collection.comparator;
3658
3659		this.$el.sortable( 'option', 'disabled', ! enabled );
3660	},
3661
3662	/**
3663	 * @param {wp.media.model.Attachment} attachment
3664	 * @returns {wp.media.View}
3665	 */
3666	createAttachmentView: function( attachment ) {
3667		var view = new this.options.AttachmentView({
3668			controller:           this.controller,
3669			model:                attachment,
3670			collection:           this.collection,
3671			selection:            this.options.selection
3672		});
3673
3674		return this._viewsByCid[ attachment.cid ] = view;
3675	},
3676
3677	prepare: function() {
3678		// Create all of the Attachment views, and replace
3679		// the list in a single DOM operation.
3680		if ( this.collection.length ) {
3681			this.views.set( this.collection.map( this.createAttachmentView, this ) );
3682
3683		// If there are no elements, clear the views and load some.
3684		} else {
3685			this.views.unset();
3686			this.collection.more().done( this.scroll );
3687		}
3688	},
3689
3690	ready: function() {
3691		// Trigger the scroll event to check if we're within the
3692		// threshold to query for additional attachments.
3693		this.scroll();
3694	},
3695
3696	scroll: function() {
3697		var view = this,
3698			el = this.options.scrollElement,
3699			scrollTop = el.scrollTop,
3700			toolbar;
3701
3702		// The scroll event occurs on the document, but the element
3703		// that should be checked is the document body.
3704		if ( el === document ) {
3705			el = document.body;
3706			scrollTop = $(document).scrollTop();
3707		}
3708
3709		if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
3710			return;
3711		}
3712
3713		toolbar = this.views.parent.toolbar;
3714
3715		// Show the spinner only if we are close to the bottom.
3716		if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
3717			toolbar.get('spinner').show();
3718		}
3719
3720		if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
3721			this.collection.more().done(function() {
3722				view.scroll();
3723				toolbar.get('spinner').hide();
3724			});
3725		}
3726	}
3727});
3728
3729module.exports = Attachments;
3730
3731},{}],32:[function(require,module,exports){
3732/**
3733 * wp.media.view.AttachmentsBrowser
3734 *
3735 * @class
3736 * @augments wp.media.View
3737 * @augments wp.Backbone.View
3738 * @augments Backbone.View
3739 *
3740 * @param {object}         [options]               The options hash passed to the view.
3741 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
3742 *                                                 Accepts 'uploaded' and 'all'.
3743 * @param {boolean}        [options.search=true]   Whether to show the search interface in the
3744 *                                                 browser's toolbar.
3745 * @param {boolean}        [options.date=true]     Whether to show the date filter in the
3746 *                                                 browser's toolbar.
3747 * @param {boolean}        [options.display=false] Whether to show the attachments display settings
3748 *                                                 view in the sidebar.
3749 * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
3750 *                                                 Accepts true, false, and 'errors'.
3751 */
3752var View = wp.media.View,
3753	mediaTrash = wp.media.view.settings.mediaTrash,
3754	l10n = wp.media.view.l10n,
3755	$ = jQuery,
3756	AttachmentsBrowser;
3757
3758AttachmentsBrowser = View.extend({
3759	tagName:   'div',
3760	className: 'attachments-browser',
3761
3762	initialize: function() {
3763		_.defaults( this.options, {
3764			filters: false,
3765			search:  true,
3766			date:    true,
3767			display: false,
3768			sidebar: true,
3769			AttachmentView: wp.media.view.Attachment.Library
3770		});
3771
3772		this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
3773		this.controller.on( 'edit:selection', this.editSelection );
3774		this.createToolbar();
3775		// In the Media Library, the sidebar is used to display errors before the attachments grid.
3776		if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
3777			this.createSidebar();
3778		}
3779		this.createUploader();
3780		this.createAttachments();
3781		// For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
3782		if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
3783			this.createSidebar();
3784		}
3785		this.updateContent();
3786
3787		if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
3788			this.$el.addClass( 'hide-sidebar' );
3789
3790			if ( 'errors' === this.options.sidebar ) {
3791				this.$el.addClass( 'sidebar-for-errors' );
3792			}
3793		}
3794
3795		this.collection.on( 'add remove reset', this.updateContent, this );
3796	},
3797
3798	editSelection: function( modal ) {
3799		modal.$( '.media-button-backToLibrary' ).focus();
3800	},
3801
3802	/**
3803	 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
3804	 */
3805	dispose: function() {
3806		this.options.selection.off( null, null, this );
3807		View.prototype.dispose.apply( this, arguments );
3808		return this;
3809	},
3810
3811	createToolbar: function() {
3812		var LibraryViewSwitcher, Filters, toolbarOptions;
3813
3814		toolbarOptions = {
3815			controller: this.controller
3816		};
3817
3818		if ( this.controller.isModeActive( 'grid' ) ) {
3819			toolbarOptions.className = 'media-toolbar wp-filter';
3820		}
3821
3822		/**
3823		* @member {wp.media.view.Toolbar}
3824		*/
3825		this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
3826
3827		this.views.add( this.toolbar );
3828
3829		this.toolbar.set( 'spinner', new wp.media.view.Spinner({
3830			priority: -60
3831		}) );
3832
3833		if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
3834			// "Filters" will return a <select>, need to render
3835			// screen reader text before
3836			this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
3837				value: l10n.filterByType,
3838				attributes: {
3839					'for':  'media-attachment-filters'
3840				},
3841				priority:   -80
3842			}).render() );
3843
3844			if ( 'uploaded' === this.options.filters ) {
3845				this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
3846					controller: this.controller,
3847					model:      this.collection.props,
3848					priority:   -80
3849				}).render() );
3850			} else {
3851				Filters = new wp.media.view.AttachmentFilters.All({
3852					controller: this.controller,
3853					model:      this.collection.props,
3854					priority:   -80
3855				});
3856
3857				this.toolbar.set( 'filters', Filters.render() );
3858			}
3859		}
3860
3861		// Feels odd to bring the global media library switcher into the Attachment
3862		// browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
3863		// which the controller can tap into and add this view?
3864		if ( this.controller.isModeActive( 'grid' ) ) {
3865			LibraryViewSwitcher = View.extend({
3866				className: 'view-switch media-grid-view-switch',
3867				template: wp.template( 'media-library-view-switcher')
3868			});
3869
3870			this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
3871				controller: this.controller,
3872				priority: -90
3873			}).render() );
3874
3875			// DateFilter is a <select>, screen reader text needs to be rendered before
3876			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
3877				value: l10n.filterByDate,
3878				attributes: {
3879					'for': 'media-attachment-date-filters'
3880				},
3881				priority: -75
3882			}).render() );
3883			this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
3884				controller: this.controller,
3885				model:      this.collection.props,
3886				priority: -75
3887			}).render() );
3888
3889			// BulkSelection is a <div> with subviews, including screen reader text
3890			this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
3891				text: l10n.bulkSelect,
3892				controller: this.controller,
3893				priority: -70
3894			}).render() );
3895
3896			this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
3897				filters: Filters,
3898				style: 'primary',
3899				disabled: true,
3900				text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
3901				controller: this.controller,
3902				priority: -60,
3903				click: function() {
3904					var changed = [], removed = [],
3905						selection = this.controller.state().get( 'selection' ),
3906						library = this.controller.state().get( 'library' );
3907
3908					if ( ! selection.length ) {
3909						return;
3910					}
3911
3912					if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
3913						return;
3914					}
3915
3916					if ( mediaTrash &&
3917						'trash' !== selection.at( 0 ).get( 'status' ) &&
3918						! window.confirm( l10n.warnBulkTrash ) ) {
3919
3920						return;
3921					}
3922
3923					selection.each( function( model ) {
3924						if ( ! model.get( 'nonces' )['delete'] ) {
3925							removed.push( model );
3926							return;
3927						}
3928
3929						if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
3930							model.set( 'status', 'inherit' );
3931							changed.push( model.save() );
3932							removed.push( model );
3933						} else if ( mediaTrash ) {
3934							model.set( 'status', 'trash' );
3935							changed.push( model.save() );
3936							removed.push( model );
3937						} else {
3938							model.destroy({wait: true});
3939						}
3940					} );
3941
3942					if ( changed.length ) {
3943						selection.remove( removed );
3944
3945						$.when.apply( null, changed ).then( _.bind( function() {
3946							library._requery( true );
3947							this.controller.trigger( 'selection:action:done' );
3948						}, this ) );
3949					} else {
3950						this.controller.trigger( 'selection:action:done' );
3951					}
3952				}
3953			}).render() );
3954
3955			if ( mediaTrash ) {
3956				this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
3957					filters: Filters,
3958					style: 'primary',
3959					disabled: true,
3960					text: l10n.deleteSelected,
3961					controller: this.controller,
3962					priority: -55,
3963					click: function() {
3964						var removed = [], selection = this.controller.state().get( 'selection' );
3965
3966						if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
3967							return;
3968						}
3969
3970						selection.each( function( model ) {
3971							if ( ! model.get( 'nonces' )['delete'] ) {
3972								removed.push( model );
3973								return;
3974							}
3975
3976							model.destroy();
3977						} );
3978
3979						selection.remove( removed );
3980						this.controller.trigger( 'selection:action:done' );
3981					}
3982				}).render() );
3983			}
3984
3985		} else if ( this.options.date ) {
3986			// DateFilter is a <select>, screen reader text needs to be rendered before
3987			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
3988				value: l10n.filterByDate,
3989				attributes: {
3990					'for': 'media-attachment-date-filters'
3991				},
3992				priority: -75
3993			}).render() );
3994			this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
3995				controller: this.controller,
3996				model:      this.collection.props,
3997				priority: -75
3998			}).render() );
3999		}
4000
4001		if ( this.options.search ) {
4002			// Search is an input, screen reader text needs to be rendered before
4003			this.toolbar.set( 'searchLabel', new wp.media.view.Label({
4004				value: l10n.searchMediaLabel,
4005				attributes: {
4006					'for': 'media-search-input'
4007				},
4008				priority:   60
4009			}).render() );
4010			this.toolbar.set( 'search', new wp.media.view.Search({
4011				controller: this.controller,
4012				model:      this.collection.props,
4013				priority:   60
4014			}).render() );
4015		}
4016
4017		if ( this.options.dragInfo ) {
4018			this.toolbar.set( 'dragInfo', new View({
4019				el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
4020				priority: -40
4021			}) );
4022		}
4023
4024		if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
4025			this.toolbar.set( 'suggestedDimensions', new View({
4026				el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' &times; ' + this.options.suggestedHeight + '</div>' )[0],
4027				priority: -40
4028			}) );
4029		}
4030	},
4031
4032	updateContent: function() {
4033		var view = this,
4034			noItemsView;
4035
4036		if ( this.controller.isModeActive( 'grid' ) ) {
4037			noItemsView = view.attachmentsNoResults;
4038		} else {
4039			noItemsView = view.uploader;
4040		}
4041
4042		if ( ! this.collection.length ) {
4043			this.toolbar.get( 'spinner' ).show();
4044			this.dfd = this.collection.more().done( function() {
4045				if ( ! view.collection.length ) {
4046					noItemsView.$el.removeClass( 'hidden' );
4047				} else {
4048					noItemsView.$el.addClass( 'hidden' );
4049				}
4050				view.toolbar.get( 'spinner' ).hide();
4051			} );
4052		} else {
4053			noItemsView.$el.addClass( 'hidden' );
4054			view.toolbar.get( 'spinner' ).hide();
4055		}
4056	},
4057
4058	createUploader: function() {
4059		this.uploader = new wp.media.view.UploaderInline({
4060			controller: this.controller,
4061			status:     false,
4062			message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
4063			canClose:   this.controller.isModeActive( 'grid' )
4064		});
4065
4066		this.uploader.hide();
4067		this.views.add( this.uploader );
4068	},
4069
4070	toggleUploader: function() {
4071		if ( this.uploader.$el.hasClass( 'hidden' ) ) {
4072			this.uploader.show();
4073		} else {
4074			this.uploader.hide();
4075		}
4076	},
4077
4078	createAttachments: function() {
4079		this.attachments = new wp.media.view.Attachments({
4080			controller:           this.controller,
4081			collection:           this.collection,
4082			selection:            this.options.selection,
4083			model:                this.model,
4084			sortable:             this.options.sortable,
4085			scrollElement:        this.options.scrollElement,
4086			idealColumnWidth:     this.options.idealColumnWidth,
4087
4088			// The single `Attachment` view to be used in the `Attachments` view.
4089			AttachmentView: this.options.AttachmentView
4090		});
4091
4092		// Add keydown listener to the instance of the Attachments view
4093		this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
4094		this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
4095
4096		this.views.add( this.attachments );
4097
4098
4099		if ( this.controller.isModeActive( 'grid' ) ) {
4100			this.attachmentsNoResults = new View({
4101				controller: this.controller,
4102				tagName: 'p'
4103			});
4104
4105			this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
4106			this.attachmentsNoResults.$el.html( l10n.noMedia );
4107
4108			this.views.add( this.attachmentsNoResults );
4109		}
4110	},
4111
4112	createSidebar: function() {
4113		var options = this.options,
4114			selection = options.selection,
4115			sidebar = this.sidebar = new wp.media.view.Sidebar({
4116				controller: this.controller
4117			});
4118
4119		this.views.add( sidebar );
4120
4121		if ( this.controller.uploader ) {
4122			sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
4123				controller: this.controller,
4124				priority:   40
4125			}) );
4126		}
4127
4128		selection.on( 'selection:single', this.createSingle, this );
4129		selection.on( 'selection:unsingle', this.disposeSingle, this );
4130
4131		if ( selection.single() ) {
4132			this.createSingle();
4133		}
4134	},
4135
4136	createSingle: function() {
4137		var sidebar = this.sidebar,
4138			single = this.options.selection.single();
4139
4140		sidebar.set( 'details', new wp.media.view.Attachment.Details({
4141			controller: this.controller,
4142			model:      single,
4143			priority:   80
4144		}) );
4145
4146		sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
4147			controller: this.controller,
4148			model:      single,
4149			priority:   120
4150		}) );
4151
4152		if ( this.options.display ) {
4153			sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
4154				controller:   this.controller,
4155				model:        this.model.display( single ),
4156				attachment:   single,
4157				priority:     160,
4158				userSettings: this.model.get('displayUserSettings')
4159			}) );
4160		}
4161
4162		// Show the sidebar on mobile
4163		if ( this.model.id === 'insert' ) {
4164			sidebar.$el.addClass( 'visible' );
4165		}
4166	},
4167
4168	disposeSingle: function() {
4169		var sidebar = this.sidebar;
4170		sidebar.unset('details');
4171		sidebar.unset('compat');
4172		sidebar.unset('display');
4173		// Hide the sidebar on mobile
4174		sidebar.$el.removeClass( 'visible' );
4175	}
4176});
4177
4178module.exports = AttachmentsBrowser;
4179
4180},{}],33:[function(require,module,exports){
4181/**
4182 * wp.media.view.Attachments.Selection
4183 *
4184 * @class
4185 * @augments wp.media.view.Attachments
4186 * @augments wp.media.View
4187 * @augments wp.Backbone.View
4188 * @augments Backbone.View
4189 */
4190var Attachments = wp.media.view.Attachments,
4191	Selection;
4192
4193Selection = Attachments.extend({
4194	events: {},
4195	initialize: function() {
4196		_.defaults( this.options, {
4197			sortable:   false,
4198			resize:     false,
4199
4200			// The single `Attachment` view to be used in the `Attachments` view.
4201			AttachmentView: wp.media.view.Attachment.Selection
4202		});
4203		// Call 'initialize' directly on the parent class.
4204		return Attachments.prototype.initialize.apply( this, arguments );
4205	}
4206});
4207
4208module.exports = Selection;
4209
4210},{}],34:[function(require,module,exports){
4211/**
4212 * wp.media.view.ButtonGroup
4213 *
4214 * @class
4215 * @augments wp.media.View
4216 * @augments wp.Backbone.View
4217 * @augments Backbone.View
4218 */
4219var $ = Backbone.$,
4220	ButtonGroup;
4221
4222ButtonGroup = wp.media.View.extend({
4223	tagName:   'div',
4224	className: 'button-group button-large media-button-group',
4225
4226	initialize: function() {
4227		/**
4228		 * @member {wp.media.view.Button[]}
4229		 */
4230		this.buttons = _.map( this.options.buttons || [], function( button ) {
4231			if ( button instanceof Backbone.View ) {
4232				return button;
4233			} else {
4234				return new wp.media.view.Button( button ).render();
4235			}
4236		});
4237
4238		delete this.options.buttons;
4239
4240		if ( this.options.classes ) {
4241			this.$el.addClass( this.options.classes );
4242		}
4243	},
4244
4245	/**
4246	 * @returns {wp.media.view.ButtonGroup}
4247	 */
4248	render: function() {
4249		this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
4250		return this;
4251	}
4252});
4253
4254module.exports = ButtonGroup;
4255
4256},{}],35:[function(require,module,exports){
4257/**
4258 * wp.media.view.Button
4259 *
4260 * @class
4261 * @augments wp.media.View
4262 * @augments wp.Backbone.View
4263 * @augments Backbone.View
4264 */
4265var Button = wp.media.View.extend({
4266	tagName:    'button',
4267	className:  'media-button',
4268	attributes: { type: 'button' },
4269
4270	events: {
4271		'click': 'click'
4272	},
4273
4274	defaults: {
4275		text:     '',
4276		style:    '',
4277		size:     'large',
4278		disabled: false
4279	},
4280
4281	initialize: function() {
4282		/**
4283		 * Create a model with the provided `defaults`.
4284		 *
4285		 * @member {Backbone.Model}
4286		 */
4287		this.model = new Backbone.Model( this.defaults );
4288
4289		// If any of the `options` have a key from `defaults`, apply its
4290		// value to the `model` and remove it from the `options object.
4291		_.each( this.defaults, function( def, key ) {
4292			var value = this.options[ key ];
4293			if ( _.isUndefined( value ) ) {
4294				return;
4295			}
4296
4297			this.model.set( key, value );
4298			delete this.options[ key ];
4299		}, this );
4300
4301		this.listenTo( this.model, 'change', this.render );
4302	},
4303	/**
4304	 * @returns {wp.media.view.Button} Returns itself to allow chaining
4305	 */
4306	render: function() {
4307		var classes = [ 'button', this.className ],
4308			model = this.model.toJSON();
4309
4310		if ( model.style ) {
4311			classes.push( 'button-' + model.style );
4312		}
4313
4314		if ( model.size ) {
4315			classes.push( 'button-' + model.size );
4316		}
4317
4318		classes = _.uniq( classes.concat( this.options.classes ) );
4319		this.el.className = classes.join(' ');
4320
4321		this.$el.attr( 'disabled', model.disabled );
4322		this.$el.text( this.model.get('text') );
4323
4324		return this;
4325	},
4326	/**
4327	 * @param {Object} event
4328	 */
4329	click: function( event ) {
4330		if ( '#' === this.attributes.href ) {
4331			event.preventDefault();
4332		}
4333
4334		if ( this.options.click && ! this.model.get('disabled') ) {
4335			this.options.click.apply( this, arguments );
4336		}
4337	}
4338});
4339
4340module.exports = Button;
4341
4342},{}],36:[function(require,module,exports){
4343/**
4344 * wp.media.view.Cropper
4345 *
4346 * Uses the imgAreaSelect plugin to allow a user to crop an image.
4347 *
4348 * Takes imgAreaSelect options from
4349 * wp.customize.HeaderControl.calculateImageSelectOptions via
4350 * wp.customize.HeaderControl.openMM.
4351 *
4352 * @class
4353 * @augments wp.media.View
4354 * @augments wp.Backbone.View
4355 * @augments Backbone.View
4356 */
4357var View = wp.media.View,
4358	UploaderStatus = wp.media.view.UploaderStatus,
4359	l10n = wp.media.view.l10n,
4360	$ = jQuery,
4361	Cropper;
4362
4363Cropper = View.extend({
4364	className: 'crop-content',
4365	template: wp.template('crop-content'),
4366	initialize: function() {
4367		_.bindAll(this, 'onImageLoad');
4368	},
4369	ready: function() {
4370		this.controller.frame.on('content:error:crop', this.onError, this);
4371		this.$image = this.$el.find('.crop-image');
4372		this.$image.on('load', this.onImageLoad);
4373		$(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
4374	},
4375	remove: function() {
4376		$(window).off('resize.cropper');
4377		this.$el.remove();
4378		this.$el.off();
4379		View.prototype.remove.apply(this, arguments);
4380	},
4381	prepare: function() {
4382		return {
4383			title: l10n.cropYourImage,
4384			url: this.options.attachment.get('url')
4385		};
4386	},
4387	onImageLoad: function() {
4388		var imgOptions = this.controller.get('imgSelectOptions');
4389		if (typeof imgOptions === 'function') {
4390			imgOptions = imgOptions(this.options.attachment, this.controller);
4391		}
4392
4393		imgOptions = _.extend(imgOptions, {parent: this.$el});
4394		this.trigger('image-loaded');
4395		this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
4396	},
4397	onError: function() {
4398		var filename = this.options.attachment.get('filename');
4399
4400		this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
4401			filename: UploaderStatus.prototype.filename(filename),
4402			message: window._wpMediaViewsL10n.cropError
4403		}), { at: 0 });
4404	}
4405});
4406
4407module.exports = Cropper;
4408
4409},{}],37:[function(require,module,exports){
4410/**
4411 * wp.media.view.EditImage
4412 *
4413 * @class
4414 * @augments wp.media.View
4415 * @augments wp.Backbone.View
4416 * @augments Backbone.View
4417 */
4418var View = wp.media.View,
4419	EditImage;
4420
4421EditImage = View.extend({
4422	className: 'image-editor',
4423	template: wp.template('image-editor'),
4424
4425	initialize: function( options ) {
4426		this.editor = window.imageEdit;
4427		this.controller = options.controller;
4428		View.prototype.initialize.apply( this, arguments );
4429	},
4430
4431	prepare: function() {
4432		return this.model.toJSON();
4433	},
4434
4435	loadEditor: function() {
4436		var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
4437		dfd.done( _.bind( this.focus, this ) );
4438	},
4439
4440	focus: function() {
4441		this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
4442	},
4443
4444	back: function() {
4445		var lastState = this.controller.lastState();
4446		this.controller.setState( lastState );
4447	},
4448
4449	refresh: function() {
4450		this.model.fetch();
4451	},
4452
4453	save: function() {
4454		var lastState = this.controller.lastState();
4455
4456		this.model.fetch().done( _.bind( function() {
4457			this.controller.setState( lastState );
4458		}, this ) );
4459	}
4460
4461});
4462
4463module.exports = EditImage;
4464
4465},{}],38:[function(require,module,exports){
4466/**
4467 * wp.media.view.Embed
4468 *
4469 * @class
4470 * @augments wp.media.View
4471 * @augments wp.Backbone.View
4472 * @augments Backbone.View
4473 */
4474var Embed = wp.media.View.extend({
4475	className: 'media-embed',
4476
4477	initialize: function() {
4478		/**
4479		 * @member {wp.media.view.EmbedUrl}
4480		 */
4481		this.url = new wp.media.view.EmbedUrl({
4482			controller: this.controller,
4483			model:      this.model.props
4484		}).render();
4485
4486		this.views.set([ this.url ]);
4487		this.refresh();
4488		this.listenTo( this.model, 'change:type', this.refresh );
4489		this.listenTo( this.model, 'change:loading', this.loading );
4490	},
4491
4492	/**
4493	 * @param {Object} view
4494	 */
4495	settings: function( view ) {
4496		if ( this._settings ) {
4497			this._settings.remove();
4498		}
4499		this._settings = view;
4500		this.views.add( view );
4501	},
4502
4503	refresh: function() {
4504		var type = this.model.get('type'),
4505			constructor;
4506
4507		if ( 'image' === type ) {
4508			constructor = wp.media.view.EmbedImage;
4509		} else if ( 'link' === type ) {
4510			constructor = wp.media.view.EmbedLink;
4511		} else {
4512			return;
4513		}
4514
4515		this.settings( new constructor({
4516			controller: this.controller,
4517			model:      this.model.props,
4518			priority:   40
4519		}) );
4520	},
4521
4522	loading: function() {
4523		this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
4524	}
4525});
4526
4527module.exports = Embed;
4528
4529},{}],39:[function(require,module,exports){
4530/**
4531 * wp.media.view.EmbedImage
4532 *
4533 * @class
4534 * @augments wp.media.view.Settings.AttachmentDisplay
4535 * @augments wp.media.view.Settings
4536 * @augments wp.media.View
4537 * @augments wp.Backbone.View
4538 * @augments Backbone.View
4539 */
4540var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
4541	EmbedImage;
4542
4543EmbedImage = AttachmentDisplay.extend({
4544	className: 'embed-media-settings',
4545	template:  wp.template('embed-image-settings'),
4546
4547	initialize: function() {
4548		/**
4549		 * Call `initialize` directly on parent class with passed arguments
4550		 */
4551		AttachmentDisplay.prototype.initialize.apply( this, arguments );
4552		this.listenTo( this.model, 'change:url', this.updateImage );
4553	},
4554
4555	updateImage: function() {
4556		this.$('img').attr( 'src', this.model.get('url') );
4557	}
4558});
4559
4560module.exports = EmbedImage;
4561
4562},{}],40:[function(require,module,exports){
4563/**
4564 * wp.media.view.EmbedLink
4565 *
4566 * @class
4567 * @augments wp.media.view.Settings
4568 * @augments wp.media.View
4569 * @augments wp.Backbone.View
4570 * @augments Backbone.View
4571 */
4572var $ = jQuery,
4573	EmbedLink;
4574
4575EmbedLink = wp.media.view.Settings.extend({
4576	className: 'embed-link-settings',
4577	template:  wp.template('embed-link-settings'),
4578
4579	initialize: function() {
4580		this.listenTo( this.model, 'change:url', this.updateoEmbed );
4581	},
4582
4583	updateoEmbed: _.debounce( function() {
4584		var url = this.model.get( 'url' );
4585
4586		// clear out previous results
4587		this.$('.embed-container').hide().find('.embed-preview').empty();
4588		this.$( '.setting' ).hide();
4589
4590		// only proceed with embed if the field contains more than 11 characters
4591		// Example: http://a.io is 11 chars
4592		if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
4593			return;
4594		}
4595
4596		this.fetch();
4597	}, wp.media.controller.Embed.sensitivity ),
4598
4599	fetch: function() {
4600		var embed;
4601
4602		// check if they haven't typed in 500 ms
4603		if ( $('#embed-url-field').val() !== this.model.get('url') ) {
4604			return;
4605		}
4606
4607		if ( this.dfd && 'pending' === this.dfd.state() ) {
4608			this.dfd.abort();
4609		}
4610
4611		embed = new wp.shortcode({
4612			tag: 'embed',
4613			attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ),
4614			content: this.model.get('url')
4615		});
4616
4617		this.dfd = $.ajax({
4618			type:    'POST',
4619			url:     wp.ajax.settings.url,
4620			context: this,
4621			data:    {
4622				action: 'parse-embed',
4623				post_ID: wp.media.view.settings.post.id,
4624				shortcode: embed.string()
4625			}
4626		})
4627			.done( this.renderoEmbed )
4628			.fail( this.renderFail );
4629	},
4630
4631	renderFail: function ( response, status ) {
4632		if ( 'abort' === status ) {
4633			return;
4634		}
4635		this.$( '.link-text' ).show();
4636	},
4637
4638	renderoEmbed: function( response ) {
4639		var html = ( response && response.data && response.data.body ) || '';
4640
4641		if ( html ) {
4642			this.$('.embed-container').show().find('.embed-preview').html( html );
4643		} else {
4644			this.renderFail();
4645		}
4646	}
4647});
4648
4649module.exports = EmbedLink;
4650
4651},{}],41:[function(require,module,exports){
4652/**
4653 * wp.media.view.EmbedUrl
4654 *
4655 * @class
4656 * @augments wp.media.View
4657 * @augments wp.Backbone.View
4658 * @augments Backbone.View
4659 */
4660var View = wp.media.View,
4661	$ = jQuery,
4662	EmbedUrl;
4663
4664EmbedUrl = View.extend({
4665	tagName:   'label',
4666	className: 'embed-url',
4667
4668	events: {
4669		'input':  'url',
4670		'keyup':  'url',
4671		'change': 'url'
4672	},
4673
4674	initialize: function() {
4675		this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
4676		this.input = this.$input[0];
4677
4678		this.spinner = $('<span class="spinner" />')[0];
4679		this.$el.append([ this.input, this.spinner ]);
4680
4681		this.listenTo( this.model, 'change:url', this.render );
4682
4683		if ( this.model.get( 'url' ) ) {
4684			_.delay( _.bind( function () {
4685				this.model.trigger( 'change:url' );
4686			}, this ), 500 );
4687		}
4688	},
4689	/**
4690	 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
4691	 */
4692	render: function() {
4693		var $input = this.$input;
4694
4695		if ( $input.is(':focus') ) {
4696			return;
4697		}
4698
4699		this.input.value = this.model.get('url') || 'http://';
4700		/**
4701		 * Call `render` directly on parent class with passed arguments
4702		 */
4703		View.prototype.render.apply( this, arguments );
4704		return this;
4705	},
4706
4707	ready: function() {
4708		if ( ! wp.media.isTouchDevice ) {
4709			this.focus();
4710		}
4711	},
4712
4713	url: function( event ) {
4714		this.model.set( 'url', event.target.value );
4715	},
4716
4717	/**
4718	 * If the input is visible, focus and select its contents.
4719	 */
4720	focus: function() {
4721		var $input = this.$input;
4722		if ( $input.is(':visible') ) {
4723			$input.focus()[0].select();
4724		}
4725	}
4726});
4727
4728module.exports = EmbedUrl;
4729
4730},{}],42:[function(require,module,exports){
4731/**
4732 * wp.media.view.FocusManager
4733 *
4734 * @class
4735 * @augments wp.media.View
4736 * @augments wp.Backbone.View
4737 * @augments Backbone.View
4738 */
4739var FocusManager = wp.media.View.extend({
4740
4741	events: {
4742		'keydown': 'constrainTabbing'
4743	},
4744
4745	focus: function() { // Reset focus on first left menu item
4746		this.$('.media-menu-item').first().focus();
4747	},
4748	/**
4749	 * @param {Object} event
4750	 */
4751	constrainTabbing: function( event ) {
4752		var tabbables;
4753
4754		// Look for the tab key.
4755		if ( 9 !== event.keyCode ) {
4756			return;
4757		}
4758
4759		// Skip the file input added by Plupload.
4760		tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
4761
4762		// Keep tab focus within media modal while it's open
4763		if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
4764			tabbables.first().focus();
4765			return false;
4766		} else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
4767			tabbables.last().focus();
4768			return false;
4769		}
4770	}
4771
4772});
4773
4774module.exports = FocusManager;
4775
4776},{}],43:[function(require,module,exports){
4777/**
4778 * wp.media.view.Frame
4779 *
4780 * A frame is a composite view consisting of one or more regions and one or more
4781 * states.
4782 *
4783 * @see wp.media.controller.State
4784 * @see wp.media.controller.Region
4785 *
4786 * @class
4787 * @augments wp.media.View
4788 * @augments wp.Backbone.View
4789 * @augments Backbone.View
4790 * @mixes wp.media.controller.StateMachine
4791 */
4792var Frame = wp.media.View.extend({
4793	initialize: function() {
4794		_.defaults( this.options, {
4795			mode: [ 'select' ]
4796		});
4797		this._createRegions();
4798		this._createStates();
4799		this._createModes();
4800	},
4801
4802	_createRegions: function() {
4803		// Clone the regions array.
4804		this.regions = this.regions ? this.regions.slice() : [];
4805
4806		// Initialize regions.
4807		_.each( this.regions, function( region ) {
4808			this[ region ] = new wp.media.controller.Region({
4809				view:     this,
4810				id:       region,
4811				selector: '.media-frame-' + region
4812			});
4813		}, this );
4814	},
4815	/**
4816	 * Create the frame's states.
4817	 *
4818	 * @see wp.media.controller.State
4819	 * @see wp.media.controller.StateMachine
4820	 *
4821	 * @fires wp.media.controller.State#ready
4822	 */
4823	_createStates: function() {
4824		// Create the default `states` collection.
4825		this.states = new Backbone.Collection( null, {
4826			model: wp.media.controller.State
4827		});
4828
4829		// Ensure states have a reference to the frame.
4830		this.states.on( 'add', function( model ) {
4831			model.frame = this;
4832			model.trigger('ready');
4833		}, this );
4834
4835		if ( this.options.states ) {
4836			this.states.add( this.options.states );
4837		}
4838	},
4839
4840	/**
4841	 * A frame can be in a mode or multiple modes at one time.
4842	 *
4843	 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
4844	 */
4845	_createModes: function() {
4846		// Store active "modes" that the frame is in. Unrelated to region modes.
4847		this.activeModes = new Backbone.Collection();
4848		this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
4849
4850		_.each( this.options.mode, function( mode ) {
4851			this.activateMode( mode );
4852		}, this );
4853	},
4854	/**
4855	 * Reset all states on the frame to their defaults.
4856	 *
4857	 * @returns {wp.media.view.Frame} Returns itself to allow chaining
4858	 */
4859	reset: function() {
4860		this.states.invoke( 'trigger', 'reset' );
4861		return this;
4862	},
4863	/**
4864	 * Map activeMode collection events to the frame.
4865	 */
4866	triggerModeEvents: function( model, collection, options ) {
4867		var collectionEvent,
4868			modeEventMap = {
4869				add: 'activate',
4870				remove: 'deactivate'
4871			},
4872			eventToTrigger;
4873		// Probably a better way to do this.
4874		_.each( options, function( value, key ) {
4875			if ( value ) {
4876				collectionEvent = key;
4877			}
4878		} );
4879
4880		if ( ! _.has( modeEventMap, collectionEvent ) ) {
4881			return;
4882		}
4883
4884		eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
4885		this.trigger( eventToTrigger );
4886	},
4887	/**
4888	 * Activate a mode on the frame.
4889	 *
4890	 * @param string mode Mode ID.
4891	 * @returns {this} Returns itself to allow chaining.
4892	 */
4893	activateMode: function( mode ) {
4894		// Bail if the mode is already active.
4895		if ( this.isModeActive( mode ) ) {
4896			return;
4897		}
4898		this.activeModes.add( [ { id: mode } ] );
4899		// Add a CSS class to the frame so elements can be styled for the mode.
4900		this.$el.addClass( 'mode-' + mode );
4901
4902		return this;
4903	},
4904	/**
4905	 * Deactivate a mode on the frame.
4906	 *
4907	 * @param string mode Mode ID.
4908	 * @returns {this} Returns itself to allow chaining.
4909	 */
4910	deactivateMode: function( mode ) {
4911		// Bail if the mode isn't active.
4912		if ( ! this.isModeActive( mode ) ) {
4913			return this;
4914		}
4915		this.activeModes.remove( this.activeModes.where( { id: mode } ) );
4916		this.$el.removeClass( 'mode-' + mode );
4917		/**
4918		 * Frame mode deactivation event.
4919		 *
4920		 * @event this#{mode}:deactivate
4921		 */
4922		this.trigger( mode + ':deactivate' );
4923
4924		return this;
4925	},
4926	/**
4927	 * Check if a mode is enabled on the frame.
4928	 *
4929	 * @param  string mode Mode ID.
4930	 * @return bool
4931	 */
4932	isModeActive: function( mode ) {
4933		return Boolean( this.activeModes.where( { id: mode } ).length );
4934	}
4935});
4936
4937// Make the `Frame` a `StateMachine`.
4938_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
4939
4940module.exports = Frame;
4941
4942},{}],44:[function(require,module,exports){
4943/**
4944 * wp.media.view.MediaFrame.ImageDetails
4945 *
4946 * A media frame for manipulating an image that's already been inserted
4947 * into a post.
4948 *
4949 * @class
4950 * @augments wp.media.view.MediaFrame.Select
4951 * @augments wp.media.view.MediaFrame
4952 * @augments wp.media.view.Frame
4953 * @augments wp.media.View
4954 * @augments wp.Backbone.View
4955 * @augments Backbone.View
4956 * @mixes wp.media.controller.StateMachine
4957 */
4958var Select = wp.media.view.MediaFrame.Select,
4959	l10n = wp.media.view.l10n,
4960	ImageDetails;
4961
4962ImageDetails = Select.extend({
4963	defaults: {
4964		id:      'image',
4965		url:     '',
4966		menu:    'image-details',
4967		content: 'image-details',
4968		toolbar: 'image-details',
4969		type:    'link',
4970		title:    l10n.imageDetailsTitle,
4971		priority: 120
4972	},
4973
4974	initialize: function( options ) {
4975		this.image = new wp.media.model.PostImage( options.metadata );
4976		this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
4977		Select.prototype.initialize.apply( this, arguments );
4978	},
4979
4980	bindHandlers: function() {
4981		Select.prototype.bindHandlers.apply( this, arguments );
4982		this.on( 'menu:create:image-details', this.createMenu, this );
4983		this.on( 'content:create:image-details', this.imageDetailsContent, this );
4984		this.on( 'content:render:edit-image', this.editImageContent, this );
4985		this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
4986		// override the select toolbar
4987		this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
4988	},
4989
4990	createStates: function() {
4991		this.states.add([
4992			new wp.media.controller.ImageDetails({
4993				image: this.image,
4994				editable: false
4995			}),
4996			new wp.media.controller.ReplaceImage({
4997				id: 'replace-image',
4998				library: wp.media.query( { type: 'image' } ),
4999				image: this.image,
5000				multiple:  false,
5001				title:     l10n.imageReplaceTitle,
5002				toolbar: 'replace',
5003				priority:  80,
5004				displaySettings: true
5005			}),
5006			new wp.media.controller.EditImage( {
5007				image: this.image,
5008				selection: this.options.selection
5009			} )
5010		]);
5011	},
5012
5013	imageDetailsContent: function( options ) {
5014		options.view = new wp.media.view.ImageDetails({
5015			controller: this,
5016			model: this.state().image,
5017			attachment: this.state().image.attachment
5018		});
5019	},
5020
5021	editImageContent: function() {
5022		var state = this.state(),
5023			model = state.get('image'),
5024			view;
5025
5026		if ( ! model ) {
5027			return;
5028		}
5029
5030		view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
5031
5032		this.content.set( view );
5033
5034		// after bringing in the frame, load the actual editor via an ajax call
5035		view.loadEditor();
5036
5037	},
5038
5039	renderImageDetailsToolbar: function() {
5040		this.toolbar.set( new wp.media.view.Toolbar({
5041			controller: this,
5042			items: {
5043				select: {
5044					style:    'primary',
5045					text:     l10n.update,
5046					priority: 80,
5047
5048					click: function() {
5049						var controller = this.controller,
5050							state = controller.state();
5051
5052						controller.close();
5053
5054						// not sure if we want to use wp.media.string.image which will create a shortcode or
5055						// perhaps wp.html.string to at least to build the <img />
5056						state.trigger( 'update', controller.image.toJSON() );
5057
5058						// Restore and reset the default state.
5059						controller.setState( controller.options.state );
5060						controller.reset();
5061					}
5062				}
5063			}
5064		}) );
5065	},
5066
5067	renderReplaceImageToolbar: function() {
5068		var frame = this,
5069			lastState = frame.lastState(),
5070			previous = lastState && lastState.id;
5071
5072		this.toolbar.set( new wp.media.view.Toolbar({
5073			controller: this,
5074			items: {
5075				back: {
5076					text:     l10n.back,
5077					priority: 20,
5078					click:    function() {
5079						if ( previous ) {
5080							frame.setState( previous );
5081						} else {
5082							frame.close();
5083						}
5084					}
5085				},
5086
5087				replace: {
5088					style:    'primary',
5089					text:     l10n.replace,
5090					priority: 80,
5091
5092					click: function() {
5093						var controller = this.controller,
5094							state = controller.state(),
5095							selection = state.get( 'selection' ),
5096							attachment = selection.single();
5097
5098						controller.close();
5099
5100						controller.image.changeAttachment( attachment, state.display( attachment ) );
5101
5102						// not sure if we want to use wp.media.string.image which will create a shortcode or
5103						// perhaps wp.html.string to at least to build the <img />
5104						state.trigger( 'replace', controller.image.toJSON() );
5105
5106						// Restore and reset the default state.
5107						controller.setState( controller.options.state );
5108						controller.reset();
5109					}
5110				}
5111			}
5112		}) );
5113	}
5114
5115});
5116
5117module.exports = ImageDetails;
5118
5119},{}],45:[function(require,module,exports){
5120/**
5121 * wp.media.view.MediaFrame.Post
5122 *
5123 * The frame for manipulating media on the Edit Post page.
5124 *
5125 * @class
5126 * @augments wp.media.view.MediaFrame.Select
5127 * @augments wp.media.view.MediaFrame
5128 * @augments wp.media.view.Frame
5129 * @augments wp.media.View
5130 * @augments wp.Backbone.View
5131 * @augments Backbone.View
5132 * @mixes wp.media.controller.StateMachine
5133 */
5134var Select = wp.media.view.MediaFrame.Select,
5135	Library = wp.media.controller.Library,
5136	l10n = wp.media.view.l10n,
5137	Post;
5138
5139Post = Select.extend({
5140	initialize: function() {
5141		this.counts = {
5142			audio: {
5143				count: wp.media.view.settings.attachmentCounts.audio,
5144				state: 'playlist'
5145			},
5146			video: {
5147				count: wp.media.view.settings.attachmentCounts.video,
5148				state: 'video-playlist'
5149			}
5150		};
5151
5152		_.defaults( this.options, {
5153			multiple:  true,
5154			editing:   false,
5155			state:    'insert',
5156			metadata:  {}
5157		});
5158
5159		// Call 'initialize' directly on the parent class.
5160		Select.prototype.initialize.apply( this, arguments );
5161		this.createIframeStates();
5162
5163	},
5164
5165	/**
5166	 * Create the default states.
5167	 */
5168	createStates: function() {
5169		var options = this.options;
5170
5171		this.states.add([
5172			// Main states.
5173			new Library({
5174				id:         'insert',
5175				title:      l10n.insertMediaTitle,
5176				priority:   20,
5177				toolbar:    'main-insert',
5178				filterable: 'all',
5179				library:    wp.media.query( options.library ),
5180				multiple:   options.multiple ? 'reset' : false,
5181				editable:   true,
5182
5183				// If the user isn't allowed to edit fields,
5184				// can they still edit it locally?
5185				allowLocalEdits: true,
5186
5187				// Show the attachment display settings.
5188				displaySettings: true,
5189				// Update user settings when users adjust the
5190				// attachment display settings.
5191				displayUserSettings: true
5192			}),
5193
5194			new Library({
5195				id:         'gallery',
5196				title:      l10n.createGalleryTitle,
5197				priority:   40,
5198				toolbar:    'main-gallery',
5199				filterable: 'uploaded',
5200				multiple:   'add',
5201				editable:   false,
5202
5203				library:  wp.media.query( _.defaults({
5204					type: 'image'
5205				}, options.library ) )
5206			}),
5207
5208			// Embed states.
5209			new wp.media.controller.Embed( { metadata: options.metadata } ),
5210
5211			new wp.media.controller.EditImage( { model: options.editImage } ),
5212
5213			// Gallery states.
5214			new wp.media.controller.GalleryEdit({
5215				library: options.selection,
5216				editing: options.editing,
5217				menu:    'gallery'
5218			}),
5219
5220			new wp.media.controller.GalleryAdd(),
5221
5222			new Library({
5223				id:         'playlist',
5224				title:      l10n.createPlaylistTitle,
5225				priority:   60,
5226				toolbar:    'main-playlist',
5227				filterable: 'uploaded',
5228				multiple:   'add',
5229				editable:   false,
5230
5231				library:  wp.media.query( _.defaults({
5232					type: 'audio'
5233				}, options.library ) )
5234			}),
5235
5236			// Playlist states.
5237			new wp.media.controller.CollectionEdit({
5238				type: 'audio',
5239				collectionType: 'playlist',
5240				title:          l10n.editPlaylistTitle,
5241				SettingsView:   wp.media.view.Settings.Playlist,
5242				library:        options.selection,
5243				editing:        options.editing,
5244				menu:           'playlist',
5245				dragInfoText:   l10n.playlistDragInfo,
5246				dragInfo:       false
5247			}),
5248
5249			new wp.media.controller.CollectionAdd({
5250				type: 'audio',
5251				collectionType: 'playlist',
5252				title: l10n.addToPlaylistTitle
5253			}),
5254
5255			new Library({
5256				id:         'video-playlist',
5257				title:      l10n.createVideoPlaylistTitle,
5258				priority:   60,
5259				toolbar:    'main-video-playlist',
5260				filterable: 'uploaded',
5261				multiple:   'add',
5262				editable:   false,
5263
5264				library:  wp.media.query( _.defaults({
5265					type: 'video'
5266				}, options.library ) )
5267			}),
5268
5269			new wp.media.controller.CollectionEdit({
5270				type: 'video',
5271				collectionType: 'playlist',
5272				title:          l10n.editVideoPlaylistTitle,
5273				SettingsView:   wp.media.view.Settings.Playlist,
5274				library:        options.selection,
5275				editing:        options.editing,
5276				menu:           'video-playlist',
5277				dragInfoText:   l10n.videoPlaylistDragInfo,
5278				dragInfo:       false
5279			}),
5280
5281			new wp.media.controller.CollectionAdd({
5282				type: 'video',
5283				collectionType: 'playlist',
5284				title: l10n.addToVideoPlaylistTitle
5285			})
5286		]);
5287
5288		if ( wp.media.view.settings.post.featuredImageId ) {
5289			this.states.add( new wp.media.controller.FeaturedImage() );
5290		}
5291	},
5292
5293	bindHandlers: function() {
5294		var handlers, checkCounts;
5295
5296		Select.prototype.bindHandlers.apply( this, arguments );
5297
5298		this.on( 'activate', this.activate, this );
5299
5300		// Only bother checking media type counts if one of the counts is zero
5301		checkCounts = _.find( this.counts, function( type ) {
5302			return type.count === 0;
5303		} );
5304
5305		if ( typeof checkCounts !== 'undefined' ) {
5306			this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
5307		}
5308
5309		this.on( 'menu:create:gallery', this.createMenu, this );
5310		this.on( 'menu:create:playlist', this.createMenu, this );
5311		this.on( 'menu:create:video-playlist', this.createMenu, this );
5312		this.on( 'toolbar:create:main-insert', this.createToolbar, this );
5313		this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
5314		this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
5315		this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
5316		this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
5317		this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
5318
5319		handlers = {
5320			menu: {
5321				'default': 'mainMenu',
5322				'gallery': 'galleryMenu',
5323				'playlist': 'playlistMenu',
5324				'video-playlist': 'videoPlaylistMenu'
5325			},
5326
5327			content: {
5328				'embed':          'embedContent',
5329				'edit-image':     'editImageContent',
5330				'edit-selection': 'editSelectionContent'
5331			},
5332
5333			toolbar: {
5334				'main-insert':      'mainInsertToolbar',
5335				'main-gallery':     'mainGalleryToolbar',
5336				'gallery-edit':     'galleryEditToolbar',
5337				'gallery-add':      'galleryAddToolbar',
5338				'main-playlist':	'mainPlaylistToolbar',
5339				'playlist-edit':	'playlistEditToolbar',
5340				'playlist-add':		'playlistAddToolbar',
5341				'main-video-playlist': 'mainVideoPlaylistToolbar',
5342				'video-playlist-edit': 'videoPlaylistEditToolbar',
5343				'video-playlist-add': 'videoPlaylistAddToolbar'
5344			}
5345		};
5346
5347		_.each( handlers, function( regionHandlers, region ) {
5348			_.each( regionHandlers, function( callback, handler ) {
5349				this.on( region + ':render:' + handler, this[ callback ], this );
5350			}, this );
5351		}, this );
5352	},
5353
5354	activate: function() {
5355		// Hide menu items for states tied to particular media types if there are no items
5356		_.each( this.counts, function( type ) {
5357			if ( type.count < 1 ) {
5358				this.menuItemVisibility( type.state, 'hide' );
5359			}
5360		}, this );
5361	},
5362
5363	mediaTypeCounts: function( model, attr ) {
5364		if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
5365			this.counts[ attr ].count++;
5366			this.menuItemVisibility( this.counts[ attr ].state, 'show' );
5367		}
5368	},
5369
5370	// Menus
5371	/**
5372	 * @param {wp.Backbone.View} view
5373	 */
5374	mainMenu: function( view ) {
5375		view.set({
5376			'library-separator': new wp.media.View({
5377				className: 'separator',
5378				priority: 100
5379			})
5380		});
5381	},
5382
5383	menuItemVisibility: function( state, visibility ) {
5384		var menu = this.menu.get();
5385		if ( visibility === 'hide' ) {
5386			menu.hide( state );
5387		} else if ( visibility === 'show' ) {
5388			menu.show( state );
5389		}
5390	},
5391	/**
5392	 * @param {wp.Backbone.View} view
5393	 */
5394	galleryMenu: function( view ) {
5395		var lastState = this.lastState(),
5396			previous = lastState && lastState.id,
5397			frame = this;
5398
5399		view.set({
5400			cancel: {
5401				text:     l10n.cancelGalleryTitle,
5402				priority: 20,
5403				click:    function() {
5404					if ( previous ) {
5405						frame.setState( previous );
5406					} else {
5407						frame.close();
5408					}
5409
5410					// Keep focus inside media modal
5411					// after canceling a gallery
5412					this.controller.modal.focusManager.focus();
5413				}
5414			},
5415			separateCancel: new wp.media.View({
5416				className: 'separator',
5417				priority: 40
5418			})
5419		});
5420	},
5421
5422	playlistMenu: function( view ) {
5423		var lastState = this.lastState(),
5424			previous = lastState && lastState.id,
5425			frame = this;
5426
5427		view.set({
5428			cancel: {
5429				text:     l10n.cancelPlaylistTitle,
5430				priority: 20,
5431				click:    function() {
5432					if ( previous ) {
5433						frame.setState( previous );
5434					} else {
5435						frame.close();
5436					}
5437				}
5438			},
5439			separateCancel: new wp.media.View({
5440				className: 'separator',
5441				priority: 40
5442			})
5443		});
5444	},
5445
5446	videoPlaylistMenu: function( view ) {
5447		var lastState = this.lastState(),
5448			previous = lastState && lastState.id,
5449			frame = this;
5450
5451		view.set({
5452			cancel: {
5453				text:     l10n.cancelVideoPlaylistTitle,
5454				priority: 20,
5455				click:    function() {
5456					if ( previous ) {
5457						frame.setState( previous );
5458					} else {
5459						frame.close();
5460					}
5461				}
5462			},
5463			separateCancel: new wp.media.View({
5464				className: 'separator',
5465				priority: 40
5466			})
5467		});
5468	},
5469
5470	// Content
5471	embedContent: function() {
5472		var view = new wp.media.view.Embed({
5473			controller: this,
5474			model:      this.state()
5475		}).render();
5476
5477		this.content.set( view );
5478
5479		if ( ! wp.media.isTouchDevice ) {
5480			view.url.focus();
5481		}
5482	},
5483
5484	editSelectionContent: function() {
5485		var state = this.state(),
5486			selection = state.get('selection'),
5487			view;
5488
5489		view = new wp.media.view.AttachmentsBrowser({
5490			controller: this,
5491			collection: selection,
5492			selection:  selection,
5493			model:      state,
5494			sortable:   true,
5495			search:     false,
5496			date:       false,
5497			dragInfo:   true,
5498
5499			AttachmentView: wp.media.view.Attachments.EditSelection
5500		}).render();
5501
5502		view.toolbar.set( 'backToLibrary', {
5503			text:     l10n.returnToLibrary,
5504			priority: -100,
5505
5506			click: function() {
5507				this.controller.content.mode('browse');
5508			}
5509		});
5510
5511		// Browse our library of attachments.
5512		this.content.set( view );
5513
5514		// Trigger the controller to set focus
5515		this.trigger( 'edit:selection', this );
5516	},
5517
5518	editImageContent: function() {
5519		var image = this.state().get('image'),
5520			view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
5521
5522		this.content.set( view );
5523
5524		// after creating the wrapper view, load the actual editor via an ajax call
5525		view.loadEditor();
5526
5527	},
5528
5529	// Toolbars
5530
5531	/**
5532	 * @param {wp.Backbone.View} view
5533	 */
5534	selectionStatusToolbar: function( view ) {
5535		var editable = this.state().get('editable');
5536
5537		view.set( 'selection', new wp.media.view.Selection({
5538			controller: this,
5539			collection: this.state().get('selection'),
5540			priority:   -40,
5541
5542			// If the selection is editable, pass the callback to
5543			// switch the content mode.
5544			editable: editable && function() {
5545				this.controller.content.mode('edit-selection');
5546			}
5547		}).render() );
5548	},
5549
5550	/**
5551	 * @param {wp.Backbone.View} view
5552	 */
5553	mainInsertToolbar: function( view ) {
5554		var controller = this;
5555
5556		this.selectionStatusToolbar( view );
5557
5558		view.set( 'insert', {
5559			style:    'primary',
5560			priority: 80,
5561			text:     l10n.insertIntoPost,
5562			requires: { selection: true },
5563
5564			/**
5565			 * @fires wp.media.controller.State#insert
5566			 */
5567			click: function() {
5568				var state = controller.state(),
5569					selection = state.get('selection');
5570
5571				controller.close();
5572				state.trigger( 'insert', selection ).reset();
5573			}
5574		});
5575	},
5576
5577	/**
5578	 * @param {wp.Backbone.View} view
5579	 */
5580	mainGalleryToolbar: function( view ) {
5581		var controller = this;
5582
5583		this.selectionStatusToolbar( view );
5584
5585		view.set( 'gallery', {
5586			style:    'primary',
5587			text:     l10n.createNewGallery,
5588			priority: 60,
5589			requires: { selection: true },
5590
5591			click: function() {
5592				var selection = controller.state().get('selection'),
5593					edit = controller.state('gallery-edit'),
5594					models = selection.where({ type: 'image' });
5595
5596				edit.set( 'library', new wp.media.model.Selection( models, {
5597					props:    selection.props.toJSON(),
5598					multiple: true
5599				}) );
5600
5601				this.controller.setState('gallery-edit');
5602
5603				// Keep focus inside media modal
5604				// after jumping to gallery view
5605				this.controller.modal.focusManager.focus();
5606			}
5607		});
5608	},
5609
5610	mainPlaylistToolbar: function( view ) {
5611		var controller = this;
5612
5613		this.selectionStatusToolbar( view );
5614
5615		view.set( 'playlist', {
5616			style:    'primary',
5617			text:     l10n.createNewPlaylist,
5618			priority: 100,
5619			requires: { selection: true },
5620
5621			click: function() {
5622				var selection = controller.state().get('selection'),
5623					edit = controller.state('playlist-edit'),
5624					models = selection.where({ type: 'audio' });
5625
5626				edit.set( 'library', new wp.media.model.Selection( models, {
5627					props:    selection.props.toJSON(),
5628					multiple: true
5629				}) );
5630
5631				this.controller.setState('playlist-edit');
5632
5633				// Keep focus inside media modal
5634				// after jumping to playlist view
5635				this.controller.modal.focusManager.focus();
5636			}
5637		});
5638	},
5639
5640	mainVideoPlaylistToolbar: function( view ) {
5641		var controller = this;
5642
5643		this.selectionStatusToolbar( view );
5644
5645		view.set( 'video-playlist', {
5646			style:    'primary',
5647			text:     l10n.createNewVideoPlaylist,
5648			priority: 100,
5649			requires: { selection: true },
5650
5651			click: function() {
5652				var selection = controller.state().get('selection'),
5653					edit = controller.state('video-playlist-edit'),
5654					models = selection.where({ type: 'video' });
5655
5656				edit.set( 'library', new wp.media.model.Selection( models, {
5657					props:    selection.props.toJSON(),
5658					multiple: true
5659				}) );
5660
5661				this.controller.setState('video-playlist-edit');
5662
5663				// Keep focus inside media modal
5664				// after jumping to video playlist view
5665				this.controller.modal.focusManager.focus();
5666			}
5667		});
5668	},
5669
5670	featuredImageToolbar: function( toolbar ) {
5671		this.createSelectToolbar( toolbar, {
5672			text:  l10n.setFeaturedImage,
5673			state: this.options.state
5674		});
5675	},
5676
5677	mainEmbedToolbar: function( toolbar ) {
5678		toolbar.view = new wp.media.view.Toolbar.Embed({
5679			controller: this
5680		});
5681	},
5682
5683	galleryEditToolbar: function() {
5684		var editing = this.state().get('editing');
5685		this.toolbar.set( new wp.media.view.Toolbar({
5686			controller: this,
5687			items: {
5688				insert: {
5689					style:    'primary',
5690					text:     editing ? l10n.updateGallery : l10n.insertGallery,
5691					priority: 80,
5692					requires: { library: true },
5693
5694					/**
5695					 * @fires wp.media.controller.State#update
5696					 */
5697					click: function() {
5698						var controller = this.controller,
5699							state = controller.state();
5700
5701						controller.close();
5702						state.trigger( 'update', state.get('library') );
5703
5704						// Restore and reset the default state.
5705						controller.setState( controller.options.state );
5706						controller.reset();
5707					}
5708				}
5709			}
5710		}) );
5711	},
5712
5713	galleryAddToolbar: function() {
5714		this.toolbar.set( new wp.media.view.Toolbar({
5715			controller: this,
5716			items: {
5717				insert: {
5718					style:    'primary',
5719					text:     l10n.addToGallery,
5720					priority: 80,
5721					requires: { selection: true },
5722
5723					/**
5724					 * @fires wp.media.controller.State#reset
5725					 */
5726					click: function() {
5727						var controller = this.controller,
5728							state = controller.state(),
5729							edit = controller.state('gallery-edit');
5730
5731						edit.get('library').add( state.get('selection').models );
5732						state.trigger('reset');
5733						controller.setState('gallery-edit');
5734					}
5735				}
5736			}
5737		}) );
5738	},
5739
5740	playlistEditToolbar: function() {
5741		var editing = this.state().get('editing');
5742		this.toolbar.set( new wp.media.view.Toolbar({
5743			controller: this,
5744			items: {
5745				insert: {
5746					style:    'primary',
5747					text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
5748					priority: 80,
5749					requires: { library: true },
5750
5751					/**
5752					 * @fires wp.media.controller.State#update
5753					 */
5754					click: function() {
5755						var controller = this.controller,
5756							state = controller.state();
5757
5758						controller.close();
5759						state.trigger( 'update', state.get('library') );
5760
5761						// Restore and reset the default state.
5762						controller.setState( controller.options.state );
5763						controller.reset();
5764					}
5765				}
5766			}
5767		}) );
5768	},
5769
5770	playlistAddToolbar: function() {
5771		this.toolbar.set( new wp.media.view.Toolbar({
5772			controller: this,
5773			items: {
5774				insert: {
5775					style:    'primary',
5776					text:     l10n.addToPlaylist,
5777					priority: 80,
5778					requires: { selection: true },
5779
5780					/**
5781					 * @fires wp.media.controller.State#reset
5782					 */
5783					click: function() {
5784						var controller = this.controller,
5785							state = controller.state(),
5786							edit = controller.state('playlist-edit');
5787
5788						edit.get('library').add( state.get('selection').models );
5789						state.trigger('reset');
5790						controller.setState('playlist-edit');
5791					}
5792				}
5793			}
5794		}) );
5795	},
5796
5797	videoPlaylistEditToolbar: function() {
5798		var editing = this.state().get('editing');
5799		this.toolbar.set( new wp.media.view.Toolbar({
5800			controller: this,
5801			items: {
5802				insert: {
5803					style:    'primary',
5804					text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
5805					priority: 140,
5806					requires: { library: true },
5807
5808					click: function() {
5809						var controller = this.controller,
5810							state = controller.state(),
5811							library = state.get('library');
5812
5813						library.type = 'video';
5814
5815						controller.close();
5816						state.trigger( 'update', library );
5817
5818						// Restore and reset the default state.
5819						controller.setState( controller.options.state );
5820						controller.reset();
5821					}
5822				}
5823			}
5824		}) );
5825	},
5826
5827	videoPlaylistAddToolbar: function() {
5828		this.toolbar.set( new wp.media.view.Toolbar({
5829			controller: this,
5830			items: {
5831				insert: {
5832					style:    'primary',
5833					text:     l10n.addToVideoPlaylist,
5834					priority: 140,
5835					requires: { selection: true },
5836
5837					click: function() {
5838						var controller = this.controller,
5839							state = controller.state(),
5840							edit = controller.state('video-playlist-edit');
5841
5842						edit.get('library').add( state.get('selection').models );
5843						state.trigger('reset');
5844						controller.setState('video-playlist-edit');
5845					}
5846				}
5847			}
5848		}) );
5849	}
5850});
5851
5852module.exports = Post;
5853
5854},{}],46:[function(require,module,exports){
5855/**
5856 * wp.media.view.MediaFrame.Select
5857 *
5858 * A frame for selecting an item or items from the media library.
5859 *
5860 * @class
5861 * @augments wp.media.view.MediaFrame
5862 * @augments wp.media.view.Frame
5863 * @augments wp.media.View
5864 * @augments wp.Backbone.View
5865 * @augments Backbone.View
5866 * @mixes wp.media.controller.StateMachine
5867 */
5868
5869var MediaFrame = wp.media.view.MediaFrame,
5870	l10n = wp.media.view.l10n,
5871	Select;
5872
5873Select = MediaFrame.extend({
5874	initialize: function() {
5875		// Call 'initialize' directly on the parent class.
5876		MediaFrame.prototype.initialize.apply( this, arguments );
5877
5878		_.defaults( this.options, {
5879			selection: [],
5880			library:   {},
5881			multiple:  false,
5882			state:    'library'
5883		});
5884
5885		this.createSelection();
5886		this.createStates();
5887		this.bindHandlers();
5888	},
5889
5890	/**
5891	 * Attach a selection collection to the frame.
5892	 *
5893	 * A selection is a collection of attachments used for a specific purpose
5894	 * by a media frame. e.g. Selecting an attachment (or many) to insert into
5895	 * post content.
5896	 *
5897	 * @see media.model.Selection
5898	 */
5899	createSelection: function() {
5900		var selection = this.options.selection;
5901
5902		if ( ! (selection instanceof wp.media.model.Selection) ) {
5903			this.options.selection = new wp.media.model.Selection( selection, {
5904				multiple: this.options.multiple
5905			});
5906		}
5907
5908		this._selection = {
5909			attachments: new wp.media.model.Attachments(),
5910			difference: []
5911		};
5912	},
5913
5914	/**
5915	 * Create the default states on the frame.
5916	 */
5917	createStates: function() {
5918		var options = this.options;
5919
5920		if ( this.options.states ) {
5921			return;
5922		}
5923
5924		// Add the default states.
5925		this.states.add([
5926			// Main states.
5927			new wp.media.controller.Library({
5928				library:   wp.media.query( options.library ),
5929				multiple:  options.multiple,
5930				title:     options.title,
5931				priority:  20
5932			})
5933		]);
5934	},
5935
5936	/**
5937	 * Bind region mode event callbacks.
5938	 *
5939	 * @see media.controller.Region.render
5940	 */
5941	bindHandlers: function() {
5942		this.on( 'router:create:browse', this.createRouter, this );
5943		this.on( 'router:render:browse', this.browseRouter, this );
5944		this.on( 'content:create:browse', this.browseContent, this );
5945		this.on( 'content:render:upload', this.uploadContent, this );
5946		this.on( 'toolbar:create:select', this.createSelectToolbar, this );
5947	},
5948
5949	/**
5950	 * Render callback for the router region in the `browse` mode.
5951	 *
5952	 * @param {wp.media.view.Router} routerView
5953	 */
5954	browseRouter: function( routerView ) {
5955		routerView.set({
5956			upload: {
5957				text:     l10n.uploadFilesTitle,
5958				priority: 20
5959			},
5960			browse: {
5961				text:     l10n.mediaLibraryTitle,
5962				priority: 40
5963			}
5964		});
5965	},
5966
5967	/**
5968	 * Render callback for the content region in the `browse` mode.
5969	 *
5970	 * @param {wp.media.controller.Region} contentRegion
5971	 */
5972	browseContent: function( contentRegion ) {
5973		var state = this.state();
5974
5975		this.$el.removeClass('hide-toolbar');
5976
5977		// Browse our library of attachments.
5978		contentRegion.view = new wp.media.view.AttachmentsBrowser({
5979			controller: this,
5980			collection: state.get('library'),
5981			selection:  state.get('selection'),
5982			model:      state,
5983			sortable:   state.get('sortable'),
5984			search:     state.get('searchable'),
5985			filters:    state.get('filterable'),
5986			date:       state.get('date'),
5987			display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
5988			dragInfo:   state.get('dragInfo'),
5989
5990			idealColumnWidth: state.get('idealColumnWidth'),
5991			suggestedWidth:   state.get('suggestedWidth'),
5992			suggestedHeight:  state.get('suggestedHeight'),
5993
5994			AttachmentView: state.get('AttachmentView')
5995		});
5996	},
5997
5998	/**
5999	 * Render callback for the content region in the `upload` mode.
6000	 */
6001	uploadContent: function() {
6002		this.$el.removeClass( 'hide-toolbar' );
6003		this.content.set( new wp.media.view.UploaderInline({
6004			controller: this
6005		}) );
6006	},
6007
6008	/**
6009	 * Toolbars
6010	 *
6011	 * @param {Object} toolbar
6012	 * @param {Object} [options={}]
6013	 * @this wp.media.controller.Region
6014	 */
6015	createSelectToolbar: function( toolbar, options ) {
6016		options = options || this.options.button || {};
6017		options.controller = this;
6018
6019		toolbar.view = new wp.media.view.Toolbar.Select( options );
6020	}
6021});
6022
6023module.exports = Select;
6024
6025},{}],47:[function(require,module,exports){
6026/**
6027 * wp.media.view.Iframe
6028 *
6029 * @class
6030 * @augments wp.media.View
6031 * @augments wp.Backbone.View
6032 * @augments Backbone.View
6033 */
6034var Iframe = wp.media.View.extend({
6035	className: 'media-iframe',
6036	/**
6037	 * @returns {wp.media.view.Iframe} Returns itself to allow chaining
6038	 */
6039	render: function() {
6040		this.views.detach();
6041		this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
6042		this.views.render();
6043		return this;
6044	}
6045});
6046
6047module.exports = Iframe;
6048
6049},{}],48:[function(require,module,exports){
6050/**
6051 * wp.media.view.ImageDetails
6052 *
6053 * @class
6054 * @augments wp.media.view.Settings.AttachmentDisplay
6055 * @augments wp.media.view.Settings
6056 * @augments wp.media.View
6057 * @augments wp.Backbone.View
6058 * @augments Backbone.View
6059 */
6060var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
6061	$ = jQuery,
6062	ImageDetails;
6063
6064ImageDetails = AttachmentDisplay.extend({
6065	className: 'image-details',
6066	template:  wp.template('image-details'),
6067	events: _.defaults( AttachmentDisplay.prototype.events, {
6068		'click .edit-attachment': 'editAttachment',
6069		'click .replace-attachment': 'replaceAttachment',
6070		'click .advanced-toggle': 'onToggleAdvanced',
6071		'change [data-setting="customWidth"]': 'onCustomSize',
6072		'change [data-setting="customHeight"]': 'onCustomSize',
6073		'keyup [data-setting="customWidth"]': 'onCustomSize',
6074		'keyup [data-setting="customHeight"]': 'onCustomSize'
6075	} ),
6076	initialize: function() {
6077		// used in AttachmentDisplay.prototype.updateLinkTo
6078		this.options.attachment = this.model.attachment;
6079		this.listenTo( this.model, 'change:url', this.updateUrl );
6080		this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
6081		this.listenTo( this.model, 'change:size', this.toggleCustomSize );
6082
6083		AttachmentDisplay.prototype.initialize.apply( this, arguments );
6084	},
6085
6086	prepare: function() {
6087		var attachment = false;
6088
6089		if ( this.model.attachment ) {
6090			attachment = this.model.attachment.toJSON();
6091		}
6092		return _.defaults({
6093			model: this.model.toJSON(),
6094			attachment: attachment
6095		}, this.options );
6096	},
6097
6098	render: function() {
6099		var args = arguments;
6100
6101		if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
6102			this.model.dfd
6103				.done( _.bind( function() {
6104					AttachmentDisplay.prototype.render.apply( this, args );
6105					this.postRender();
6106				}, this ) )
6107				.fail( _.bind( function() {
6108					this.model.attachment = false;
6109					AttachmentDisplay.prototype.render.apply( this, args );
6110					this.postRender();
6111				}, this ) );
6112		} else {
6113			AttachmentDisplay.prototype.render.apply( this, arguments );
6114			this.postRender();
6115		}
6116
6117		return this;
6118	},
6119
6120	postRender: function() {
6121		setTimeout( _.bind( this.resetFocus, this ), 10 );
6122		this.toggleLinkSettings();
6123		if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
6124			this.toggleAdvanced( true );
6125		}
6126		this.trigger( 'post-render' );
6127	},
6128
6129	resetFocus: function() {
6130		this.$( '.link-to-custom' ).blur();
6131		this.$( '.embed-media-settings' ).scrollTop( 0 );
6132	},
6133
6134	updateUrl: function() {
6135		this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
6136		this.$( '.url' ).val( this.model.get( 'url' ) );
6137	},
6138
6139	toggleLinkSettings: function() {
6140		if ( this.model.get( 'link' ) === 'none' ) {
6141			this.$( '.link-settings' ).addClass('hidden');
6142		} else {
6143			this.$( '.link-settings' ).removeClass('hidden');
6144		}
6145	},
6146
6147	toggleCustomSize: function() {
6148		if ( this.model.get( 'size' ) !== 'custom' ) {
6149			this.$( '.custom-size' ).addClass('hidden');
6150		} else {
6151			this.$( '.custom-size' ).removeClass('hidden');
6152		}
6153	},
6154
6155	onCustomSize: function( event ) {
6156		var dimension = $( event.target ).data('setting'),
6157			num = $( event.target ).val(),
6158			value;
6159
6160		// Ignore bogus input
6161		if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
6162			event.preventDefault();
6163			return;
6164		}
6165
6166		if ( dimension === 'customWidth' ) {
6167			value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
6168			this.model.set( 'customHeight', value, { silent: true } );
6169			this.$( '[data-setting="customHeight"]' ).val( value );
6170		} else {
6171			value = Math.round( this.model.get( 'aspectRatio' ) * num );
6172			this.model.set( 'customWidth', value, { silent: true  } );
6173			this.$( '[data-setting="customWidth"]' ).val( value );
6174		}
6175	},
6176
6177	onToggleAdvanced: function( event ) {
6178		event.preventDefault();
6179		this.toggleAdvanced();
6180	},
6181
6182	toggleAdvanced: function( show ) {
6183		var $advanced = this.$el.find( '.advanced-section' ),
6184			mode;
6185
6186		if ( $advanced.hasClass('advanced-visible') || show === false ) {
6187			$advanced.removeClass('advanced-visible');
6188			$advanced.find('.advanced-settings').addClass('hidden');
6189			mode = 'hide';
6190		} else {
6191			$advanced.addClass('advanced-visible');
6192			$advanced.find('.advanced-settings').removeClass('hidden');
6193			mode = 'show';
6194		}
6195
6196		window.setUserSetting( 'advImgDetails', mode );
6197	},
6198
6199	editAttachment: function( event ) {
6200		var editState = this.controller.states.get( 'edit-image' );
6201
6202		if ( window.imageEdit && editState ) {
6203			event.preventDefault();
6204			editState.set( 'image', this.model.attachment );
6205			this.controller.setState( 'edit-image' );
6206		}
6207	},
6208
6209	replaceAttachment: function( event ) {
6210		event.preventDefault();
6211		this.controller.setState( 'replace-image' );
6212	}
6213});
6214
6215module.exports = ImageDetails;
6216
6217},{}],49:[function(require,module,exports){
6218/**
6219 * wp.media.view.Label
6220 *
6221 * @class
6222 * @augments wp.media.View
6223 * @augments wp.Backbone.View
6224 * @augments Backbone.View
6225 */
6226var Label = wp.media.View.extend({
6227	tagName: 'label',
6228	className: 'screen-reader-text'