wp/wp-includes/js/media-views.js
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
   113 delete l10n.settings;
   113 delete l10n.settings;
   114 
   114 
   115 // Copy the `post` setting over to the model settings.
   115 // Copy the `post` setting over to the model settings.
   116 media.model.settings.post = media.view.settings.post;
   116 media.model.settings.post = media.view.settings.post;
   117 
   117 
   118 // Check if the browser supports CSS 3.0 transitions
   118 // Check if the browser supports CSS 3.0 transitions.
   119 $.support.transition = (function(){
   119 $.support.transition = (function(){
   120 	var style = document.documentElement.style,
   120 	var style = document.documentElement.style,
   121 		transitions = {
   121 		transitions = {
   122 			WebkitTransition: 'webkitTransitionEnd',
   122 			WebkitTransition: 'webkitTransitionEnd',
   123 			MozTransition:    'transitionend',
   123 			MozTransition:    'transitionend',
   143 
   143 
   144 /**
   144 /**
   145  * Makes it easier to bind events using transitions.
   145  * Makes it easier to bind events using transitions.
   146  *
   146  *
   147  * @param {string} selector
   147  * @param {string} selector
   148  * @param {Number} sensitivity
   148  * @param {number} sensitivity
   149  * @returns {Promise}
   149  * @return {Promise}
   150  */
   150  */
   151 media.transition = function( selector, sensitivity ) {
   151 media.transition = function( selector, sensitivity ) {
   152 	var deferred = $.Deferred();
   152 	var deferred = $.Deferred();
   153 
   153 
   154 	sensitivity = sensitivity || 2000;
   154 	sensitivity = sensitivity || 2000;
   270  *
   270  *
   271  * @memberOf wp.media.controller
   271  * @memberOf wp.media.controller
   272  *
   272  *
   273  * @class
   273  * @class
   274  *
   274  *
   275  * @param {object}        options          Options hash for the region.
   275  * @param {Object}        options          Options hash for the region.
   276  * @param {string}        options.id       Unique identifier for the region.
   276  * @param {string}        options.id       Unique identifier for the region.
   277  * @param {Backbone.View} options.view     A parent view the region exists within.
   277  * @param {Backbone.View} options.view     A parent view the region exists within.
   278  * @param {string}        options.selector jQuery selector for the region within the parent view.
   278  * @param {string}        options.selector jQuery selector for the region within the parent view.
   279  */
   279  */
   280 var Region = function( options ) {
   280 var Region = function( options ) {
   293 	 * @param {string} mode
   293 	 * @param {string} mode
   294 	 *
   294 	 *
   295 	 * @fires Region#activate
   295 	 * @fires Region#activate
   296 	 * @fires Region#deactivate
   296 	 * @fires Region#deactivate
   297 	 *
   297 	 *
   298 	 * @returns {wp.media.controller.Region} Returns itself to allow chaining.
   298 	 * @return {wp.media.controller.Region} Returns itself to allow chaining.
   299 	 */
   299 	 */
   300 	mode: function( mode ) {
   300 	mode: function( mode ) {
   301 		if ( ! mode ) {
   301 		if ( ! mode ) {
   302 			return this._mode;
   302 			return this._mode;
   303 		}
   303 		}
   332 	 * @param {string} mode
   332 	 * @param {string} mode
   333 	 *
   333 	 *
   334 	 * @fires Region#create
   334 	 * @fires Region#create
   335 	 * @fires Region#render
   335 	 * @fires Region#render
   336 	 *
   336 	 *
   337 	 * @returns {wp.media.controller.Region} Returns itself to allow chaining
   337 	 * @return {wp.media.controller.Region} Returns itself to allow chaining.
   338 	 */
   338 	 */
   339 	render: function( mode ) {
   339 	render: function( mode ) {
   340 		// If the mode isn't active, activate it.
   340 		// If the mode isn't active, activate it.
   341 		if ( mode && mode !== this._mode ) {
   341 		if ( mode && mode !== this._mode ) {
   342 			return this.mode( mode );
   342 			return this.mode( mode );
   375 	/**
   375 	/**
   376 	 * Get the region's view.
   376 	 * Get the region's view.
   377 	 *
   377 	 *
   378 	 * @since 3.5.0
   378 	 * @since 3.5.0
   379 	 *
   379 	 *
   380 	 * @returns {wp.media.View}
   380 	 * @return {wp.media.View}
   381 	 */
   381 	 */
   382 	get: function() {
   382 	get: function() {
   383 		return this.view.views.first( this.selector );
   383 		return this.view.views.first( this.selector );
   384 	},
   384 	},
   385 
   385 
   388 	 *
   388 	 *
   389 	 * @since 3.5.0
   389 	 * @since 3.5.0
   390 	 *
   390 	 *
   391 	 * @param {Array|Object} views
   391 	 * @param {Array|Object} views
   392 	 * @param {Object} [options={}]
   392 	 * @param {Object} [options={}]
   393 	 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
   393 	 * @return {wp.Backbone.Subviews} Subviews is returned to allow chaining.
   394 	 */
   394 	 */
   395 	set: function( views, options ) {
   395 	set: function( views, options ) {
   396 		if ( options ) {
   396 		if ( options ) {
   397 			options.add = false;
   397 			options.add = false;
   398 		}
   398 		}
   403 	 * Trigger regional view events on the frame.
   403 	 * Trigger regional view events on the frame.
   404 	 *
   404 	 *
   405 	 * @since 3.5.0
   405 	 * @since 3.5.0
   406 	 *
   406 	 *
   407 	 * @param {string} event
   407 	 * @param {string} event
   408 	 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
   408 	 * @return {undefined|wp.media.controller.Region} Returns itself to allow chaining.
   409 	 */
   409 	 */
   410 	trigger: function( event ) {
   410 	trigger: function( event ) {
   411 		var base, args;
   411 		var base, args;
   412 
   412 
   413 		if ( ! this._mode ) {
   413 		if ( ! this._mode ) {
   449  *
   449  *
   450  * @class
   450  * @class
   451  * @augments Backbone.Model
   451  * @augments Backbone.Model
   452  * @mixin
   452  * @mixin
   453  * @mixes Backbone.Events
   453  * @mixes Backbone.Events
   454  *
       
   455  * @param {Array} states
       
   456  */
   454  */
   457 var StateMachine = function( states ) {
   455 var StateMachine = function() {
   458 	// @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
   456 	return {
   459 	this.states = new Backbone.Collection( states );
   457 		// Use Backbone's self-propagating `extend` inheritance method.
       
   458 		extend: Backbone.Model.extend
       
   459 	};
   460 };
   460 };
   461 
   461 
   462 // Use Backbone's self-propagating `extend` inheritance method.
       
   463 StateMachine.extend = Backbone.Model.extend;
       
   464 
       
   465 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
   462 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
   466 	/**
   463 	/**
   467 	 * Fetch a state.
   464 	 * Fetch a state.
   468 	 *
   465 	 *
   469 	 * If no `id` is provided, returns the active state.
   466 	 * If no `id` is provided, returns the active state.
   470 	 *
   467 	 *
   471 	 * Implicitly creates states.
   468 	 * Implicitly creates states.
   472 	 *
   469 	 *
   473 	 * Ensure that the `states` collection exists so the `StateMachine`
   470 	 * Ensure that the `states` collection exists so the `StateMachine`
   474 	 *   can be used as a mixin.
   471 	 * can be used as a mixin.
   475 	 *
   472 	 *
   476 	 * @since 3.5.0
   473 	 * @since 3.5.0
   477 	 *
   474 	 *
   478 	 * @param {string} id
   475 	 * @param {string} id
   479 	 * @returns {wp.media.controller.State} Returns a State model
   476 	 * @return {wp.media.controller.State} Returns a State model from
   480 	 *   from the StateMachine collection
   477 	 *                                     the StateMachine collection.
   481 	 */
   478 	 */
   482 	state: function( id ) {
   479 	state: function( id ) {
   483 		this.states = this.states || new Backbone.Collection();
   480 		this.states = this.states || new Backbone.Collection();
   484 
   481 
   485 		// Default to the active state.
   482 		// Default to the active state.
   503 	 * @param {string} id
   500 	 * @param {string} id
   504 	 *
   501 	 *
   505 	 * @fires wp.media.controller.State#deactivate
   502 	 * @fires wp.media.controller.State#deactivate
   506 	 * @fires wp.media.controller.State#activate
   503 	 * @fires wp.media.controller.State#activate
   507 	 *
   504 	 *
   508 	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
   505 	 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
   509 	 */
   506 	 */
   510 	setState: function( id ) {
   507 	setState: function( id ) {
   511 		var previous = this.state();
   508 		var previous = this.state();
   512 
   509 
   513 		if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
   510 		if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
   531 	 * Call the `state()` method with no parameters to retrieve the current
   528 	 * Call the `state()` method with no parameters to retrieve the current
   532 	 * active state.
   529 	 * active state.
   533 	 *
   530 	 *
   534 	 * @since 3.5.0
   531 	 * @since 3.5.0
   535 	 *
   532 	 *
   536 	 * @returns {wp.media.controller.State} Returns a State model
   533 	 * @return {wp.media.controller.State} Returns a State model from
   537 	 *    from the StateMachine collection
   534 	 *                                     the StateMachine collection.
   538 	 */
   535 	 */
   539 	lastState: function() {
   536 	lastState: function() {
   540 		if ( this._lastState ) {
   537 		if ( this._lastState ) {
   541 			return this.state( this._lastState );
   538 			return this.state( this._lastState );
   542 		}
   539 		}
   547 _.each([ 'on', 'off', 'trigger' ], function( method ) {
   544 _.each([ 'on', 'off', 'trigger' ], function( method ) {
   548 	/**
   545 	/**
   549 	 * @function on
   546 	 * @function on
   550 	 * @memberOf wp.media.controller.StateMachine
   547 	 * @memberOf wp.media.controller.StateMachine
   551 	 * @instance
   548 	 * @instance
   552 	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
   549 	 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
   553 	 */
   550 	 */
   554 	/**
   551 	/**
   555 	 * @function off
   552 	 * @function off
   556 	 * @memberOf wp.media.controller.StateMachine
   553 	 * @memberOf wp.media.controller.StateMachine
   557 	 * @instance
   554 	 * @instance
   558 	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
   555 	 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
   559 	 */
   556 	 */
   560 	/**
   557 	/**
   561 	 * @function trigger
   558 	 * @function trigger
   562 	 * @memberOf wp.media.controller.StateMachine
   559 	 * @memberOf wp.media.controller.StateMachine
   563 	 * @instance
   560 	 * @instance
   564 	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
   561 	 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining.
   565 	 */
   562 	 */
   566 	StateMachine.prototype[ method ] = function() {
   563 	StateMachine.prototype[ method ] = function() {
   567 		// Ensure that the `states` collection exists so the `StateMachine`
   564 		// Ensure that the `states` collection exists so the `StateMachine`
   568 		// can be used as a mixin.
   565 		// can be used as a mixin.
   569 		this.states = this.states || new Backbone.Collection();
   566 		this.states = this.states || new Backbone.Collection();
   651 	 * @since 3.5.0
   648 	 * @since 3.5.0
   652 	 */
   649 	 */
   653 	reset: function() {},
   650 	reset: function() {},
   654 
   651 
   655 	/**
   652 	/**
       
   653 	 * @since 3.5.0
   656 	 * @access private
   654 	 * @access private
   657 	 * @since 3.5.0
       
   658 	 */
   655 	 */
   659 	_ready: function() {
   656 	_ready: function() {
   660 		this._updateMenu();
   657 		this._updateMenu();
   661 	},
   658 	},
   662 
   659 
   663 	/**
   660 	/**
       
   661 	 * @since 3.5.0
   664 	 * @access private
   662 	 * @access private
   665 	 * @since 3.5.0
       
   666 	*/
   663 	*/
   667 	_preActivate: function() {
   664 	_preActivate: function() {
   668 		this.active = true;
   665 		this.active = true;
   669 	},
   666 	},
   670 
   667 
   671 	/**
   668 	/**
       
   669 	 * @since 3.5.0
   672 	 * @access private
   670 	 * @access private
   673 	 * @since 3.5.0
       
   674 	 */
   671 	 */
   675 	_postActivate: function() {
   672 	_postActivate: function() {
   676 		this.on( 'change:menu', this._menu, this );
   673 		this.on( 'change:menu', this._menu, this );
   677 		this.on( 'change:titleMode', this._title, this );
   674 		this.on( 'change:titleMode', this._title, this );
   678 		this.on( 'change:content', this._content, this );
   675 		this.on( 'change:content', this._content, this );
   686 		this._content();
   683 		this._content();
   687 		this._router();
   684 		this._router();
   688 	},
   685 	},
   689 
   686 
   690 	/**
   687 	/**
       
   688 	 * @since 3.5.0
   691 	 * @access private
   689 	 * @access private
   692 	 * @since 3.5.0
       
   693 	 */
   690 	 */
   694 	_deactivate: function() {
   691 	_deactivate: function() {
   695 		this.active = false;
   692 		this.active = false;
   696 
   693 
   697 		this.frame.off( 'title:render:default', this._renderTitle, this );
   694 		this.frame.off( 'title:render:default', this._renderTitle, this );
   701 		this.off( 'change:content', this._content, this );
   698 		this.off( 'change:content', this._content, this );
   702 		this.off( 'change:toolbar', this._toolbar, this );
   699 		this.off( 'change:toolbar', this._toolbar, this );
   703 	},
   700 	},
   704 
   701 
   705 	/**
   702 	/**
       
   703 	 * @since 3.5.0
   706 	 * @access private
   704 	 * @access private
   707 	 * @since 3.5.0
       
   708 	 */
   705 	 */
   709 	_title: function() {
   706 	_title: function() {
   710 		this.frame.title.render( this.get('titleMode') || 'default' );
   707 		this.frame.title.render( this.get('titleMode') || 'default' );
   711 	},
   708 	},
   712 
   709 
   713 	/**
   710 	/**
       
   711 	 * @since 3.5.0
   714 	 * @access private
   712 	 * @access private
   715 	 * @since 3.5.0
       
   716 	 */
   713 	 */
   717 	_renderTitle: function( view ) {
   714 	_renderTitle: function( view ) {
   718 		view.$el.text( this.get('title') || '' );
   715 		view.$el.text( this.get('title') || '' );
   719 	},
   716 	},
   720 
   717 
   721 	/**
   718 	/**
       
   719 	 * @since 3.5.0
   722 	 * @access private
   720 	 * @access private
   723 	 * @since 3.5.0
       
   724 	 */
   721 	 */
   725 	_router: function() {
   722 	_router: function() {
   726 		var router = this.frame.router,
   723 		var router = this.frame.router,
   727 			mode = this.get('router'),
   724 			mode = this.get('router'),
   728 			view;
   725 			view;
   739 			view.select( this.frame.content.mode() );
   736 			view.select( this.frame.content.mode() );
   740 		}
   737 		}
   741 	},
   738 	},
   742 
   739 
   743 	/**
   740 	/**
       
   741 	 * @since 3.5.0
   744 	 * @access private
   742 	 * @access private
   745 	 * @since 3.5.0
       
   746 	 */
   743 	 */
   747 	_menu: function() {
   744 	_menu: function() {
   748 		var menu = this.frame.menu,
   745 		var menu = this.frame.menu,
   749 			mode = this.get('menu'),
   746 			mode = this.get('menu'),
   750 			view;
   747 			view;
   761 			view.select( this.id );
   758 			view.select( this.id );
   762 		}
   759 		}
   763 	},
   760 	},
   764 
   761 
   765 	/**
   762 	/**
       
   763 	 * @since 3.5.0
   766 	 * @access private
   764 	 * @access private
   767 	 * @since 3.5.0
       
   768 	 */
   765 	 */
   769 	_updateMenu: function() {
   766 	_updateMenu: function() {
   770 		var previous = this.previous('menu'),
   767 		var previous = this.previous('menu'),
   771 			menu = this.get('menu');
   768 			menu = this.get('menu');
   772 
   769 
   780 	},
   777 	},
   781 
   778 
   782 	/**
   779 	/**
   783 	 * Create a view in the media menu for the state.
   780 	 * Create a view in the media menu for the state.
   784 	 *
   781 	 *
       
   782 	 * @since 3.5.0
   785 	 * @access private
   783 	 * @access private
   786 	 * @since 3.5.0
       
   787 	 *
   784 	 *
   788 	 * @param {media.view.Menu} view The menu view.
   785 	 * @param {media.view.Menu} view The menu view.
   789 	 */
   786 	 */
   790 	_renderMenu: function( view ) {
   787 	_renderMenu: function( view ) {
   791 		var menuItem = this.get('menuItem'),
   788 		var menuItem = this.get('menuItem'),
   849 
   846 
   850 		if ( ! this.get('syncSelection') || ! manager || ! selection ) {
   847 		if ( ! this.get('syncSelection') || ! manager || ! selection ) {
   851 			return;
   848 			return;
   852 		}
   849 		}
   853 
   850 
   854 		// If the selection supports multiple items, validate the stored
   851 		/*
   855 		// attachments based on the new selection's conditions. Record
   852 		 * If the selection supports multiple items, validate the stored
   856 		// the attachments that are not included; we'll maintain a
   853 		 * attachments based on the new selection's conditions. Record
   857 		// reference to those. Other attachments are considered in flux.
   854 		 * the attachments that are not included; we'll maintain a
       
   855 		 * reference to those. Other attachments are considered in flux.
       
   856 		 */
   858 		if ( selection.multiple ) {
   857 		if ( selection.multiple ) {
   859 			selection.reset( [], { silent: true });
   858 			selection.reset( [], { silent: true });
   860 			selection.validateAll( manager.attachments );
   859 			selection.validateAll( manager.attachments );
   861 			manager.difference = _.difference( manager.attachments.models, selection.models );
   860 			manager.difference = _.difference( manager.attachments.models, selection.models );
   862 		}
   861 		}
  1052 	 * Create a model to represent display settings (alignment, etc.) for an attachment.
  1051 	 * Create a model to represent display settings (alignment, etc.) for an attachment.
  1053 	 *
  1052 	 *
  1054 	 * @since 3.5.0
  1053 	 * @since 3.5.0
  1055 	 *
  1054 	 *
  1056 	 * @param {wp.media.model.Attachment} attachment
  1055 	 * @param {wp.media.model.Attachment} attachment
  1057 	 * @returns {Backbone.Model}
  1056 	 * @return {Backbone.Model}
  1058 	 */
  1057 	 */
  1059 	display: function( attachment ) {
  1058 	display: function( attachment ) {
  1060 		var displays = this._displays;
  1059 		var displays = this._displays;
  1061 
  1060 
  1062 		if ( ! displays[ attachment.cid ] ) {
  1061 		if ( ! displays[ attachment.cid ] ) {
  1069 	 * Given an attachment, create attachment display settings properties.
  1068 	 * Given an attachment, create attachment display settings properties.
  1070 	 *
  1069 	 *
  1071 	 * @since 3.6.0
  1070 	 * @since 3.6.0
  1072 	 *
  1071 	 *
  1073 	 * @param {wp.media.model.Attachment} attachment
  1072 	 * @param {wp.media.model.Attachment} attachment
  1074 	 * @returns {Object}
  1073 	 * @return {Object}
  1075 	 */
  1074 	 */
  1076 	defaultDisplaySettings: function( attachment ) {
  1075 	defaultDisplaySettings: function( attachment ) {
  1077 		var settings = _.clone( this._defaultDisplaySettings );
  1076 		var settings = _.clone( this._defaultDisplaySettings );
  1078 
  1077 
  1079 		if ( settings.canEmbed = this.canEmbed( attachment ) ) {
  1078 		settings.canEmbed = this.canEmbed( attachment );
       
  1079 		if ( settings.canEmbed ) {
  1080 			settings.link = 'embed';
  1080 			settings.link = 'embed';
  1081 		} else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
  1081 		} else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
  1082 			settings.link = 'file';
  1082 			settings.link = 'file';
  1083 		}
  1083 		}
  1084 
  1084 
  1089 	 * Whether an attachment is image.
  1089 	 * Whether an attachment is image.
  1090 	 *
  1090 	 *
  1091 	 * @since 4.4.1
  1091 	 * @since 4.4.1
  1092 	 *
  1092 	 *
  1093 	 * @param {wp.media.model.Attachment} attachment
  1093 	 * @param {wp.media.model.Attachment} attachment
  1094 	 * @returns {Boolean}
  1094 	 * @return {boolean}
  1095 	 */
  1095 	 */
  1096 	isImageAttachment: function( attachment ) {
  1096 	isImageAttachment: function( attachment ) {
  1097 		// If uploading, we know the filename but not the mime type.
  1097 		// If uploading, we know the filename but not the mime type.
  1098 		if ( attachment.get('uploading') ) {
  1098 		if ( attachment.get('uploading') ) {
  1099 			return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
  1099 			return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
  1106 	 * Whether an attachment can be embedded (audio or video).
  1106 	 * Whether an attachment can be embedded (audio or video).
  1107 	 *
  1107 	 *
  1108 	 * @since 3.6.0
  1108 	 * @since 3.6.0
  1109 	 *
  1109 	 *
  1110 	 * @param {wp.media.model.Attachment} attachment
  1110 	 * @param {wp.media.model.Attachment} attachment
  1111 	 * @returns {Boolean}
  1111 	 * @return {boolean}
  1112 	 */
  1112 	 */
  1113 	canEmbed: function( attachment ) {
  1113 	canEmbed: function( attachment ) {
  1114 		// If uploading, we know the filename but not the mime type.
  1114 		// If uploading, we know the filename but not the mime type.
  1115 		if ( ! attachment.get('uploading') ) {
  1115 		if ( ! attachment.get('uploading') ) {
  1116 			var type = attachment.get('type');
  1116 			var type = attachment.get('type');
  1497 	/**
  1497 	/**
  1498 	 * Initializes the library. Creates a library of images if a library isn't supplied.
  1498 	 * Initializes the library. Creates a library of images if a library isn't supplied.
  1499 	 *
  1499 	 *
  1500 	 * @since 3.5.0
  1500 	 * @since 3.5.0
  1501 	 *
  1501 	 *
  1502 	 * @returns {void}
  1502 	 * @return {void}
  1503 	 */
  1503 	 */
  1504 	initialize: function() {
  1504 	initialize: function() {
  1505 		if ( ! this.get('library') ) {
  1505 		if ( ! this.get('library') ) {
  1506 			this.set( 'library', wp.media.query({ type: 'image' }) );
  1506 			this.set( 'library', wp.media.query({ type: 'image' }) );
  1507 		}
  1507 		}
  1515 	 * Removes all event listeners if in edit mode. Creates a validator to check an attachment.
  1515 	 * Removes all event listeners if in edit mode. Creates a validator to check an attachment.
  1516 	 * Resets library and re-enables event listeners. Activates edit mode. Calls the parent's activate method.
  1516 	 * Resets library and re-enables event listeners. Activates edit mode. Calls the parent's activate method.
  1517 	 *
  1517 	 *
  1518 	 * @since 3.5.0
  1518 	 * @since 3.5.0
  1519 	 *
  1519 	 *
  1520 	 * @returns {void}
  1520 	 * @return {void}
  1521 	 */
  1521 	 */
  1522 	activate: function() {
  1522 	activate: function() {
  1523 		var library = this.get('library'),
  1523 		var library = this.get('library'),
  1524 			edit    = this.frame.state('gallery-edit').get('library');
  1524 			edit    = this.frame.state('gallery-edit').get('library');
  1525 
  1525 
  1738  * @augments wp.media.controller.Library
  1738  * @augments wp.media.controller.Library
  1739  * @augments wp.media.controller.State
  1739  * @augments wp.media.controller.State
  1740  * @augments Backbone.Model
  1740  * @augments Backbone.Model
  1741  *
  1741  *
  1742  * @param {object}                     [attributes]                         The attributes hash passed to the state.
  1742  * @param {object}                     [attributes]                         The attributes hash passed to the state.
  1743  * @param {string}                     [attributes.id=library]      Unique identifier.
  1743  * @param {string}                     [attributes.id=library]              Unique identifier.
  1744  * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
  1744  * @param {string}                     attributes.title                     Title for the state. Displays in the frame's title region.
  1745  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
  1745  * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
  1746  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
  1746  * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
  1747  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
  1747  *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
  1748  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
  1748  * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
  1749  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
  1749  *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
  1757  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
  1757  * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
  1758  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
  1758  * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
  1759  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
  1759  * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
  1760  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
  1760  * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
  1761  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
  1761  *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
  1762  * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
  1762  * @param {string}                     attributes.type                      The collection's media type. (e.g. 'video').
  1763  * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
  1763  * @param {string}                     attributes.collectionType            The collection type. (e.g. 'playlist').
  1764  */
  1764  */
  1765 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
  1765 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
  1766 	defaults: _.defaults( {
  1766 	defaults: _.defaults( {
  1767 		// Selection defaults. @see media.model.Selection
  1767 		// Selection defaults. @see media.model.Selection
  1768 		multiple:      'add',
  1768 		multiple:      'add',
  1810 		// that do not exist in gallery's library.
  1810 		// that do not exist in gallery's library.
  1811 		library.validator = function( attachment ) {
  1811 		library.validator = function( attachment ) {
  1812 			return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
  1812 			return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
  1813 		};
  1813 		};
  1814 
  1814 
  1815 		// Reset the library to ensure that all attachments are re-added
  1815 		/*
  1816 		// to the collection. Do so silently, as calling `observe` will
  1816 		 * Reset the library to ensure that all attachments are re-added
  1817 		// trigger the `reset` event.
  1817 		 * to the collection. Do so silently, as calling `observe` will
       
  1818 		 * trigger the `reset` event.
       
  1819 		 */
  1818 		library.reset( library.mirroring.models, { silent: true });
  1820 		library.reset( library.mirroring.models, { silent: true });
  1819 		library.observe( edit );
  1821 		library.observe( edit );
  1820 		this.set('editLibrary', edit);
  1822 		this.set('editLibrary', edit);
  1821 
  1823 
  1822 		Library.prototype.activate.apply( this, arguments );
  1824 		Library.prototype.activate.apply( this, arguments );
  2108 	/**
  2110 	/**
  2109 	 * Activates a frame for editing a featured image.
  2111 	 * Activates a frame for editing a featured image.
  2110 	 *
  2112 	 *
  2111 	 * @since 3.9.0
  2113 	 * @since 3.9.0
  2112 	 *
  2114 	 *
  2113 	 * @returns {void}
  2115 	 * @return {void}
  2114 	 */
  2116 	 */
  2115 	activate: function() {
  2117 	activate: function() {
  2116 		this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
  2118 		this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
  2117 	},
  2119 	},
  2118 
  2120 
  2119 	/**
  2121 	/**
  2120 	 * Deactivates a frame for editing a featured image.
  2122 	 * Deactivates a frame for editing a featured image.
  2121 	 *
  2123 	 *
  2122 	 * @since 3.9.0
  2124 	 * @since 3.9.0
  2123 	 *
  2125 	 *
  2124 	 * @returns {void}
  2126 	 * @return {void}
  2125 	 */
  2127 	 */
  2126 	deactivate: function() {
  2128 	deactivate: function() {
  2127 		this.frame.off( 'toolbar:render:edit-image' );
  2129 		this.frame.off( 'toolbar:render:edit-image' );
  2128 	},
  2130 	},
  2129 
  2131 
  2134 	 * In case there is a previous state it sets that previous state otherwise it
  2136 	 * In case there is a previous state it sets that previous state otherwise it
  2135 	 * closes the frame.
  2137 	 * closes the frame.
  2136 	 *
  2138 	 *
  2137 	 * @since 3.9.0
  2139 	 * @since 3.9.0
  2138 	 *
  2140 	 *
  2139 	 * @returns {void}
  2141 	 * @return {void}
  2140 	 */
  2142 	 */
  2141 	toolbar: function() {
  2143 	toolbar: function() {
  2142 		var frame = this.frame,
  2144 		var frame = this.frame,
  2143 			lastState = frame.lastState(),
  2145 			lastState = frame.lastState(),
  2144 			previous = lastState && lastState.id;
  2146 			previous = lastState && lastState.id;
  2289 			attributes = {
  2291 			attributes = {
  2290 				type: 'link',
  2292 				type: 'link',
  2291 				scanners: []
  2293 				scanners: []
  2292 			};
  2294 			};
  2293 
  2295 
  2294 		// Scan is triggered with the list of `attributes` to set on the
  2296 		/*
  2295 		// state, useful for the 'type' attribute and 'scanners' attribute,
  2297 		 * Scan is triggered with the list of `attributes` to set on the
  2296 		// an array of promise objects for asynchronous scan operations.
  2298 		 * state, useful for the 'type' attribute and 'scanners' attribute,
       
  2299 		 * an array of promise objects for asynchronous scan operations.
       
  2300 		 */
  2297 		if ( this.props.get('url') ) {
  2301 		if ( this.props.get('url') ) {
  2298 			this.trigger( 'scan', attributes );
  2302 			this.trigger( 'scan', attributes );
  2299 		}
  2303 		}
  2300 
  2304 
  2301 		if ( attributes.scanners.length ) {
  2305 		if ( attributes.scanners.length ) {
  2399 	/**
  2403 	/**
  2400 	 * Shows the crop image window when called from the Add new image button.
  2404 	 * Shows the crop image window when called from the Add new image button.
  2401 	 *
  2405 	 *
  2402 	 * @since 4.2.0
  2406 	 * @since 4.2.0
  2403 	 *
  2407 	 *
  2404 	 * @returns {void}
  2408 	 * @return {void}
  2405 	 */
  2409 	 */
  2406 	activate: function() {
  2410 	activate: function() {
  2407 		this.frame.on( 'content:create:crop', this.createCropContent, this );
  2411 		this.frame.on( 'content:create:crop', this.createCropContent, this );
  2408 		this.frame.on( 'close', this.removeCropper, this );
  2412 		this.frame.on( 'close', this.removeCropper, this );
  2409 		this.set('selection', new Backbone.Collection(this.frame._selection.single));
  2413 		this.set('selection', new Backbone.Collection(this.frame._selection.single));
  2412 	/**
  2416 	/**
  2413 	 * Changes the state of the toolbar window to browse mode.
  2417 	 * Changes the state of the toolbar window to browse mode.
  2414 	 *
  2418 	 *
  2415 	 * @since 4.2.0
  2419 	 * @since 4.2.0
  2416 	 *
  2420 	 *
  2417 	 * @returns {void}
  2421 	 * @return {void}
  2418 	 */
  2422 	 */
  2419 	deactivate: function() {
  2423 	deactivate: function() {
  2420 		this.frame.toolbar.mode('browse');
  2424 		this.frame.toolbar.mode('browse');
  2421 	},
  2425 	},
  2422 
  2426 
  2427 	 *
  2431 	 *
  2428 	 * @since 4.2.0
  2432 	 * @since 4.2.0
  2429 	 *
  2433 	 *
  2430 	 * @fires crop window
  2434 	 * @fires crop window
  2431 	 *
  2435 	 *
  2432 	 * @returns {void}
  2436 	 * @return {void}
  2433 	 */
  2437 	 */
  2434 	createCropContent: function() {
  2438 	createCropContent: function() {
  2435 		this.cropperView = new wp.media.view.Cropper({
  2439 		this.cropperView = new wp.media.view.Cropper({
  2436 			controller: this,
  2440 			controller: this,
  2437 			attachment: this.get('selection').first()
  2441 			attachment: this.get('selection').first()
  2444 	/**
  2448 	/**
  2445 	 * Removes the image selection and closes the cropping window.
  2449 	 * Removes the image selection and closes the cropping window.
  2446 	 *
  2450 	 *
  2447 	 * @since 4.2.0
  2451 	 * @since 4.2.0
  2448 	 *
  2452 	 *
  2449 	 * @returns {void}
  2453 	 * @return {void}
  2450 	 */
  2454 	 */
  2451 	removeCropper: function() {
  2455 	removeCropper: function() {
  2452 		this.imgSelect.cancelSelection();
  2456 		this.imgSelect.cancelSelection();
  2453 		this.imgSelect.setOptions({remove: true});
  2457 		this.imgSelect.setOptions({remove: true});
  2454 		this.imgSelect.update();
  2458 		this.imgSelect.update();
  2458 	/**
  2462 	/**
  2459 	 * Checks if cropping can be skipped and creates crop toolbar accordingly.
  2463 	 * Checks if cropping can be skipped and creates crop toolbar accordingly.
  2460 	 *
  2464 	 *
  2461 	 * @since 4.2.0
  2465 	 * @since 4.2.0
  2462 	 *
  2466 	 *
  2463 	 * @returns {void}
  2467 	 * @return {void}
  2464 	 */
  2468 	 */
  2465 	createCropToolbar: function() {
  2469 	createCropToolbar: function() {
  2466 		var canSkipCrop, toolbarOptions;
  2470 		var canSkipCrop, toolbarOptions;
  2467 
  2471 
  2468 		canSkipCrop = this.get('canSkipCrop') || false;
  2472 		canSkipCrop = this.get('canSkipCrop') || false;
  2520 	/**
  2524 	/**
  2521 	 * Creates an object with the image attachment and crop properties.
  2525 	 * Creates an object with the image attachment and crop properties.
  2522 	 *
  2526 	 *
  2523 	 * @since 4.2.0
  2527 	 * @since 4.2.0
  2524 	 *
  2528 	 *
  2525 	 * @returns {$.promise} A jQuery promise with the custom header crop details.
  2529 	 * @return {$.promise} A jQuery promise with the custom header crop details.
  2526 	 */
  2530 	 */
  2527 	doCrop: function( attachment ) {
  2531 	doCrop: function( attachment ) {
  2528 		return wp.ajax.post( 'custom-header-crop', _.extend(
  2532 		return wp.ajax.post( 'custom-header-crop', _.extend(
  2529 			{},
  2533 			{},
  2530 			this.defaults.doCropArgs,
  2534 			this.defaults.doCropArgs,
  2566 	 *
  2570 	 *
  2567 	 * @since 4.3.0
  2571 	 * @since 4.3.0
  2568 	 *
  2572 	 *
  2569 	 * @param {Object} attachment The attachment to crop.
  2573 	 * @param {Object} attachment The attachment to crop.
  2570 	 *
  2574 	 *
  2571 	 * @returns {$.promise} A jQuery promise that represents the crop image request.
  2575 	 * @return {$.promise} A jQuery promise that represents the crop image request.
  2572 	 */
  2576 	 */
  2573 	doCrop: function( attachment ) {
  2577 	doCrop: function( attachment ) {
  2574 		var cropDetails = attachment.get( 'cropDetails' ),
  2578 		var cropDetails = attachment.get( 'cropDetails' ),
  2575 			control = this.get( 'control' ),
  2579 			control = this.get( 'control' ),
  2576 			ratio = cropDetails.width / cropDetails.height;
  2580 			ratio = cropDetails.width / cropDetails.height;
  2686 	/**
  2690 	/**
  2687 	 * @todo The internal comment mentions this might have been a stop-gap
  2691 	 * @todo The internal comment mentions this might have been a stop-gap
  2688 	 *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
  2692 	 *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
  2689 	 *       care of this in Backbone.View now.
  2693 	 *       care of this in Backbone.View now.
  2690 	 *
  2694 	 *
  2691 	 * @returns {wp.media.View} Returns itself to allow chaining
  2695 	 * @return {wp.media.View} Returns itself to allow chaining.
  2692 	 */
  2696 	 */
  2693 	dispose: function() {
  2697 	dispose: function() {
  2694 		// Undelegating events, removing events from the model, and
  2698 		/*
  2695 		// removing events from the controller mirror the code for
  2699 		 * Undelegating events, removing events from the model, and
  2696 		// `Backbone.View.dispose` in Backbone 0.9.8 development.
  2700 		 * removing events from the controller mirror the code for
       
  2701 		 * `Backbone.View.dispose` in Backbone 0.9.8 development.
       
  2702 		 */
  2697 		this.undelegateEvents();
  2703 		this.undelegateEvents();
  2698 
  2704 
  2699 		if ( this.model && this.model.off ) {
  2705 		if ( this.model && this.model.off ) {
  2700 			this.model.off( null, null, this );
  2706 			this.model.off( null, null, this );
  2701 		}
  2707 		}
  2710 		}
  2716 		}
  2711 
  2717 
  2712 		return this;
  2718 		return this;
  2713 	},
  2719 	},
  2714 	/**
  2720 	/**
  2715 	 * @returns {wp.media.View} Returns itself to allow chaining
  2721 	 * @return {wp.media.View} Returns itself to allow chaining.
  2716 	 */
  2722 	 */
  2717 	remove: function() {
  2723 	remove: function() {
  2718 		this.dispose();
  2724 		this.dispose();
  2719 		/**
  2725 		/**
  2720 		 * call 'remove' directly on the parent class
  2726 		 * call 'remove' directly on the parent class
  2810 		}, this );
  2816 		}, this );
  2811 	},
  2817 	},
  2812 	/**
  2818 	/**
  2813 	 * Reset all states on the frame to their defaults.
  2819 	 * Reset all states on the frame to their defaults.
  2814 	 *
  2820 	 *
  2815 	 * @returns {wp.media.view.Frame} Returns itself to allow chaining
  2821 	 * @return {wp.media.view.Frame} Returns itself to allow chaining.
  2816 	 */
  2822 	 */
  2817 	reset: function() {
  2823 	reset: function() {
  2818 		this.states.invoke( 'trigger', 'reset' );
  2824 		this.states.invoke( 'trigger', 'reset' );
  2819 		return this;
  2825 		return this;
  2820 	},
  2826 	},
  2844 	},
  2850 	},
  2845 	/**
  2851 	/**
  2846 	 * Activate a mode on the frame.
  2852 	 * Activate a mode on the frame.
  2847 	 *
  2853 	 *
  2848 	 * @param string mode Mode ID.
  2854 	 * @param string mode Mode ID.
  2849 	 * @returns {this} Returns itself to allow chaining.
  2855 	 * @return {this} Returns itself to allow chaining.
  2850 	 */
  2856 	 */
  2851 	activateMode: function( mode ) {
  2857 	activateMode: function( mode ) {
  2852 		// Bail if the mode is already active.
  2858 		// Bail if the mode is already active.
  2853 		if ( this.isModeActive( mode ) ) {
  2859 		if ( this.isModeActive( mode ) ) {
  2854 			return;
  2860 			return;
  2861 	},
  2867 	},
  2862 	/**
  2868 	/**
  2863 	 * Deactivate a mode on the frame.
  2869 	 * Deactivate a mode on the frame.
  2864 	 *
  2870 	 *
  2865 	 * @param string mode Mode ID.
  2871 	 * @param string mode Mode ID.
  2866 	 * @returns {this} Returns itself to allow chaining.
  2872 	 * @return {this} Returns itself to allow chaining.
  2867 	 */
  2873 	 */
  2868 	deactivateMode: function( mode ) {
  2874 	deactivateMode: function( mode ) {
  2869 		// Bail if the mode isn't active.
  2875 		// Bail if the mode isn't active.
  2870 		if ( ! this.isModeActive( mode ) ) {
  2876 		if ( ! this.isModeActive( mode ) ) {
  2871 			return this;
  2877 			return this;
  2882 		return this;
  2888 		return this;
  2883 	},
  2889 	},
  2884 	/**
  2890 	/**
  2885 	 * Check if a mode is enabled on the frame.
  2891 	 * Check if a mode is enabled on the frame.
  2886 	 *
  2892 	 *
  2887 	 * @param  string mode Mode ID.
  2893 	 * @param string mode Mode ID.
  2888 	 * @return bool
  2894 	 * @return bool
  2889 	 */
  2895 	 */
  2890 	isModeActive: function( mode ) {
  2896 	isModeActive: function( mode ) {
  2891 		return Boolean( this.activeModes.where( { id: mode } ).length );
  2897 		return Boolean( this.activeModes.where( { id: mode } ).length );
  2892 	}
  2898 	}
  2901 /***/ }),
  2907 /***/ }),
  2902 /* 51 */
  2908 /* 51 */
  2903 /***/ (function(module, exports) {
  2909 /***/ (function(module, exports) {
  2904 
  2910 
  2905 var Frame = wp.media.view.Frame,
  2911 var Frame = wp.media.view.Frame,
       
  2912 	l10n = wp.media.view.l10n,
  2906 	$ = jQuery,
  2913 	$ = jQuery,
  2907 	MediaFrame;
  2914 	MediaFrame;
  2908 
  2915 
  2909 /**
  2916 /**
  2910  * wp.media.view.MediaFrame
  2917  * wp.media.view.MediaFrame
  2924 	className: 'media-frame',
  2931 	className: 'media-frame',
  2925 	template:  wp.template('media-frame'),
  2932 	template:  wp.template('media-frame'),
  2926 	regions:   ['menu','title','content','toolbar','router'],
  2933 	regions:   ['menu','title','content','toolbar','router'],
  2927 
  2934 
  2928 	events: {
  2935 	events: {
  2929 		'click div.media-frame-title h1': 'toggleMenu'
  2936 		'click .media-frame-menu-toggle': 'toggleMenu'
  2930 	},
  2937 	},
  2931 
  2938 
  2932 	/**
  2939 	/**
  2933 	 * @constructs
  2940 	 * @constructs
  2934 	 */
  2941 	 */
  2935 	initialize: function() {
  2942 	initialize: function() {
  2936 		Frame.prototype.initialize.apply( this, arguments );
  2943 		Frame.prototype.initialize.apply( this, arguments );
  2937 
  2944 
  2938 		_.defaults( this.options, {
  2945 		_.defaults( this.options, {
  2939 			title:    '',
  2946 			title:    l10n.mediaFrameDefaultTitle,
  2940 			modal:    true,
  2947 			modal:    true,
  2941 			uploader: true
  2948 			uploader: true
  2942 		});
  2949 		});
  2943 
  2950 
  2944 		// Ensure core UI is enabled.
  2951 		// Ensure core UI is enabled.
  2976 
  2983 
  2977 		// Bind default title creation.
  2984 		// Bind default title creation.
  2978 		this.on( 'title:create:default', this.createTitle, this );
  2985 		this.on( 'title:create:default', this.createTitle, this );
  2979 		this.title.mode('default');
  2986 		this.title.mode('default');
  2980 
  2987 
  2981 		this.on( 'title:render', function( view ) {
       
  2982 			view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
       
  2983 		});
       
  2984 
       
  2985 		// Bind default menu.
  2988 		// Bind default menu.
  2986 		this.on( 'menu:create:default', this.createMenu, this );
  2989 		this.on( 'menu:create:default', this.createMenu, this );
  2987 	},
  2990 
  2988 	/**
  2991 		// Set the menu ARIA tab panel attributes when the modal opens.
  2989 	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
  2992 		this.on( 'open', this.setMenuTabPanelAriaAttributes, this );
       
  2993 		// Set the router ARIA tab panel attributes when the modal opens.
       
  2994 		this.on( 'open', this.setRouterTabPanelAriaAttributes, this );
       
  2995 
       
  2996 		// Update the menu ARIA tab panel attributes when the content updates.
       
  2997 		this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this );
       
  2998 		// Update the router ARIA tab panel attributes when the content updates.
       
  2999 		this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this );
       
  3000 	},
       
  3001 
       
  3002 	/**
       
  3003 	 * Sets the attributes to be used on the menu ARIA tab panel.
       
  3004 	 *
       
  3005 	 * @since 5.3.0
       
  3006 	 *
       
  3007 	 * @return {void}
       
  3008 	 */
       
  3009 	setMenuTabPanelAriaAttributes: function() {
       
  3010 		var stateId = this.state().get( 'id' ),
       
  3011 			tabPanelEl = this.$el.find( '.media-frame-tab-panel' ),
       
  3012 			ariaLabelledby;
       
  3013 
       
  3014 		tabPanelEl.removeAttr( 'role aria-labelledby tabindex' );
       
  3015 
       
  3016 		if ( this.state().get( 'menu' ) && this.menuView && this.menuView.isVisible ) {
       
  3017 			ariaLabelledby = 'menu-item-' + stateId;
       
  3018 
       
  3019 			// Set the tab panel attributes only if the tabs are visible.
       
  3020 			tabPanelEl
       
  3021 				.attr( {
       
  3022 					role: 'tabpanel',
       
  3023 					'aria-labelledby': ariaLabelledby,
       
  3024 					tabIndex: '0'
       
  3025 				} );
       
  3026 		}
       
  3027 	},
       
  3028 
       
  3029 	/**
       
  3030 	 * Sets the attributes to be used on the router ARIA tab panel.
       
  3031 	 *
       
  3032 	 * @since 5.3.0
       
  3033 	 *
       
  3034 	 * @return {void}
       
  3035 	 */
       
  3036 	setRouterTabPanelAriaAttributes: function() {
       
  3037 		var tabPanelEl = this.$el.find( '.media-frame-content' ),
       
  3038 			ariaLabelledby;
       
  3039 
       
  3040 		tabPanelEl.removeAttr( 'role aria-labelledby tabindex' );
       
  3041 
       
  3042 		// Set the tab panel attributes only if the tabs are visible.
       
  3043 		if ( this.state().get( 'router' ) && this.routerView && this.routerView.isVisible && this.content._mode ) {
       
  3044 			ariaLabelledby = 'menu-item-' + this.content._mode;
       
  3045 
       
  3046 			tabPanelEl
       
  3047 				.attr( {
       
  3048 					role: 'tabpanel',
       
  3049 					'aria-labelledby': ariaLabelledby,
       
  3050 					tabIndex: '0'
       
  3051 				} );
       
  3052 		}
       
  3053 	},
       
  3054 
       
  3055 	/**
       
  3056 	 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
  2990 	 */
  3057 	 */
  2991 	render: function() {
  3058 	render: function() {
  2992 		// Activate the default state if no active state exists.
  3059 		// Activate the default state if no active state exists.
  2993 		if ( ! this.state() && this.options.state ) {
  3060 		if ( ! this.state() && this.options.state ) {
  2994 			this.setState( this.options.state );
  3061 			this.setState( this.options.state );
  3012 	 * @param {Object} menu
  3079 	 * @param {Object} menu
  3013 	 * @this wp.media.controller.Region
  3080 	 * @this wp.media.controller.Region
  3014 	 */
  3081 	 */
  3015 	createMenu: function( menu ) {
  3082 	createMenu: function( menu ) {
  3016 		menu.view = new wp.media.view.Menu({
  3083 		menu.view = new wp.media.view.Menu({
  3017 			controller: this
  3084 			controller: this,
       
  3085 
       
  3086 			attributes: {
       
  3087 				role:               'tablist',
       
  3088 				'aria-orientation': 'vertical'
       
  3089 			}
  3018 		});
  3090 		});
  3019 	},
  3091 
  3020 
  3092 		this.menuView = menu.view;
  3021 	toggleMenu: function() {
  3093 	},
  3022 		this.$el.find( '.media-menu' ).toggleClass( 'visible' );
  3094 
       
  3095 	toggleMenu: function( event ) {
       
  3096 		var menu = this.$el.find( '.media-menu' );
       
  3097 
       
  3098 		menu.toggleClass( 'visible' );
       
  3099 		$( event.target ).attr( 'aria-expanded', menu.hasClass( 'visible' ) );
  3023 	},
  3100 	},
  3024 
  3101 
  3025 	/**
  3102 	/**
  3026 	 * @param {Object} toolbar
  3103 	 * @param {Object} toolbar
  3027 	 * @this wp.media.controller.Region
  3104 	 * @this wp.media.controller.Region
  3035 	 * @param {Object} router
  3112 	 * @param {Object} router
  3036 	 * @this wp.media.controller.Region
  3113 	 * @this wp.media.controller.Region
  3037 	 */
  3114 	 */
  3038 	createRouter: function( router ) {
  3115 	createRouter: function( router ) {
  3039 		router.view = new wp.media.view.Router({
  3116 		router.view = new wp.media.view.Router({
  3040 			controller: this
  3117 			controller: this,
       
  3118 
       
  3119 			attributes: {
       
  3120 				role:               'tablist',
       
  3121 				'aria-orientation': 'horizontal'
       
  3122 			}
  3041 		});
  3123 		});
       
  3124 
       
  3125 		this.routerView = router.view;
  3042 	},
  3126 	},
  3043 	/**
  3127 	/**
  3044 	 * @param {Object} options
  3128 	 * @param {Object} options
  3045 	 */
  3129 	 */
  3046 	createIframeStates: function( options ) {
  3130 	createIframeStates: function( options ) {
  3140 	/**
  3224 	/**
  3141 	 * @function open
  3225 	 * @function open
  3142 	 * @memberOf wp.media.view.MediaFrame
  3226 	 * @memberOf wp.media.view.MediaFrame
  3143 	 * @instance
  3227 	 * @instance
  3144 	 *
  3228 	 *
  3145 	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
  3229 	 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
  3146 	 */
  3230 	 */
  3147 	/**
  3231 	/**
  3148 	 * @function close
  3232 	 * @function close
  3149 	 * @memberOf wp.media.view.MediaFrame
  3233 	 * @memberOf wp.media.view.MediaFrame
  3150 	 * @instance
  3234 	 * @instance
  3151 	 *
  3235 	 *
  3152 	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
  3236 	 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
  3153 	 */
  3237 	 */
  3154 	/**
  3238 	/**
  3155 	 * @function attach
  3239 	 * @function attach
  3156 	 * @memberOf wp.media.view.MediaFrame
  3240 	 * @memberOf wp.media.view.MediaFrame
  3157 	 * @instance
  3241 	 * @instance
  3158 	 *
  3242 	 *
  3159 	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
  3243 	 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
  3160 	 */
  3244 	 */
  3161 	/**
  3245 	/**
  3162 	 * @function detach
  3246 	 * @function detach
  3163 	 * @memberOf wp.media.view.MediaFrame
  3247 	 * @memberOf wp.media.view.MediaFrame
  3164 	 * @instance
  3248 	 * @instance
  3165 	 *
  3249 	 *
  3166 	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
  3250 	 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
  3167 	 */
  3251 	 */
  3168 	/**
  3252 	/**
  3169 	 * @function escape
  3253 	 * @function escape
  3170 	 * @memberOf wp.media.view.MediaFrame
  3254 	 * @memberOf wp.media.view.MediaFrame
  3171 	 * @instance
  3255 	 * @instance
  3172 	 *
  3256 	 *
  3173 	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
  3257 	 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining.
  3174 	 */
  3258 	 */
  3175 	MediaFrame.prototype[ method ] = function() {
  3259 	MediaFrame.prototype[ method ] = function() {
  3176 		if ( this.modal ) {
  3260 		if ( this.modal ) {
  3177 			this.modal[ method ].apply( this.modal, arguments );
  3261 			this.modal[ method ].apply( this.modal, arguments );
  3178 		}
  3262 		}
  3245 			attachments: new wp.media.model.Attachments(),
  3329 			attachments: new wp.media.model.Attachments(),
  3246 			difference: []
  3330 			difference: []
  3247 		};
  3331 		};
  3248 	},
  3332 	},
  3249 
  3333 
       
  3334 	editImageContent: function() {
       
  3335 		var image = this.state().get('image'),
       
  3336 			view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
       
  3337 
       
  3338 		this.content.set( view );
       
  3339 
       
  3340 		// After creating the wrapper view, load the actual editor via an Ajax call.
       
  3341 		view.loadEditor();
       
  3342 	},
       
  3343 
  3250 	/**
  3344 	/**
  3251 	 * Create the default states on the frame.
  3345 	 * Create the default states on the frame.
  3252 	 */
  3346 	 */
  3253 	createStates: function() {
  3347 	createStates: function() {
  3254 		var options = this.options;
  3348 		var options = this.options;
  3263 			new wp.media.controller.Library({
  3357 			new wp.media.controller.Library({
  3264 				library:   wp.media.query( options.library ),
  3358 				library:   wp.media.query( options.library ),
  3265 				multiple:  options.multiple,
  3359 				multiple:  options.multiple,
  3266 				title:     options.title,
  3360 				title:     options.title,
  3267 				priority:  20
  3361 				priority:  20
  3268 			})
  3362 			}),
       
  3363 			new wp.media.controller.EditImage( { model: options.editImage } )
  3269 		]);
  3364 		]);
  3270 	},
  3365 	},
  3271 
  3366 
  3272 	/**
  3367 	/**
  3273 	 * Bind region mode event callbacks.
  3368 	 * Bind region mode event callbacks.
  3278 		this.on( 'router:create:browse', this.createRouter, this );
  3373 		this.on( 'router:create:browse', this.createRouter, this );
  3279 		this.on( 'router:render:browse', this.browseRouter, this );
  3374 		this.on( 'router:render:browse', this.browseRouter, this );
  3280 		this.on( 'content:create:browse', this.browseContent, this );
  3375 		this.on( 'content:create:browse', this.browseContent, this );
  3281 		this.on( 'content:render:upload', this.uploadContent, this );
  3376 		this.on( 'content:render:upload', this.uploadContent, this );
  3282 		this.on( 'toolbar:create:select', this.createSelectToolbar, this );
  3377 		this.on( 'toolbar:create:select', this.createSelectToolbar, this );
       
  3378 		this.on( 'content:render:edit-image', this.editImageContent, this );
  3283 	},
  3379 	},
  3284 
  3380 
  3285 	/**
  3381 	/**
  3286 	 * Render callback for the router region in the `browse` mode.
  3382 	 * Render callback for the router region in the `browse` mode.
  3287 	 *
  3383 	 *
  3543 
  3639 
  3544 		Select.prototype.bindHandlers.apply( this, arguments );
  3640 		Select.prototype.bindHandlers.apply( this, arguments );
  3545 
  3641 
  3546 		this.on( 'activate', this.activate, this );
  3642 		this.on( 'activate', this.activate, this );
  3547 
  3643 
  3548 		// Only bother checking media type counts if one of the counts is zero
  3644 		// Only bother checking media type counts if one of the counts is zero.
  3549 		checkCounts = _.find( this.counts, function( type ) {
  3645 		checkCounts = _.find( this.counts, function( type ) {
  3550 			return type.count === 0;
  3646 			return type.count === 0;
  3551 		} );
  3647 		} );
  3552 
  3648 
  3553 		if ( typeof checkCounts !== 'undefined' ) {
  3649 		if ( typeof checkCounts !== 'undefined' ) {
  3598 			}, this );
  3694 			}, this );
  3599 		}, this );
  3695 		}, this );
  3600 	},
  3696 	},
  3601 
  3697 
  3602 	activate: function() {
  3698 	activate: function() {
  3603 		// Hide menu items for states tied to particular media types if there are no items
  3699 		// Hide menu items for states tied to particular media types if there are no items.
  3604 		_.each( this.counts, function( type ) {
  3700 		_.each( this.counts, function( type ) {
  3605 			if ( type.count < 1 ) {
  3701 			if ( type.count < 1 ) {
  3606 				this.menuItemVisibility( type.state, 'hide' );
  3702 				this.menuItemVisibility( type.state, 'hide' );
  3607 			}
  3703 			}
  3608 		}, this );
  3704 		}, this );
  3613 			this.counts[ attr ].count++;
  3709 			this.counts[ attr ].count++;
  3614 			this.menuItemVisibility( this.counts[ attr ].state, 'show' );
  3710 			this.menuItemVisibility( this.counts[ attr ].state, 'show' );
  3615 		}
  3711 		}
  3616 	},
  3712 	},
  3617 
  3713 
  3618 	// Menus
  3714 	// Menus.
  3619 	/**
  3715 	/**
  3620 	 * @param {wp.Backbone.View} view
  3716 	 * @param {wp.Backbone.View} view
  3621 	 */
  3717 	 */
  3622 	mainMenu: function( view ) {
  3718 	mainMenu: function( view ) {
  3623 		view.set({
  3719 		view.set({
  3624 			'library-separator': new wp.media.View({
  3720 			'library-separator': new wp.media.View({
  3625 				className: 'separator',
  3721 				className:  'separator',
  3626 				priority: 100
  3722 				priority:   100,
       
  3723 				attributes: {
       
  3724 					role: 'presentation'
       
  3725 				}
  3627 			})
  3726 			})
  3628 		});
  3727 		});
  3629 	},
  3728 	},
  3630 
  3729 
  3631 	menuItemVisibility: function( state, visibility ) {
  3730 	menuItemVisibility: function( state, visibility ) {
  3653 						frame.setState( previous );
  3752 						frame.setState( previous );
  3654 					} else {
  3753 					} else {
  3655 						frame.close();
  3754 						frame.close();
  3656 					}
  3755 					}
  3657 
  3756 
  3658 					// Keep focus inside media modal
  3757 					// Move focus to the modal after canceling a Gallery.
  3659 					// after canceling a gallery
       
  3660 					this.controller.modal.focusManager.focus();
  3758 					this.controller.modal.focusManager.focus();
  3661 				}
  3759 				}
  3662 			},
  3760 			},
  3663 			separateCancel: new wp.media.View({
  3761 			separateCancel: new wp.media.View({
  3664 				className: 'separator',
  3762 				className: 'separator',
  3680 					if ( previous ) {
  3778 					if ( previous ) {
  3681 						frame.setState( previous );
  3779 						frame.setState( previous );
  3682 					} else {
  3780 					} else {
  3683 						frame.close();
  3781 						frame.close();
  3684 					}
  3782 					}
       
  3783 
       
  3784 					// Move focus to the modal after canceling an Audio Playlist.
       
  3785 					this.controller.modal.focusManager.focus();
  3685 				}
  3786 				}
  3686 			},
  3787 			},
  3687 			separateCancel: new wp.media.View({
  3788 			separateCancel: new wp.media.View({
  3688 				className: 'separator',
  3789 				className: 'separator',
  3689 				priority: 40
  3790 				priority: 40
  3704 					if ( previous ) {
  3805 					if ( previous ) {
  3705 						frame.setState( previous );
  3806 						frame.setState( previous );
  3706 					} else {
  3807 					} else {
  3707 						frame.close();
  3808 						frame.close();
  3708 					}
  3809 					}
       
  3810 
       
  3811 					// Move focus to the modal after canceling a Video Playlist.
       
  3812 					this.controller.modal.focusManager.focus();
  3709 				}
  3813 				}
  3710 			},
  3814 			},
  3711 			separateCancel: new wp.media.View({
  3815 			separateCancel: new wp.media.View({
  3712 				className: 'separator',
  3816 				className: 'separator',
  3713 				priority: 40
  3817 				priority: 40
  3714 			})
  3818 			})
  3715 		});
  3819 		});
  3716 	},
  3820 	},
  3717 
  3821 
  3718 	// Content
  3822 	// Content.
  3719 	embedContent: function() {
  3823 	embedContent: function() {
  3720 		var view = new wp.media.view.Embed({
  3824 		var view = new wp.media.view.Embed({
  3721 			controller: this,
  3825 			controller: this,
  3722 			model:      this.state()
  3826 			model:      this.state()
  3723 		}).render();
  3827 		}).render();
  3724 
  3828 
  3725 		this.content.set( view );
  3829 		this.content.set( view );
  3726 
       
  3727 		if ( ! wp.media.isTouchDevice ) {
       
  3728 			view.url.focus();
       
  3729 		}
       
  3730 	},
  3830 	},
  3731 
  3831 
  3732 	editSelectionContent: function() {
  3832 	editSelectionContent: function() {
  3733 		var state = this.state(),
  3833 		var state = this.state(),
  3734 			selection = state.get('selection'),
  3834 			selection = state.get('selection'),
  3751 			text:     l10n.returnToLibrary,
  3851 			text:     l10n.returnToLibrary,
  3752 			priority: -100,
  3852 			priority: -100,
  3753 
  3853 
  3754 			click: function() {
  3854 			click: function() {
  3755 				this.controller.content.mode('browse');
  3855 				this.controller.content.mode('browse');
       
  3856 				// Move focus to the modal when jumping back from Edit Selection to Add Media view.
       
  3857 				this.controller.modal.focusManager.focus();
  3756 			}
  3858 			}
  3757 		});
  3859 		});
  3758 
  3860 
  3759 		// Browse our library of attachments.
  3861 		// Browse our library of attachments.
  3760 		this.content.set( view );
  3862 		this.content.set( view );
  3761 
  3863 
  3762 		// Trigger the controller to set focus
  3864 		// Trigger the controller to set focus.
  3763 		this.trigger( 'edit:selection', this );
  3865 		this.trigger( 'edit:selection', this );
  3764 	},
  3866 	},
  3765 
  3867 
  3766 	editImageContent: function() {
  3868 	editImageContent: function() {
  3767 		var image = this.state().get('image'),
  3869 		var image = this.state().get('image'),
  3768 			view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
  3870 			view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
  3769 
  3871 
  3770 		this.content.set( view );
  3872 		this.content.set( view );
  3771 
  3873 
  3772 		// after creating the wrapper view, load the actual editor via an ajax call
  3874 		// After creating the wrapper view, load the actual editor via an Ajax call.
  3773 		view.loadEditor();
  3875 		view.loadEditor();
  3774 
  3876 
  3775 	},
  3877 	},
  3776 
  3878 
  3777 	// Toolbars
  3879 	// Toolbars.
  3778 
  3880 
  3779 	/**
  3881 	/**
  3780 	 * @param {wp.Backbone.View} view
  3882 	 * @param {wp.Backbone.View} view
  3781 	 */
  3883 	 */
  3782 	selectionStatusToolbar: function( view ) {
  3884 	selectionStatusToolbar: function( view ) {
  3846 				edit.set( 'library', new wp.media.model.Selection( models, {
  3948 				edit.set( 'library', new wp.media.model.Selection( models, {
  3847 					props:    selection.props.toJSON(),
  3949 					props:    selection.props.toJSON(),
  3848 					multiple: true
  3950 					multiple: true
  3849 				}) );
  3951 				}) );
  3850 
  3952 
  3851 				this.controller.setState('gallery-edit');
  3953 				// Jump to Edit Gallery view.
  3852 
  3954 				this.controller.setState( 'gallery-edit' );
  3853 				// Keep focus inside media modal
  3955 
  3854 				// after jumping to gallery view
  3956 				// Move focus to the modal after jumping to Edit Gallery view.
  3855 				this.controller.modal.focusManager.focus();
  3957 				this.controller.modal.focusManager.focus();
  3856 			}
  3958 			}
  3857 		});
  3959 		});
  3858 	},
  3960 	},
  3859 
  3961 
  3876 				edit.set( 'library', new wp.media.model.Selection( models, {
  3978 				edit.set( 'library', new wp.media.model.Selection( models, {
  3877 					props:    selection.props.toJSON(),
  3979 					props:    selection.props.toJSON(),
  3878 					multiple: true
  3980 					multiple: true
  3879 				}) );
  3981 				}) );
  3880 
  3982 
  3881 				this.controller.setState('playlist-edit');
  3983 				// Jump to Edit Audio Playlist view.
  3882 
  3984 				this.controller.setState( 'playlist-edit' );
  3883 				// Keep focus inside media modal
  3985 
  3884 				// after jumping to playlist view
  3986 				// Move focus to the modal after jumping to Edit Audio Playlist view.
  3885 				this.controller.modal.focusManager.focus();
  3987 				this.controller.modal.focusManager.focus();
  3886 			}
  3988 			}
  3887 		});
  3989 		});
  3888 	},
  3990 	},
  3889 
  3991 
  3906 				edit.set( 'library', new wp.media.model.Selection( models, {
  4008 				edit.set( 'library', new wp.media.model.Selection( models, {
  3907 					props:    selection.props.toJSON(),
  4009 					props:    selection.props.toJSON(),
  3908 					multiple: true
  4010 					multiple: true
  3909 				}) );
  4011 				}) );
  3910 
  4012 
  3911 				this.controller.setState('video-playlist-edit');
  4013 				// Jump to Edit Video Playlist view.
  3912 
  4014 				this.controller.setState( 'video-playlist-edit' );
  3913 				// Keep focus inside media modal
  4015 
  3914 				// after jumping to video playlist view
  4016 				// Move focus to the modal after jumping to Edit Video Playlist view.
  3915 				this.controller.modal.focusManager.focus();
  4017 				this.controller.modal.focusManager.focus();
  3916 			}
  4018 			}
  3917 		});
  4019 		});
  3918 	},
  4020 	},
  3919 
  4021 
  3979 							edit = controller.state('gallery-edit');
  4081 							edit = controller.state('gallery-edit');
  3980 
  4082 
  3981 						edit.get('library').add( state.get('selection').models );
  4083 						edit.get('library').add( state.get('selection').models );
  3982 						state.trigger('reset');
  4084 						state.trigger('reset');
  3983 						controller.setState('gallery-edit');
  4085 						controller.setState('gallery-edit');
       
  4086 						// Move focus to the modal when jumping back from Add to Gallery to Edit Gallery view.
       
  4087 						this.controller.modal.focusManager.focus();
  3984 					}
  4088 					}
  3985 				}
  4089 				}
  3986 			}
  4090 			}
  3987 		}) );
  4091 		}) );
  3988 	},
  4092 	},
  4036 							edit = controller.state('playlist-edit');
  4140 							edit = controller.state('playlist-edit');
  4037 
  4141 
  4038 						edit.get('library').add( state.get('selection').models );
  4142 						edit.get('library').add( state.get('selection').models );
  4039 						state.trigger('reset');
  4143 						state.trigger('reset');
  4040 						controller.setState('playlist-edit');
  4144 						controller.setState('playlist-edit');
       
  4145 						// Move focus to the modal when jumping back from Add to Audio Playlist to Edit Audio Playlist view.
       
  4146 						this.controller.modal.focusManager.focus();
  4041 					}
  4147 					}
  4042 				}
  4148 				}
  4043 			}
  4149 			}
  4044 		}) );
  4150 		}) );
  4045 	},
  4151 	},
  4090 							edit = controller.state('video-playlist-edit');
  4196 							edit = controller.state('video-playlist-edit');
  4091 
  4197 
  4092 						edit.get('library').add( state.get('selection').models );
  4198 						edit.get('library').add( state.get('selection').models );
  4093 						state.trigger('reset');
  4199 						state.trigger('reset');
  4094 						controller.setState('video-playlist-edit');
  4200 						controller.setState('video-playlist-edit');
       
  4201 						// Move focus to the modal when jumping back from Add to Video Playlist to Edit Video Playlist view.
       
  4202 						this.controller.modal.focusManager.focus();
  4095 					}
  4203 					}
  4096 				}
  4204 				}
  4097 			}
  4205 			}
  4098 		}) );
  4206 		}) );
  4099 	}
  4207 	}
  4149 		Select.prototype.bindHandlers.apply( this, arguments );
  4257 		Select.prototype.bindHandlers.apply( this, arguments );
  4150 		this.on( 'menu:create:image-details', this.createMenu, this );
  4258 		this.on( 'menu:create:image-details', this.createMenu, this );
  4151 		this.on( 'content:create:image-details', this.imageDetailsContent, this );
  4259 		this.on( 'content:create:image-details', this.imageDetailsContent, this );
  4152 		this.on( 'content:render:edit-image', this.editImageContent, this );
  4260 		this.on( 'content:render:edit-image', this.editImageContent, this );
  4153 		this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
  4261 		this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
  4154 		// override the select toolbar
  4262 		// Override the select toolbar.
  4155 		this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
  4263 		this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
  4156 	},
  4264 	},
  4157 
  4265 
  4158 	createStates: function() {
  4266 	createStates: function() {
  4159 		this.states.add([
  4267 		this.states.add([
  4197 
  4305 
  4198 		view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
  4306 		view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
  4199 
  4307 
  4200 		this.content.set( view );
  4308 		this.content.set( view );
  4201 
  4309 
  4202 		// after bringing in the frame, load the actual editor via an ajax call
  4310 		// After bringing in the frame, load the actual editor via an Ajax call.
  4203 		view.loadEditor();
  4311 		view.loadEditor();
  4204 
  4312 
  4205 	},
  4313 	},
  4206 
  4314 
  4207 	renderImageDetailsToolbar: function() {
  4315 	renderImageDetailsToolbar: function() {
  4217 						var controller = this.controller,
  4325 						var controller = this.controller,
  4218 							state = controller.state();
  4326 							state = controller.state();
  4219 
  4327 
  4220 						controller.close();
  4328 						controller.close();
  4221 
  4329 
  4222 						// not sure if we want to use wp.media.string.image which will create a shortcode or
  4330 						// Not sure if we want to use wp.media.string.image which will create a shortcode or
  4223 						// perhaps wp.html.string to at least to build the <img />
  4331 						// perhaps wp.html.string to at least to build the <img />.
  4224 						state.trigger( 'update', controller.image.toJSON() );
  4332 						state.trigger( 'update', controller.image.toJSON() );
  4225 
  4333 
  4226 						// Restore and reset the default state.
  4334 						// Restore and reset the default state.
  4227 						controller.setState( controller.options.state );
  4335 						controller.setState( controller.options.state );
  4228 						controller.reset();
  4336 						controller.reset();
  4240 		this.toolbar.set( new wp.media.view.Toolbar({
  4348 		this.toolbar.set( new wp.media.view.Toolbar({
  4241 			controller: this,
  4349 			controller: this,
  4242 			items: {
  4350 			items: {
  4243 				back: {
  4351 				back: {
  4244 					text:     l10n.back,
  4352 					text:     l10n.back,
  4245 					priority: 20,
  4353 					priority: 80,
  4246 					click:    function() {
  4354 					click:    function() {
  4247 						if ( previous ) {
  4355 						if ( previous ) {
  4248 							frame.setState( previous );
  4356 							frame.setState( previous );
  4249 						} else {
  4357 						} else {
  4250 							frame.close();
  4358 							frame.close();
  4253 				},
  4361 				},
  4254 
  4362 
  4255 				replace: {
  4363 				replace: {
  4256 					style:    'primary',
  4364 					style:    'primary',
  4257 					text:     l10n.replace,
  4365 					text:     l10n.replace,
  4258 					priority: 80,
  4366 					priority: 20,
  4259 					requires: { selection: true },
  4367 					requires: { selection: true },
  4260 
  4368 
  4261 					click: function() {
  4369 					click: function() {
  4262 						var controller = this.controller,
  4370 						var controller = this.controller,
  4263 							state = controller.state(),
  4371 							state = controller.state(),
  4266 
  4374 
  4267 						controller.close();
  4375 						controller.close();
  4268 
  4376 
  4269 						controller.image.changeAttachment( attachment, state.display( attachment ) );
  4377 						controller.image.changeAttachment( attachment, state.display( attachment ) );
  4270 
  4378 
  4271 						// not sure if we want to use wp.media.string.image which will create a shortcode or
  4379 						// Not sure if we want to use wp.media.string.image which will create a shortcode or
  4272 						// perhaps wp.html.string to at least to build the <img />
  4380 						// perhaps wp.html.string to at least to build the <img />.
  4273 						state.trigger( 'replace', controller.image.toJSON() );
  4381 						state.trigger( 'replace', controller.image.toJSON() );
  4274 
  4382 
  4275 						// Restore and reset the default state.
  4383 						// Restore and reset the default state.
  4276 						controller.setState( controller.options.state );
  4384 						controller.setState( controller.options.state );
  4277 						controller.reset();
  4385 						controller.reset();
  4327 		this.focusManager = new wp.media.view.FocusManager({
  4435 		this.focusManager = new wp.media.view.FocusManager({
  4328 			el: this.el
  4436 			el: this.el
  4329 		});
  4437 		});
  4330 	},
  4438 	},
  4331 	/**
  4439 	/**
  4332 	 * @returns {Object}
  4440 	 * @return {Object}
  4333 	 */
  4441 	 */
  4334 	prepare: function() {
  4442 	prepare: function() {
  4335 		return {
  4443 		return {
  4336 			title:          this.options.title,
  4444 			title:          this.options.title,
  4337 			hasCloseButton: this.options.hasCloseButton
  4445 			hasCloseButton: this.options.hasCloseButton
  4338 		};
  4446 		};
  4339 	},
  4447 	},
  4340 
  4448 
  4341 	/**
  4449 	/**
  4342 	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
  4450 	 * @return {wp.media.view.Modal} Returns itself to allow chaining.
  4343 	 */
  4451 	 */
  4344 	attach: function() {
  4452 	attach: function() {
  4345 		if ( this.views.attached ) {
  4453 		if ( this.views.attached ) {
  4346 			return this;
  4454 			return this;
  4347 		}
  4455 		}
  4358 
  4466 
  4359 		return this.propagate('attach');
  4467 		return this.propagate('attach');
  4360 	},
  4468 	},
  4361 
  4469 
  4362 	/**
  4470 	/**
  4363 	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
  4471 	 * @return {wp.media.view.Modal} Returns itself to allow chaining.
  4364 	 */
  4472 	 */
  4365 	detach: function() {
  4473 	detach: function() {
  4366 		if ( this.$el.is(':visible') ) {
  4474 		if ( this.$el.is(':visible') ) {
  4367 			this.close();
  4475 			this.close();
  4368 		}
  4476 		}
  4371 		this.views.attached = false;
  4479 		this.views.attached = false;
  4372 		return this.propagate('detach');
  4480 		return this.propagate('detach');
  4373 	},
  4481 	},
  4374 
  4482 
  4375 	/**
  4483 	/**
  4376 	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
  4484 	 * @return {wp.media.view.Modal} Returns itself to allow chaining.
  4377 	 */
  4485 	 */
  4378 	open: function() {
  4486 	open: function() {
  4379 		var $el = this.$el,
  4487 		var $el = this.$el,
  4380 			mceEditor;
  4488 			mceEditor;
  4381 
  4489 
  4392 		// Disable page scrolling.
  4500 		// Disable page scrolling.
  4393 		$( 'body' ).addClass( 'modal-open' );
  4501 		$( 'body' ).addClass( 'modal-open' );
  4394 
  4502 
  4395 		$el.show();
  4503 		$el.show();
  4396 
  4504 
  4397 		// Try to close the onscreen keyboard
  4505 		// Try to close the onscreen keyboard.
  4398 		if ( 'ontouchend' in document ) {
  4506 		if ( 'ontouchend' in document ) {
  4399 			if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
  4507 			if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
  4400 				mceEditor.iframeElement.focus();
  4508 				mceEditor.iframeElement.focus();
  4401 				mceEditor.iframeElement.blur();
  4509 				mceEditor.iframeElement.blur();
  4402 
  4510 
  4415 		return this.propagate('open');
  4523 		return this.propagate('open');
  4416 	},
  4524 	},
  4417 
  4525 
  4418 	/**
  4526 	/**
  4419 	 * @param {Object} options
  4527 	 * @param {Object} options
  4420 	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
  4528 	 * @return {wp.media.view.Modal} Returns itself to allow chaining.
  4421 	 */
  4529 	 */
  4422 	close: function( options ) {
  4530 	close: function( options ) {
  4423 		if ( ! this.views.attached || ! this.$el.is(':visible') ) {
  4531 		if ( ! this.views.attached || ! this.$el.is(':visible') ) {
  4424 			return this;
  4532 			return this;
  4425 		}
  4533 		}
  4426 
  4534 
  4427 		// Enable page scrolling.
  4535 		// Enable page scrolling.
  4428 		$( 'body' ).removeClass( 'modal-open' );
  4536 		$( 'body' ).removeClass( 'modal-open' );
  4429 
  4537 
  4430 		// Hide modal and remove restricted media modal tab focus once it's closed
  4538 		// Hide modal and remove restricted media modal tab focus once it's closed.
  4431 		this.$el.hide().undelegate( 'keydown' );
  4539 		this.$el.hide().undelegate( 'keydown' );
  4432 
  4540 
  4433 		/*
  4541 		/*
  4434 		 * Make visible again to assistive technologies all body children that
  4542 		 * Make visible again to assistive technologies all body children that
  4435 		 * have been made hidden when the modal opened.
  4543 		 * have been made hidden when the modal opened.
  4436 		 */
  4544 		 */
  4437 		this.focusManager.removeAriaHiddenFromBodyChildren();
  4545 		this.focusManager.removeAriaHiddenFromBodyChildren();
  4438 
  4546 
  4439 		// Put focus back in useful location once modal is closed.
  4547 		// Move focus back in useful location once modal is closed.
  4440 		if ( null !== this.clickedOpenerEl ) {
  4548 		if ( null !== this.clickedOpenerEl ) {
       
  4549 			// Move focus back to the element that opened the modal.
  4441 			this.clickedOpenerEl.focus();
  4550 			this.clickedOpenerEl.focus();
  4442 		} else {
  4551 		} else {
       
  4552 			// Fallback to the admin page main element.
  4443 			$( '#wpbody-content' )
  4553 			$( '#wpbody-content' )
  4444 				.attr( 'tabindex', '-1' )
  4554 				.attr( 'tabindex', '-1' )
  4445 				.focus();
  4555 				.focus();
  4446 		}
  4556 		}
  4447 
  4557 
  4452 		}
  4562 		}
  4453 
  4563 
  4454 		return this;
  4564 		return this;
  4455 	},
  4565 	},
  4456 	/**
  4566 	/**
  4457 	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
  4567 	 * @return {wp.media.view.Modal} Returns itself to allow chaining.
  4458 	 */
  4568 	 */
  4459 	escape: function() {
  4569 	escape: function() {
  4460 		return this.close({ escape: true });
  4570 		return this.close({ escape: true });
  4461 	},
  4571 	},
  4462 	/**
  4572 	/**
  4467 		this.escape();
  4577 		this.escape();
  4468 	},
  4578 	},
  4469 
  4579 
  4470 	/**
  4580 	/**
  4471 	 * @param {Array|Object} content Views to register to '.media-modal-content'
  4581 	 * @param {Array|Object} content Views to register to '.media-modal-content'
  4472 	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
  4582 	 * @return {wp.media.view.Modal} Returns itself to allow chaining.
  4473 	 */
  4583 	 */
  4474 	content: function( content ) {
  4584 	content: function( content ) {
  4475 		this.views.set( '.media-modal-content', content );
  4585 		this.views.set( '.media-modal-content', content );
  4476 		return this;
  4586 		return this;
  4477 	},
  4587 	},
  4479 	/**
  4589 	/**
  4480 	 * Triggers a modal event and if the `propagate` option is set,
  4590 	 * Triggers a modal event and if the `propagate` option is set,
  4481 	 * forwards events to the modal's controller.
  4591 	 * forwards events to the modal's controller.
  4482 	 *
  4592 	 *
  4483 	 * @param {string} id
  4593 	 * @param {string} id
  4484 	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
  4594 	 * @return {wp.media.view.Modal} Returns itself to allow chaining.
  4485 	 */
  4595 	 */
  4486 	propagate: function( id ) {
  4596 	propagate: function( id ) {
  4487 		this.trigger( id );
  4597 		this.trigger( id );
  4488 
  4598 
  4489 		if ( this.options.propagate ) {
  4599 		if ( this.options.propagate ) {
  4508 
  4618 
  4509 
  4619 
  4510 /***/ }),
  4620 /***/ }),
  4511 /* 56 */
  4621 /* 56 */
  4512 /***/ (function(module, exports) {
  4622 /***/ (function(module, exports) {
       
  4623 
       
  4624 var $ = jQuery;
  4513 
  4625 
  4514 /**
  4626 /**
  4515  * wp.media.view.FocusManager
  4627  * wp.media.view.FocusManager
  4516  *
  4628  *
  4517  * @memberOf wp.media.view
  4629  * @memberOf wp.media.view
  4522  * @augments Backbone.View
  4634  * @augments Backbone.View
  4523  */
  4635  */
  4524 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
  4636 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
  4525 
  4637 
  4526 	events: {
  4638 	events: {
  4527 		'keydown': 'constrainTabbing'
  4639 		'keydown': 'focusManagementMode'
  4528 	},
  4640 	},
  4529 
  4641 
  4530 	/**
  4642 	/**
  4531 	 * Moves focus to the first visible menu item in the modal.
  4643 	 * Initializes the Focus Manager.
       
  4644 	 *
       
  4645 	 * @param {Object} options The Focus Manager options.
       
  4646 	 *
       
  4647 	 * @since 5.3.0
       
  4648 	 *
       
  4649 	 * @return {void}
       
  4650 	 */
       
  4651 	initialize: function( options ) {
       
  4652 		this.mode                    = options.mode || 'constrainTabbing';
       
  4653 		this.tabsAutomaticActivation = options.tabsAutomaticActivation || false;
       
  4654 	},
       
  4655 
       
  4656  	/**
       
  4657 	 * Determines which focus management mode to use.
       
  4658 	 *
       
  4659 	 * @since 5.3.0
       
  4660 	 *
       
  4661 	 * @param {Object} event jQuery event object.
       
  4662 	 *
       
  4663 	 * @return {void}
       
  4664 	 */
       
  4665 	focusManagementMode: function( event ) {
       
  4666 		if ( this.mode === 'constrainTabbing' ) {
       
  4667 			this.constrainTabbing( event );
       
  4668 		}
       
  4669 
       
  4670 		if ( this.mode === 'tabsNavigation' ) {
       
  4671 			this.tabsNavigation( event );
       
  4672 		}
       
  4673 	},
       
  4674 
       
  4675 	/**
       
  4676 	 * Gets all the tabbable elements.
       
  4677 	 *
       
  4678 	 * @since 5.3.0
       
  4679 	 *
       
  4680 	 * @return {Object} A jQuery collection of tabbable elements.
       
  4681 	 */
       
  4682 	getTabbables: function() {
       
  4683 		// Skip the file input added by Plupload.
       
  4684 		return this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
       
  4685 	},
       
  4686 
       
  4687 	/**
       
  4688 	 * Moves focus to the modal dialog.
  4532 	 *
  4689 	 *
  4533 	 * @since 3.5.0
  4690 	 * @since 3.5.0
  4534 	 *
  4691 	 *
  4535 	 * @returns {void}
  4692 	 * @return {void}
  4536 	 */
  4693 	 */
  4537 	focus: function() {
  4694 	focus: function() {
  4538 		this.$( '.media-menu-item' ).filter( ':visible' ).first().focus();
  4695 		this.$( '.media-modal' ).focus();
  4539 	},
  4696 	},
       
  4697 
  4540 	/**
  4698 	/**
  4541 	 * Constrains navigation with the Tab key within the media view element.
  4699 	 * Constrains navigation with the Tab key within the media view element.
  4542 	 *
  4700 	 *
  4543 	 * @since 4.0.0
  4701 	 * @since 4.0.0
  4544 	 *
  4702 	 *
  4545 	 * @param {Object} event A keydown jQuery event.
  4703 	 * @param {Object} event A keydown jQuery event.
  4546 	 *
  4704 	 *
  4547 	 * @returns {void}
  4705 	 * @return {void}
  4548 	 */
  4706 	 */
  4549 	constrainTabbing: function( event ) {
  4707 	constrainTabbing: function( event ) {
  4550 		var tabbables;
  4708 		var tabbables;
  4551 
  4709 
  4552 		// Look for the tab key.
  4710 		// Look for the tab key.
  4553 		if ( 9 !== event.keyCode ) {
  4711 		if ( 9 !== event.keyCode ) {
  4554 			return;
  4712 			return;
  4555 		}
  4713 		}
  4556 
  4714 
  4557 		// Skip the file input added by Plupload.
  4715 		tabbables = this.getTabbables();
  4558 		tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
  4716 
  4559 
  4717 		// Keep tab focus within media modal while it's open.
  4560 		// Keep tab focus within media modal while it's open
       
  4561 		if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
  4718 		if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
  4562 			tabbables.first().focus();
  4719 			tabbables.first().focus();
  4563 			return false;
  4720 			return false;
  4564 		} else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
  4721 		} else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
  4565 			tabbables.last().focus();
  4722 			tabbables.last().focus();
  4566 			return false;
  4723 			return false;
  4567 		}
  4724 		}
  4568 	},
  4725 	},
  4569 
  4726 
  4570 	/**
  4727 	/**
  4571 	 * Hides from assistive technologies all the body children except the
  4728 	 * Hides from assistive technologies all the body children.
  4572 	 * provided element and other elements that should not be hidden.
  4729 	 *
       
  4730 	 * Sets an `aria-hidden="true"` attribute on all the body children except
       
  4731 	 * the provided element and other elements that should not be hidden.
  4573 	 *
  4732 	 *
  4574 	 * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy
  4733 	 * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy
  4575 	 * in Safari 11.1 and support is spotty in other browsers. In the future we
  4734 	 * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"`
  4576 	 * should consider to remove this helper function and only use `aria-modal="true"`.
  4735 	 * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside
       
  4736 	 * of the modal dialog and get hidden from assistive technologies.
  4577 	 *
  4737 	 *
  4578 	 * @since 5.2.3
  4738 	 * @since 5.2.3
  4579 	 *
  4739 	 *
  4580 	 * @param {object} visibleElement The jQuery object representing the element that should not be hidden.
  4740 	 * @param {Object} visibleElement The jQuery object representing the element that should not be hidden.
  4581 	 *
  4741 	 *
  4582 	 * @returns {void}
  4742 	 * @return {void}
  4583 	 */
  4743 	 */
  4584 	setAriaHiddenOnBodyChildren: function( visibleElement ) {
  4744 	setAriaHiddenOnBodyChildren: function( visibleElement ) {
  4585 		var bodyChildren,
  4745 		var bodyChildren,
  4586 			self = this;
  4746 			self = this;
  4587 
  4747 
  4609 
  4769 
  4610 		this.isBodyAriaHidden = true;
  4770 		this.isBodyAriaHidden = true;
  4611 	},
  4771 	},
  4612 
  4772 
  4613 	/**
  4773 	/**
  4614 	 * Makes visible again to assistive technologies all body children
  4774 	 * Unhides from assistive technologies all the body children.
       
  4775 	 *
       
  4776 	 * Makes visible again to assistive technologies all the body children
  4615 	 * previously hidden and stored in this.ariaHiddenElements.
  4777 	 * previously hidden and stored in this.ariaHiddenElements.
  4616 	 *
  4778 	 *
  4617 	 * @since 5.2.3
  4779 	 * @since 5.2.3
  4618 	 *
  4780 	 *
  4619 	 * @returns {void}
  4781 	 * @return {void}
  4620 	 */
  4782 	 */
  4621 	removeAriaHiddenFromBodyChildren: function() {
  4783 	removeAriaHiddenFromBodyChildren: function() {
  4622 		_.each( this.ariaHiddenElements, function( element ) {
  4784 		_.each( this.ariaHiddenElements, function( element ) {
  4623 			element.removeAttribute( 'aria-hidden' );
  4785 			element.removeAttribute( 'aria-hidden' );
  4624 		} );
  4786 		} );
  4630 	/**
  4792 	/**
  4631 	 * Determines if the passed element should not be hidden from assistive technologies.
  4793 	 * Determines if the passed element should not be hidden from assistive technologies.
  4632 	 *
  4794 	 *
  4633 	 * @since 5.2.3
  4795 	 * @since 5.2.3
  4634 	 *
  4796 	 *
  4635 	 * @param {object} element The DOM element that should be checked.
  4797 	 * @param {Object} element The DOM element that should be checked.
  4636 	 *
  4798 	 *
  4637 	 * @returns {boolean} Whether the element should not be hidden from assistive technologies.
  4799 	 * @return {boolean} Whether the element should not be hidden from assistive technologies.
  4638 	 */
  4800 	 */
  4639 	elementShouldBeHidden: function( element ) {
  4801 	elementShouldBeHidden: function( element ) {
  4640 		var role = element.getAttribute( 'role' ),
  4802 		var role = element.getAttribute( 'role' ),
  4641 			liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ];
  4803 			liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ];
  4642 
  4804 
  4663 	 * Stores an array of DOM elements that should be hidden from assistive
  4825 	 * Stores an array of DOM elements that should be hidden from assistive
  4664 	 * technologies, for example when the media modal dialog opens.
  4826 	 * technologies, for example when the media modal dialog opens.
  4665 	 *
  4827 	 *
  4666 	 * @since 5.2.3
  4828 	 * @since 5.2.3
  4667 	 */
  4829 	 */
  4668 	ariaHiddenElements: []
  4830 	ariaHiddenElements: [],
       
  4831 
       
  4832 	/**
       
  4833 	 * Holds the jQuery collection of ARIA tabs.
       
  4834 	 *
       
  4835 	 * @since 5.3.0
       
  4836 	 */
       
  4837 	tabs: $(),
       
  4838 
       
  4839 	/**
       
  4840 	 * Sets up tabs in an ARIA tabbed interface.
       
  4841 	 *
       
  4842 	 * @since 5.3.0
       
  4843 	 *
       
  4844 	 * @param {Object} event jQuery event object.
       
  4845 	 *
       
  4846 	 * @return {void}
       
  4847 	 */
       
  4848 	setupAriaTabs: function() {
       
  4849 		this.tabs = this.$( '[role="tab"]' );
       
  4850 
       
  4851 		// Set up initial attributes.
       
  4852 		this.tabs.attr( {
       
  4853 			'aria-selected': 'false',
       
  4854 			tabIndex: '-1'
       
  4855 		} );
       
  4856 
       
  4857 		// Set up attributes on the initially active tab.
       
  4858 		this.tabs.filter( '.active' )
       
  4859 			.removeAttr( 'tabindex' )
       
  4860 			.attr( 'aria-selected', 'true' );
       
  4861 	},
       
  4862 
       
  4863 	/**
       
  4864 	 * Enables arrows navigation within the ARIA tabbed interface.
       
  4865 	 *
       
  4866 	 * @since 5.3.0
       
  4867 	 *
       
  4868 	 * @param {Object} event jQuery event object.
       
  4869 	 *
       
  4870 	 * @return {void}
       
  4871 	 */
       
  4872 	tabsNavigation: function( event ) {
       
  4873 		var orientation = 'horizontal',
       
  4874 			keys = [ 32, 35, 36, 37, 38, 39, 40 ];
       
  4875 
       
  4876 		// Return if not Spacebar, End, Home, or Arrow keys.
       
  4877 		if ( keys.indexOf( event.which ) === -1 ) {
       
  4878 			return;
       
  4879 		}
       
  4880 
       
  4881 		// Determine navigation direction.
       
  4882 		if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) {
       
  4883 			orientation = 'vertical';
       
  4884 		}
       
  4885 
       
  4886 		// Make Up and Down arrow keys do nothing with horizontal tabs.
       
  4887 		if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) {
       
  4888 			return;
       
  4889 		}
       
  4890 
       
  4891 		// Make Left and Right arrow keys do nothing with vertical tabs.
       
  4892 		if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) {
       
  4893 			return;
       
  4894 		}
       
  4895 
       
  4896 		this.switchTabs( event, this.tabs );
       
  4897 	},
       
  4898 
       
  4899 	/**
       
  4900 	 * Switches tabs in the ARIA tabbed interface.
       
  4901 	 *
       
  4902 	 * @since 5.3.0
       
  4903 	 *
       
  4904 	 * @param {Object} event jQuery event object.
       
  4905 	 *
       
  4906 	 * @return {void}
       
  4907 	 */
       
  4908 	switchTabs: function( event ) {
       
  4909 		var key   = event.which,
       
  4910 			index = this.tabs.index( $( event.target ) ),
       
  4911 			newIndex;
       
  4912 
       
  4913 		switch ( key ) {
       
  4914 			// Space bar: Activate current targeted tab.
       
  4915 			case 32: {
       
  4916 				this.activateTab( this.tabs[ index ] );
       
  4917 				break;
       
  4918 			}
       
  4919 			// End key: Activate last tab.
       
  4920 			case 35: {
       
  4921 				event.preventDefault();
       
  4922 				this.activateTab( this.tabs[ this.tabs.length - 1 ] );
       
  4923 				break;
       
  4924 			}
       
  4925 			// Home key: Activate first tab.
       
  4926 			case 36: {
       
  4927 				event.preventDefault();
       
  4928 				this.activateTab( this.tabs[ 0 ] );
       
  4929 				break;
       
  4930 			}
       
  4931 			// Left and up keys: Activate previous tab.
       
  4932 			case 37:
       
  4933 			case 38: {
       
  4934 				event.preventDefault();
       
  4935 				newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1;
       
  4936 				this.activateTab( this.tabs[ newIndex ] );
       
  4937 				break;
       
  4938 			}
       
  4939 			// Right and down keys: Activate next tab.
       
  4940 			case 39:
       
  4941 			case 40: {
       
  4942 				event.preventDefault();
       
  4943 				newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1;
       
  4944 				this.activateTab( this.tabs[ newIndex ] );
       
  4945 				break;
       
  4946 			}
       
  4947 		}
       
  4948 	},
       
  4949 
       
  4950 	/**
       
  4951 	 * Sets a single tab to be focusable and semantically selected.
       
  4952 	 *
       
  4953 	 * @since 5.3.0
       
  4954 	 *
       
  4955 	 * @param {Object} tab The tab DOM element.
       
  4956 	 *
       
  4957 	 * @return {void}
       
  4958 	 */
       
  4959 	activateTab: function( tab ) {
       
  4960 		if ( ! tab ) {
       
  4961 			return;
       
  4962 		}
       
  4963 
       
  4964 		// The tab is a DOM element: no need for jQuery methods.
       
  4965 		tab.focus();
       
  4966 
       
  4967 		// Handle automatic activation.
       
  4968 		if ( this.tabsAutomaticActivation ) {
       
  4969 			tab.removeAttribute( 'tabindex' );
       
  4970 			tab.setAttribute( 'aria-selected', 'true' );
       
  4971 			tab.click();
       
  4972 
       
  4973 			return;
       
  4974 		}
       
  4975 
       
  4976 		// Handle manual activation.
       
  4977 		$( tab ).on( 'click', function() {
       
  4978 			tab.removeAttribute( 'tabindex' );
       
  4979 			tab.setAttribute( 'aria-selected', 'true' );
       
  4980 		} );
       
  4981  	}
  4669 });
  4982 });
  4670 
  4983 
  4671 module.exports = FocusManager;
  4984 module.exports = FocusManager;
  4672 
  4985 
  4673 
  4986 
  4856 	},
  5169 	},
  4857 
  5170 
  4858 	/**
  5171 	/**
  4859 	 * Check browser support for drag'n'drop.
  5172 	 * Check browser support for drag'n'drop.
  4860 	 *
  5173 	 *
  4861 	 * @return Boolean
  5174 	 * @return {boolean}
  4862 	 */
  5175 	 */
  4863 	browserSupport: function() {
  5176 	browserSupport: function() {
  4864 		var supports = false, div = document.createElement('div');
  5177 		var supports = false, div = document.createElement('div');
  4865 
  5178 
  4866 		supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
  5179 		supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
  4921 
  5234 
  4922 	/**
  5235 	/**
  4923 	 * When a file is dropped on the editor uploader, open up an editor media workflow
  5236 	 * When a file is dropped on the editor uploader, open up an editor media workflow
  4924 	 * and upload the file immediately.
  5237 	 * and upload the file immediately.
  4925 	 *
  5238 	 *
  4926 	 * @param  {jQuery.Event} event The 'drop' event.
  5239 	 * @param {jQuery.Event} event The 'drop' event.
  4927 	 */
  5240 	 */
  4928 	drop: function( event ) {
  5241 	drop: function( event ) {
  4929 		var $wrap, uploadView;
  5242 		var $wrap, uploadView;
  4930 
  5243 
  4931 		this.containerDragleave( event );
  5244 		this.containerDragleave( event );
  5083 		}
  5396 		}
  5084 
  5397 
  5085 		return data;
  5398 		return data;
  5086 	},
  5399 	},
  5087 	/**
  5400 	/**
  5088 	 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
  5401 	 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining.
  5089 	 */
  5402 	 */
  5090 	dispose: function() {
  5403 	dispose: function() {
  5091 		if ( this.disposing ) {
  5404 		if ( this.disposing ) {
  5092 			/**
  5405 			/**
  5093 			 * call 'dispose' directly on the parent class
  5406 			 * call 'dispose' directly on the parent class
  5094 			 */
  5407 			 */
  5095 			return View.prototype.dispose.apply( this, arguments );
  5408 			return View.prototype.dispose.apply( this, arguments );
  5096 		}
  5409 		}
  5097 
  5410 
  5098 		// Run remove on `dispose`, so we can be sure to refresh the
  5411 		/*
  5099 		// uploader with a view-less DOM. Track whether we're disposing
  5412 		 * Run remove on `dispose`, so we can be sure to refresh the
  5100 		// so we don't trigger an infinite loop.
  5413 		 * uploader with a view-less DOM. Track whether we're disposing
       
  5414 		 * so we don't trigger an infinite loop.
       
  5415 		 */
  5101 		this.disposing = true;
  5416 		this.disposing = true;
  5102 		return this.remove();
  5417 		return this.remove();
  5103 	},
  5418 	},
  5104 	/**
  5419 	/**
  5105 	 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
  5420 	 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining.
  5106 	 */
  5421 	 */
  5107 	remove: function() {
  5422 	remove: function() {
  5108 		/**
  5423 		/**
  5109 		 * call 'remove' directly on the parent class
  5424 		 * call 'remove' directly on the parent class
  5110 		 */
  5425 		 */
  5120 		if ( uploader ) {
  5435 		if ( uploader ) {
  5121 			uploader.refresh();
  5436 			uploader.refresh();
  5122 		}
  5437 		}
  5123 	},
  5438 	},
  5124 	/**
  5439 	/**
  5125 	 * @returns {wp.media.view.UploaderInline}
  5440 	 * @return {wp.media.view.UploaderInline}
  5126 	 */
  5441 	 */
  5127 	ready: function() {
  5442 	ready: function() {
  5128 		var $browser = this.options.$browser,
  5443 		var $browser = this.options.$browser,
  5129 			$placeholder;
  5444 			$placeholder;
  5130 
  5445 
  5202 		this.errors.reset();
  5517 		this.errors.reset();
  5203 		this.errors.on( 'add remove reset', this.visibility, this );
  5518 		this.errors.on( 'add remove reset', this.visibility, this );
  5204 		this.errors.on( 'add', this.error, this );
  5519 		this.errors.on( 'add', this.error, this );
  5205 	},
  5520 	},
  5206 	/**
  5521 	/**
  5207 	 * @returns {wp.media.view.UploaderStatus}
  5522 	 * @return {wp.media.view.UploaderStatus}
  5208 	 */
  5523 	 */
  5209 	dispose: function() {
  5524 	dispose: function() {
  5210 		wp.Uploader.queue.off( null, null, this );
  5525 		wp.Uploader.queue.off( null, null, this );
  5211 		/**
  5526 		/**
  5212 		 * call 'dispose' directly on the parent class
  5527 		 * call 'dispose' directly on the parent class
  5271 		this.$total.text( queue.length );
  5586 		this.$total.text( queue.length );
  5272 		this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
  5587 		this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
  5273 	},
  5588 	},
  5274 	/**
  5589 	/**
  5275 	 * @param {string} filename
  5590 	 * @param {string} filename
  5276 	 * @returns {string}
  5591 	 * @return {string}
  5277 	 */
  5592 	 */
  5278 	filename: function( filename ) {
  5593 	filename: function( filename ) {
  5279 		return _.escape( filename );
  5594 		return _.escape( filename );
  5280 	},
  5595 	},
  5281 	/**
  5596 	/**
  5282 	 * @param {Backbone.Model} error
  5597 	 * @param {Backbone.Model} error
  5283 	 */
  5598 	 */
  5284 	error: function( error ) {
  5599 	error: function( error ) {
  5285 		this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
  5600 		var statusError = new wp.media.view.UploaderStatusError( {
  5286 			filename: this.filename( error.get('file').name ),
  5601 			filename: this.filename( error.get( 'file' ).name ),
  5287 			message:  error.get('message')
  5602 			message:  error.get( 'message' )
  5288 		}), { at: 0 });
  5603 		} );
       
  5604 
       
  5605 		// Can show additional info here while retrying to create image sub-sizes.
       
  5606 		this.views.add( '.upload-errors', statusError, { at: 0 } );
  5289 	},
  5607 	},
  5290 
  5608 
  5291 	dismiss: function() {
  5609 	dismiss: function() {
  5292 		var errors = this.views.get('.upload-errors');
  5610 		var errors = this.views.get('.upload-errors');
  5293 
  5611 
  5294 		if ( errors ) {
  5612 		if ( errors ) {
  5295 			_.invoke( errors, 'remove' );
  5613 			_.invoke( errors, 'remove' );
  5296 		}
  5614 		}
  5297 		wp.Uploader.errors.reset();
  5615 		wp.Uploader.errors.reset();
  5298 		// Keep focus within the modal after the dismiss button gets removed from the DOM.
  5616 		// Move focus to the modal after the dismiss button gets removed from the DOM.
  5299 		this.controller.modal.focusManager.focus();
  5617 		if ( this.controller.modal ) {
       
  5618 			this.controller.modal.focusManager.focus();
       
  5619 		}
  5300 	}
  5620 	}
  5301 });
  5621 });
  5302 
  5622 
  5303 module.exports = UploaderStatus;
  5623 module.exports = UploaderStatus;
  5304 
  5624 
  5379 		if ( library ) {
  5699 		if ( library ) {
  5380 			library.on( 'add remove reset', this.refresh, this );
  5700 			library.on( 'add remove reset', this.refresh, this );
  5381 		}
  5701 		}
  5382 	},
  5702 	},
  5383 	/**
  5703 	/**
  5384 	 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
  5704 	 * @return {wp.media.view.Toolbar} Returns itsef to allow chaining
  5385 	 */
  5705 	 */
  5386 	dispose: function() {
  5706 	dispose: function() {
  5387 		if ( this.selection ) {
  5707 		if ( this.selection ) {
  5388 			this.selection.off( null, null, this );
  5708 			this.selection.off( null, null, this );
  5389 		}
  5709 		}
  5403 
  5723 
  5404 	/**
  5724 	/**
  5405 	 * @param {string} id
  5725 	 * @param {string} id
  5406 	 * @param {Backbone.View|Object} view
  5726 	 * @param {Backbone.View|Object} view
  5407 	 * @param {Object} [options={}]
  5727 	 * @param {Object} [options={}]
  5408 	 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
  5728 	 * @return {wp.media.view.Toolbar} Returns itself to allow chaining.
  5409 	 */
  5729 	 */
  5410 	set: function( id, view, options ) {
  5730 	set: function( id, view, options ) {
  5411 		var list;
  5731 		var list;
  5412 		options = options || {};
  5732 		options = options || {};
  5413 
  5733 
  5437 
  5757 
  5438 		return this;
  5758 		return this;
  5439 	},
  5759 	},
  5440 	/**
  5760 	/**
  5441 	 * @param {string} id
  5761 	 * @param {string} id
  5442 	 * @returns {wp.media.view.Button}
  5762 	 * @return {wp.media.view.Button}
  5443 	 */
  5763 	 */
  5444 	get: function( id ) {
  5764 	get: function( id ) {
  5445 		return this._views[ id ];
  5765 		return this._views[ id ];
  5446 	},
  5766 	},
  5447 	/**
  5767 	/**
  5448 	 * @param {string} id
  5768 	 * @param {string} id
  5449 	 * @param {Object} options
  5769 	 * @param {Object} options
  5450 	 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
  5770 	 * @return {wp.media.view.Toolbar} Returns itself to allow chaining.
  5451 	 */
  5771 	 */
  5452 	unset: function( id, options ) {
  5772 	unset: function( id, options ) {
  5453 		delete this._views[ id ];
  5773 		delete this._views[ id ];
  5454 		this.primary.unset( id, options );
  5774 		this.primary.unset( id, options );
  5455 		this.secondary.unset( id, options );
  5775 		this.secondary.unset( id, options );
  5471 			}
  5791 			}
  5472 
  5792 
  5473 			var requires = button.options.requires,
  5793 			var requires = button.options.requires,
  5474 				disabled = false;
  5794 				disabled = false;
  5475 
  5795 
  5476 			// Prevent insertion of attachments if any of them are still uploading
  5796 			// Prevent insertion of attachments if any of them are still uploading.
  5477 			if ( selection && selection.models ) {
  5797 			if ( selection && selection.models ) {
  5478 				disabled = _.some( selection.models, function( attachment ) {
  5798 				disabled = _.some( selection.models, function( attachment ) {
  5479 					return attachment.get('uploading') === true;
  5799 					return attachment.get('uploading') === true;
  5480 				});
  5800 				});
  5481 			}
  5801 			}
  5663 		}, this );
  5983 		}, this );
  5664 
  5984 
  5665 		this.listenTo( this.model, 'change', this.render );
  5985 		this.listenTo( this.model, 'change', this.render );
  5666 	},
  5986 	},
  5667 	/**
  5987 	/**
  5668 	 * @returns {wp.media.view.Button} Returns itself to allow chaining
  5988 	 * @return {wp.media.view.Button} Returns itself to allow chaining.
  5669 	 */
  5989 	 */
  5670 	render: function() {
  5990 	render: function() {
  5671 		var classes = [ 'button', this.className ],
  5991 		var classes = [ 'button', this.className ],
  5672 			model = this.model.toJSON();
  5992 			model = this.model.toJSON();
  5673 
  5993 
  5743 			this.$el.addClass( this.options.classes );
  6063 			this.$el.addClass( this.options.classes );
  5744 		}
  6064 		}
  5745 	},
  6065 	},
  5746 
  6066 
  5747 	/**
  6067 	/**
  5748 	 * @returns {wp.media.view.ButtonGroup}
  6068 	 * @return {wp.media.view.ButtonGroup}
  5749 	 */
  6069 	 */
  5750 	render: function() {
  6070 	render: function() {
  5751 		this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
  6071 		this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
  5752 		return this;
  6072 		return this;
  5753 	}
  6073 	}
  5785 	},
  6105 	},
  5786 	/**
  6106 	/**
  5787 	 * @param {string} id
  6107 	 * @param {string} id
  5788 	 * @param {wp.media.View|Object} view
  6108 	 * @param {wp.media.View|Object} view
  5789 	 * @param {Object} options
  6109 	 * @param {Object} options
  5790 	 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
  6110 	 * @return {wp.media.view.PriorityList} Returns itself to allow chaining.
  5791 	 */
  6111 	 */
  5792 	set: function( id, view, options ) {
  6112 	set: function( id, view, options ) {
  5793 		var priority, views, index;
  6113 		var priority, views, index;
  5794 
  6114 
  5795 		options = options || {};
  6115 		options = options || {};
  5826 
  6146 
  5827 		return this;
  6147 		return this;
  5828 	},
  6148 	},
  5829 	/**
  6149 	/**
  5830 	 * @param {string} id
  6150 	 * @param {string} id
  5831 	 * @returns {wp.media.View}
  6151 	 * @return {wp.media.View}
  5832 	 */
  6152 	 */
  5833 	get: function( id ) {
  6153 	get: function( id ) {
  5834 		return this._views[ id ];
  6154 		return this._views[ id ];
  5835 	},
  6155 	},
  5836 	/**
  6156 	/**
  5837 	 * @param {string} id
  6157 	 * @param {string} id
  5838 	 * @returns {wp.media.view.PriorityList}
  6158 	 * @return {wp.media.view.PriorityList}
  5839 	 */
  6159 	 */
  5840 	unset: function( id ) {
  6160 	unset: function( id ) {
  5841 		var view = this.get( id );
  6161 		var view = this.get( id );
  5842 
  6162 
  5843 		if ( view ) {
  6163 		if ( view ) {
  5847 		delete this._views[ id ];
  6167 		delete this._views[ id ];
  5848 		return this;
  6168 		return this;
  5849 	},
  6169 	},
  5850 	/**
  6170 	/**
  5851 	 * @param {Object} options
  6171 	 * @param {Object} options
  5852 	 * @returns {wp.media.View}
  6172 	 * @return {wp.media.View}
  5853 	 */
  6173 	 */
  5854 	toView: function( options ) {
  6174 	toView: function( options ) {
  5855 		return new wp.media.View( options );
  6175 		return new wp.media.View( options );
  5856 	}
  6176 	}
  5857 });
  6177 });
  5861 
  6181 
  5862 /***/ }),
  6182 /***/ }),
  5863 /* 68 */
  6183 /* 68 */
  5864 /***/ (function(module, exports) {
  6184 /***/ (function(module, exports) {
  5865 
  6185 
  5866 var $ = jQuery,
  6186 var MenuItem;
  5867 	MenuItem;
       
  5868 
  6187 
  5869 /**
  6188 /**
  5870  * wp.media.view.MenuItem
  6189  * wp.media.view.MenuItem
  5871  *
  6190  *
  5872  * @memberOf wp.media.view
  6191  * @memberOf wp.media.view
  5875  * @augments wp.media.View
  6194  * @augments wp.media.View
  5876  * @augments wp.Backbone.View
  6195  * @augments wp.Backbone.View
  5877  * @augments Backbone.View
  6196  * @augments Backbone.View
  5878  */
  6197  */
  5879 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
  6198 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
  5880 	tagName:   'a',
  6199 	tagName:   'button',
  5881 	className: 'media-menu-item',
  6200 	className: 'media-menu-item',
  5882 
  6201 
  5883 	attributes: {
  6202 	attributes: {
  5884 		href: '#'
  6203 		type: 'button',
       
  6204 		role: 'tab'
  5885 	},
  6205 	},
  5886 
  6206 
  5887 	events: {
  6207 	events: {
  5888 		'click': '_click'
  6208 		'click': '_click'
  5889 	},
  6209 	},
  5890 	/**
  6210 
  5891 	 * @param {Object} event
  6211 	/**
  5892 	 */
  6212 	 * Allows to override the click event.
  5893 	_click: function( event ) {
  6213 	 */
       
  6214 	_click: function() {
  5894 		var clickOverride = this.options.click;
  6215 		var clickOverride = this.options.click;
  5895 
       
  5896 		if ( event ) {
       
  5897 			event.preventDefault();
       
  5898 		}
       
  5899 
  6216 
  5900 		if ( clickOverride ) {
  6217 		if ( clickOverride ) {
  5901 			clickOverride.call( this );
  6218 			clickOverride.call( this );
  5902 		} else {
  6219 		} else {
  5903 			this.click();
  6220 			this.click();
  5904 		}
  6221 		}
  5905 
       
  5906 		// When selecting a tab along the left side,
       
  5907 		// focus should be transferred into the main panel
       
  5908 		if ( ! wp.media.isTouchDevice ) {
       
  5909 			$('.media-frame-content input').first().focus();
       
  5910 		}
       
  5911 	},
  6222 	},
  5912 
  6223 
  5913 	click: function() {
  6224 	click: function() {
  5914 		var state = this.options.state;
  6225 		var state = this.options.state;
  5915 
  6226 
  5916 		if ( state ) {
  6227 		if ( state ) {
  5917 			this.controller.setState( state );
  6228 			this.controller.setState( state );
  5918 			this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
  6229 			// Toggle the menu visibility in the responsive view.
  5919 		}
  6230 			this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below.
  5920 	},
  6231 		}
  5921 	/**
  6232 	},
  5922 	 * @returns {wp.media.view.MenuItem} returns itself to allow chaining
  6233 
       
  6234 	/**
       
  6235 	 * @return {wp.media.view.MenuItem} returns itself to allow chaining.
  5923 	 */
  6236 	 */
  5924 	render: function() {
  6237 	render: function() {
  5925 		var options = this.options;
  6238 		var options = this.options,
       
  6239 			menuProperty = options.state || options.contentMode;
  5926 
  6240 
  5927 		if ( options.text ) {
  6241 		if ( options.text ) {
  5928 			this.$el.text( options.text );
  6242 			this.$el.text( options.text );
  5929 		} else if ( options.html ) {
  6243 		} else if ( options.html ) {
  5930 			this.$el.html( options.html );
  6244 			this.$el.html( options.html );
  5931 		}
  6245 		}
       
  6246 
       
  6247 		// Set the menu item ID based on the frame state associated to the menu item.
       
  6248 		this.$el.attr( 'id', 'menu-item-' + menuProperty );
  5932 
  6249 
  5933 		return this;
  6250 		return this;
  5934 	}
  6251 	}
  5935 });
  6252 });
  5936 
  6253 
  5961 	className: 'media-menu',
  6278 	className: 'media-menu',
  5962 	property:  'state',
  6279 	property:  'state',
  5963 	ItemView:  MenuItem,
  6280 	ItemView:  MenuItem,
  5964 	region:    'menu',
  6281 	region:    'menu',
  5965 
  6282 
  5966 	/* TODO: alternatively hide on any click anywhere
  6283 	attributes: {
  5967 	events: {
  6284 		role:               'tablist',
  5968 		'click': 'click'
  6285 		'aria-orientation': 'horizontal'
  5969 	},
  6286 	},
  5970 
  6287 
  5971 	click: function() {
  6288 	initialize: function() {
  5972 		this.$el.removeClass( 'visible' );
  6289 		this._views = {};
  5973 	},
  6290 
  5974 	*/
  6291 		this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
       
  6292 		delete this.options.views;
       
  6293 
       
  6294 		if ( ! this.options.silent ) {
       
  6295 			this.render();
       
  6296 		}
       
  6297 
       
  6298 		// Initialize the Focus Manager.
       
  6299 		this.focusManager = new wp.media.view.FocusManager( {
       
  6300 			el:   this.el,
       
  6301 			mode: 'tabsNavigation'
       
  6302 		} );
       
  6303 
       
  6304 		// The menu is always rendered and can be visible or hidden on some frames.
       
  6305 		this.isVisible = true;
       
  6306 	},
  5975 
  6307 
  5976 	/**
  6308 	/**
  5977 	 * @param {Object} options
  6309 	 * @param {Object} options
  5978 	 * @param {string} id
  6310 	 * @param {string} id
  5979 	 * @returns {wp.media.View}
  6311 	 * @return {wp.media.View}
  5980 	 */
  6312 	 */
  5981 	toView: function( options, id ) {
  6313 	toView: function( options, id ) {
  5982 		options = options || {};
  6314 		options = options || {};
  5983 		options[ this.property ] = options[ this.property ] || id;
  6315 		options[ this.property ] = options[ this.property ] || id;
  5984 		return new this.ItemView( options ).render();
  6316 		return new this.ItemView( options ).render();
  5988 		/**
  6320 		/**
  5989 		 * call 'ready' directly on the parent class
  6321 		 * call 'ready' directly on the parent class
  5990 		 */
  6322 		 */
  5991 		PriorityList.prototype.ready.apply( this, arguments );
  6323 		PriorityList.prototype.ready.apply( this, arguments );
  5992 		this.visibility();
  6324 		this.visibility();
       
  6325 
       
  6326 		// Set up aria tabs initial attributes.
       
  6327 		this.focusManager.setupAriaTabs();
  5993 	},
  6328 	},
  5994 
  6329 
  5995 	set: function() {
  6330 	set: function() {
  5996 		/**
  6331 		/**
  5997 		 * call 'set' directly on the parent class
  6332 		 * call 'set' directly on the parent class
  6013 			view = this.controller[ region ].get(),
  6348 			view = this.controller[ region ].get(),
  6014 			views = this.views.get(),
  6349 			views = this.views.get(),
  6015 			hide = ! views || views.length < 2;
  6350 			hide = ! views || views.length < 2;
  6016 
  6351 
  6017 		if ( this === view ) {
  6352 		if ( this === view ) {
       
  6353 			// Flag this menu as hidden or visible.
       
  6354 			this.isVisible = ! hide;
       
  6355 			// Set or remove a CSS class to hide the menu.
  6018 			this.controller.$el.toggleClass( 'hide-' + region, hide );
  6356 			this.controller.$el.toggleClass( 'hide-' + region, hide );
  6019 		}
  6357 		}
  6020 	},
  6358 	},
  6021 	/**
  6359 	/**
  6022 	 * @param {string} id
  6360 	 * @param {string} id
  6028 			return;
  6366 			return;
  6029 		}
  6367 		}
  6030 
  6368 
  6031 		this.deselect();
  6369 		this.deselect();
  6032 		view.$el.addClass('active');
  6370 		view.$el.addClass('active');
       
  6371 
       
  6372 		// Set up again the aria tabs initial attributes after the menu updates.
       
  6373 		this.focusManager.setupAriaTabs();
  6033 	},
  6374 	},
  6034 
  6375 
  6035 	deselect: function() {
  6376 	deselect: function() {
  6036 		this.$el.children().removeClass('active');
  6377 		this.$el.children().removeClass('active');
  6037 	},
  6378 	},
  6114 	className: 'media-router',
  6455 	className: 'media-router',
  6115 	property:  'contentMode',
  6456 	property:  'contentMode',
  6116 	ItemView:  wp.media.view.RouterItem,
  6457 	ItemView:  wp.media.view.RouterItem,
  6117 	region:    'router',
  6458 	region:    'router',
  6118 
  6459 
       
  6460 	attributes: {
       
  6461 		role:               'tablist',
       
  6462 		'aria-orientation': 'horizontal'
       
  6463 	},
       
  6464 
  6119 	initialize: function() {
  6465 	initialize: function() {
  6120 		this.controller.on( 'content:render', this.update, this );
  6466 		this.controller.on( 'content:render', this.update, this );
  6121 		// Call 'initialize' directly on the parent class.
  6467 		// Call 'initialize' directly on the parent class.
  6122 		Menu.prototype.initialize.apply( this, arguments );
  6468 		Menu.prototype.initialize.apply( this, arguments );
  6123 	},
  6469 	},
  6225 			// Update the model's details view.
  6571 			// Update the model's details view.
  6226 			this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
  6572 			this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
  6227 			this.details( this.model, this.controller.state().get('selection') );
  6573 			this.details( this.model, this.controller.state().get('selection') );
  6228 		}
  6574 		}
  6229 
  6575 
  6230 		this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
  6576 		this.listenTo( this.controller.states, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
  6231 	},
  6577 	},
  6232 	/**
  6578 	/**
  6233 	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
  6579 	 * @return {wp.media.view.Attachment} Returns itself to allow chaining.
  6234 	 */
  6580 	 */
  6235 	dispose: function() {
  6581 	dispose: function() {
  6236 		var selection = this.options.selection;
  6582 		var selection = this.options.selection;
  6237 
  6583 
  6238 		// Make sure all settings are saved before removing the view.
  6584 		// Make sure all settings are saved before removing the view.
  6246 		 */
  6592 		 */
  6247 		View.prototype.dispose.apply( this, arguments );
  6593 		View.prototype.dispose.apply( this, arguments );
  6248 		return this;
  6594 		return this;
  6249 	},
  6595 	},
  6250 	/**
  6596 	/**
  6251 	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
  6597 	 * @return {wp.media.view.Attachment} Returns itself to allow chaining.
  6252 	 */
  6598 	 */
  6253 	render: function() {
  6599 	render: function() {
  6254 		var options = _.defaults( this.model.toJSON(), {
  6600 		var options = _.defaults( this.model.toJSON(), {
  6255 				orientation:   'landscape',
  6601 				orientation:   'landscape',
  6256 				uploading:     false,
  6602 				uploading:     false,
  6326 		// Don't do anything inside inputs and on the attachment check and remove buttons.
  6672 		// Don't do anything inside inputs and on the attachment check and remove buttons.
  6327 		if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
  6673 		if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
  6328 			return;
  6674 			return;
  6329 		}
  6675 		}
  6330 
  6676 
  6331 		// Catch arrow events
  6677 		// Catch arrow events.
  6332 		if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
  6678 		if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
  6333 			this.controller.trigger( 'attachment:keydown:arrow', event );
  6679 			this.controller.trigger( 'attachment:keydown:arrow', event );
  6334 			return;
  6680 			return;
  6335 		}
  6681 		}
  6336 
  6682 
  6337 		// Catch enter and space events
  6683 		// Catch enter and space events.
  6338 		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
  6684 		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
  6339 			return;
  6685 			return;
  6340 		}
  6686 		}
  6341 
  6687 
  6342 		event.preventDefault();
  6688 		event.preventDefault();
  6343 
  6689 
  6344 		// In the grid view, bubble up an edit:attachment event to the controller.
  6690 		// In the grid view, bubble up an edit:attachment event to the controller.
  6345 		if ( this.controller.isModeActive( 'grid' ) ) {
  6691 		if ( this.controller.isModeActive( 'grid' ) ) {
  6346 			if ( this.controller.isModeActive( 'edit' ) ) {
  6692 			if ( this.controller.isModeActive( 'edit' ) ) {
  6347 				// Pass the current target to restore focus when closing
  6693 				// Pass the current target to restore focus when closing.
  6348 				this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
  6694 				this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
  6349 				return;
  6695 				return;
  6350 			}
  6696 			}
  6351 
  6697 
  6352 			if ( this.controller.isModeActive( 'select' ) ) {
  6698 			if ( this.controller.isModeActive( 'select' ) ) {
  6414 			selection.add( model );
  6760 			selection.add( model );
  6415 			selection.single( model );
  6761 			selection.single( model );
  6416 			return;
  6762 			return;
  6417 		}
  6763 		}
  6418 
  6764 
  6419 		// Fixes bug that loses focus when selecting a featured image
  6765 		// Fixes bug that loses focus when selecting a featured image.
  6420 		if ( ! method ) {
  6766 		if ( ! method ) {
  6421 			method = 'add';
  6767 			method = 'add';
  6422 		}
  6768 		}
  6423 
  6769 
  6424 		if ( method !== 'add' ) {
  6770 		if ( method !== 'add' ) {
  6425 			method = 'reset';
  6771 			method = 'reset';
  6426 		}
  6772 		}
  6427 
  6773 
  6428 		if ( this.selected() ) {
  6774 		if ( this.selected() ) {
  6429 			// If the model is the single model, remove it.
  6775 			/*
  6430 			// If it is not the same as the single model,
  6776 			 * If the model is the single model, remove it.
  6431 			// it now becomes the single model.
  6777 			 * If it is not the same as the single model,
       
  6778 			 * it now becomes the single model.
       
  6779 			 */
  6432 			selection[ single === model ? 'remove' : 'single' ]( model );
  6780 			selection[ single === model ? 'remove' : 'single' ]( model );
  6433 		} else {
  6781 		} else {
  6434 			// If the model is not selected, run the `method` on the
  6782 			/*
  6435 			// selection. By default, we `reset` the selection, but the
  6783 			 * If the model is not selected, run the `method` on the
  6436 			// `method` can be set to `add` the model to the selection.
  6784 			 * selection. By default, we `reset` the selection, but the
       
  6785 			 * `method` can be set to `add` the model to the selection.
       
  6786 			 */
  6437 			selection[ method ]( model );
  6787 			selection[ method ]( model );
  6438 			selection.single( model );
  6788 			selection.single( model );
  6439 		}
  6789 		}
  6440 	},
  6790 	},
  6441 
  6791 
  6442 	updateSelect: function() {
  6792 	updateSelect: function() {
  6443 		this[ this.selected() ? 'select' : 'deselect' ]();
  6793 		this[ this.selected() ? 'select' : 'deselect' ]();
  6444 	},
  6794 	},
  6445 	/**
  6795 	/**
  6446 	 * @returns {unresolved|Boolean}
  6796 	 * @return {unresolved|boolean}
  6447 	 */
  6797 	 */
  6448 	selected: function() {
  6798 	selected: function() {
  6449 		var selection = this.options.selection;
  6799 		var selection = this.options.selection;
  6450 		if ( selection ) {
  6800 		if ( selection ) {
  6451 			return !! selection.get( this.model.cid );
  6801 			return !! selection.get( this.model.cid );
  6457 	 */
  6807 	 */
  6458 	select: function( model, collection ) {
  6808 	select: function( model, collection ) {
  6459 		var selection = this.options.selection,
  6809 		var selection = this.options.selection,
  6460 			controller = this.controller;
  6810 			controller = this.controller;
  6461 
  6811 
  6462 		// Check if a selection exists and if it's the collection provided.
  6812 		/*
  6463 		// If they're not the same collection, bail; we're in another
  6813 		 * Check if a selection exists and if it's the collection provided.
  6464 		// selection's event loop.
  6814 		 * If they're not the same collection, bail; we're in another
       
  6815 		 * selection's event loop.
       
  6816 		 */
  6465 		if ( ! selection || ( collection && collection !== selection ) ) {
  6817 		if ( ! selection || ( collection && collection !== selection ) ) {
  6466 			return;
  6818 			return;
  6467 		}
  6819 		}
  6468 
  6820 
  6469 		// Bail if the model is already selected.
  6821 		// Bail if the model is already selected.
  6483 	 * @param {Backbone.Collection} collection
  6835 	 * @param {Backbone.Collection} collection
  6484 	 */
  6836 	 */
  6485 	deselect: function( model, collection ) {
  6837 	deselect: function( model, collection ) {
  6486 		var selection = this.options.selection;
  6838 		var selection = this.options.selection;
  6487 
  6839 
  6488 		// Check if a selection exists and if it's the collection provided.
  6840 		/*
  6489 		// If they're not the same collection, bail; we're in another
  6841 		 * Check if a selection exists and if it's the collection provided.
  6490 		// selection's event loop.
  6842 		 * If they're not the same collection, bail; we're in another
       
  6843 		 * selection's event loop.
       
  6844 		 */
  6491 		if ( ! selection || ( collection && collection !== selection ) ) {
  6845 		if ( ! selection || ( collection && collection !== selection ) ) {
  6492 			return;
  6846 			return;
  6493 		}
  6847 		}
  6494 		this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
  6848 		this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
  6495 			.find( '.check' ).attr( 'tabindex', '-1' );
  6849 			.find( '.check' ).attr( 'tabindex', '-1' );
  6509 		details = selection.single();
  6863 		details = selection.single();
  6510 		this.$el.toggleClass( 'details', details === this.model );
  6864 		this.$el.toggleClass( 'details', details === this.model );
  6511 	},
  6865 	},
  6512 	/**
  6866 	/**
  6513 	 * @param {string} size
  6867 	 * @param {string} size
  6514 	 * @returns {Object}
  6868 	 * @return {Object}
  6515 	 */
  6869 	 */
  6516 	imageSize: function( size ) {
  6870 	imageSize: function( size ) {
  6517 		var sizes = this.model.get('sizes'), matched = false;
  6871 		var sizes = this.model.get('sizes'), matched = false;
  6518 
  6872 
  6519 		size = size || 'medium';
  6873 		size = size || 'medium';
  6593 			}, 2000 );
  6947 			}, 2000 );
  6594 		});
  6948 		});
  6595 	},
  6949 	},
  6596 	/**
  6950 	/**
  6597 	 * @param {string} status
  6951 	 * @param {string} status
  6598 	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
  6952 	 * @return {wp.media.view.Attachment} Returns itself to allow chaining.
  6599 	 */
  6953 	 */
  6600 	updateSave: function( status ) {
  6954 	updateSave: function( status ) {
  6601 		var save = this._save = this._save || { status: 'ready' };
  6955 		var save = this._save = this._save || { status: 'ready' };
  6602 
  6956 
  6603 		if ( status && status !== save.status ) {
  6957 		if ( status && status !== save.status ) {
  6637 	},
  6991 	},
  6638 	/**
  6992 	/**
  6639 	 * @param {Object} event
  6993 	 * @param {Object} event
  6640 	 */
  6994 	 */
  6641 	removeFromLibrary: function( event ) {
  6995 	removeFromLibrary: function( event ) {
  6642 		// Catch enter and space events
  6996 		// Catch enter and space events.
  6643 		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
  6997 		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
  6644 			return;
  6998 			return;
  6645 		}
  6999 		}
  6646 
  7000 
  6647 		// Stop propagation so the model isn't selected.
  7001 		// Stop propagation so the model isn't selected.
  6652 
  7006 
  6653 	/**
  7007 	/**
  6654 	 * Add the model if it isn't in the selection, if it is in the selection,
  7008 	 * Add the model if it isn't in the selection, if it is in the selection,
  6655 	 * remove it.
  7009 	 * remove it.
  6656 	 *
  7010 	 *
  6657 	 * @param  {[type]} event [description]
  7011 	 * @param {[type]} event [description]
  6658 	 * @return {[type]}       [description]
  7012 	 * @return {[type]} [description]
  6659 	 */
  7013 	 */
  6660 	checkClickHandler: function ( event ) {
  7014 	checkClickHandler: function ( event ) {
  6661 		var selection = this.options.selection;
  7015 		var selection = this.options.selection;
  6662 		if ( ! selection ) {
  7016 		if ( ! selection ) {
  6663 			return;
  7017 			return;
  6688 	 * @memberOf wp.media.view.Attachment
  7042 	 * @memberOf wp.media.view.Attachment
  6689 	 * @instance
  7043 	 * @instance
  6690 	 *
  7044 	 *
  6691 	 * @param {Backbone.Model} model
  7045 	 * @param {Backbone.Model} model
  6692 	 * @param {string} value
  7046 	 * @param {string} value
  6693 	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
  7047 	 * @return {wp.media.view.Attachment} Returns itself to allow chaining.
  6694 	 */
  7048 	 */
  6695 	/**
  7049 	/**
  6696 	 * @function _syncTitle
  7050 	 * @function _syncTitle
  6697 	 * @memberOf wp.media.view.Attachment
  7051 	 * @memberOf wp.media.view.Attachment
  6698 	 * @instance
  7052 	 * @instance
  6699 	 *
  7053 	 *
  6700 	 * @param {Backbone.Model} model
  7054 	 * @param {Backbone.Model} model
  6701 	 * @param {string} value
  7055 	 * @param {string} value
  6702 	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
  7056 	 * @return {wp.media.view.Attachment} Returns itself to allow chaining.
  6703 	 */
  7057 	 */
  6704 	/**
  7058 	/**
  6705 	 * @function _syncArtist
  7059 	 * @function _syncArtist
  6706 	 * @memberOf wp.media.view.Attachment
  7060 	 * @memberOf wp.media.view.Attachment
  6707 	 * @instance
  7061 	 * @instance
  6708 	 *
  7062 	 *
  6709 	 * @param {Backbone.Model} model
  7063 	 * @param {Backbone.Model} model
  6710 	 * @param {string} value
  7064 	 * @param {string} value
  6711 	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
  7065 	 * @return {wp.media.view.Attachment} Returns itself to allow chaining.
  6712 	 */
  7066 	 */
  6713 	/**
  7067 	/**
  6714 	 * @function _syncAlbum
  7068 	 * @function _syncAlbum
  6715 	 * @memberOf wp.media.view.Attachment
  7069 	 * @memberOf wp.media.view.Attachment
  6716 	 * @instance
  7070 	 * @instance
  6717 	 *
  7071 	 *
  6718 	 * @param {Backbone.Model} model
  7072 	 * @param {Backbone.Model} model
  6719 	 * @param {string} value
  7073 	 * @param {string} value
  6720 	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
  7074 	 * @return {wp.media.view.Attachment} Returns itself to allow chaining.
  6721 	 */
  7075 	 */
  6722 	Attachment.prototype[ method ] = function( model, value ) {
  7076 	Attachment.prototype[ method ] = function( model, value ) {
  6723 		var $setting = this.$('[data-setting="' + setting + '"]');
  7077 		var $setting = this.$('[data-setting="' + setting + '"]');
  6724 
  7078 
  6725 		if ( ! $setting.length ) {
  7079 		if ( ! $setting.length ) {
  6726 			return this;
  7080 			return this;
  6727 		}
  7081 		}
  6728 
  7082 
  6729 		// If the updated value is in sync with the value in the DOM, there
  7083 		/*
  6730 		// is no need to re-render. If we're currently editing the value,
  7084 		 * If the updated value is in sync with the value in the DOM, there
  6731 		// it will automatically be in sync, suppressing the re-render for
  7085 		 * is no need to re-render. If we're currently editing the value,
  6732 		// the view we're editing, while updating any others.
  7086 		 * it will automatically be in sync, suppressing the re-render for
       
  7087 		 * the view we're editing, while updating any others.
       
  7088 		 */
  6733 		if ( value === $setting.find('input, textarea, select, [value]').val() ) {
  7089 		if ( value === $setting.find('input, textarea, select, [value]').val() ) {
  6734 			return this;
  7090 			return this;
  6735 		}
  7091 		}
  6736 
  7092 
  6737 		return this.render();
  7093 		return this.render();
  6875 			}
  7231 			}
  6876 		}, this );
  7232 		}, this );
  6877 
  7233 
  6878 		this.collection.on( 'reset', this.render, this );
  7234 		this.collection.on( 'reset', this.render, this );
  6879 
  7235 
  6880 		this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
  7236 		this.controller.on( 'library:selection:add', this.attachmentFocus, this );
  6881 
  7237 
  6882 		// Throttle the scroll handler and bind this.
  7238 		// Throttle the scroll handler and bind this.
  6883 		this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
  7239 		this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
  6884 
  7240 
  6885 		this.options.scrollElement = this.options.scrollElement || this.el;
  7241 		this.options.scrollElement = this.options.scrollElement || this.el;
  6909 	 *
  7265 	 *
  6910 	 * @since 4.0.0
  7266 	 * @since 4.0.0
  6911 	 *
  7267 	 *
  6912 	 * @listens window:resize
  7268 	 * @listens window:resize
  6913 	 *
  7269 	 *
  6914 	 * @returns {void}
  7270 	 * @return {void}
  6915 	 */
  7271 	 */
  6916 	bindEvents: function() {
  7272 	bindEvents: function() {
  6917 		this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
  7273 		this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
  6918 	},
  7274 	},
  6919 
  7275 
  6920 	/**
  7276 	/**
  6921 	 * Focuses the first item in the collection.
  7277 	 * Focuses the first item in the collection.
  6922 	 *
  7278 	 *
  6923 	 * @since 4.0.0
  7279 	 * @since 4.0.0
  6924 	 *
  7280 	 *
  6925 	 * @returns {void}
  7281 	 * @return {void}
  6926 	 */
  7282 	 */
  6927 	attachmentFocus: function() {
  7283 	attachmentFocus: function() {
  6928 		this.$( 'li:first' ).focus();
  7284 		/*
       
  7285 		 * @todo When uploading new attachments, this tries to move focus to
       
  7286 		 * the attachments grid. Actually, a progress bar gets initially displayed
       
  7287 		 * and then updated when uploading completes, so focus is lost.
       
  7288 		 * Additionally: this view is used for both the attachments list and
       
  7289 		 * the list of selected attachments in the bottom media toolbar. Thus, when
       
  7290 		 * uploading attachments, it is called twice and returns two different `this`.
       
  7291 		 * `this.columns` is truthy within the modal.
       
  7292 		 */
       
  7293 		if ( this.columns ) {
       
  7294 			// Move focus to the grid list within the modal.
       
  7295 			this.$el.focus();
       
  7296 		}
  6929 	},
  7297 	},
  6930 
  7298 
  6931 	/**
  7299 	/**
  6932 	 * Restores focus to the selected item in the collection.
  7300 	 * Restores focus to the selected item in the collection.
  6933 	 *
  7301 	 *
       
  7302 	 * Moves focus back to the first selected attachment in the grid. Used when
       
  7303 	 * tabbing backwards from the attachment details sidebar.
       
  7304 	 * See media.view.AttachmentsBrowser.
       
  7305 	 *
  6934 	 * @since 4.0.0
  7306 	 * @since 4.0.0
  6935 	 *
  7307 	 *
  6936 	 * @returns {void}
  7308 	 * @return {void}
  6937 	 */
  7309 	 */
  6938 	restoreFocus: function() {
  7310 	restoreFocus: function() {
  6939 		this.$( 'li.selected:first' ).focus();
  7311 		this.$( 'li.selected:first' ).focus();
  6940 	},
  7312 	},
  6941 
  7313 
  6946 	 *
  7318 	 *
  6947 	 * @since 4.0.0
  7319 	 * @since 4.0.0
  6948 	 *
  7320 	 *
  6949 	 * @param {KeyboardEvent} event The keyboard event that triggered this function.
  7321 	 * @param {KeyboardEvent} event The keyboard event that triggered this function.
  6950 	 *
  7322 	 *
  6951 	 * @returns {void}
  7323 	 * @return {void}
  6952 	 */
  7324 	 */
  6953 	arrowEvent: function( event ) {
  7325 	arrowEvent: function( event ) {
  6954 		var attachments = this.$el.children( 'li' ),
  7326 		var attachments = this.$el.children( 'li' ),
  6955 			perRow = this.columns,
  7327 			perRow = this.columns,
  6956 			index = attachments.filter( ':focus' ).index(),
  7328 			index = attachments.filter( ':focus' ).index(),
  6996 	/**
  7368 	/**
  6997 	 * Clears any set event handlers.
  7369 	 * Clears any set event handlers.
  6998 	 *
  7370 	 *
  6999 	 * @since 3.5.0
  7371 	 * @since 3.5.0
  7000 	 *
  7372 	 *
  7001 	 * @returns {void}
  7373 	 * @return {void}
  7002 	 */
  7374 	 */
  7003 	dispose: function() {
  7375 	dispose: function() {
  7004 		this.collection.props.off( null, null, this );
  7376 		this.collection.props.off( null, null, this );
  7005 		if ( this.options.resize ) {
  7377 		if ( this.options.resize ) {
  7006 			this.$window.off( this.resizeEvent );
  7378 			this.$window.off( this.resizeEvent );
  7016 	 * Calculates the amount of columns and sets it on the data-columns attribute
  7388 	 * Calculates the amount of columns and sets it on the data-columns attribute
  7017 	 * of .media-frame-content.
  7389 	 * of .media-frame-content.
  7018 	 *
  7390 	 *
  7019 	 * @since 4.0.0
  7391 	 * @since 4.0.0
  7020 	 *
  7392 	 *
  7021 	 * @returns {void}
  7393 	 * @return {void}
  7022 	 */
  7394 	 */
  7023 	setColumns: function() {
  7395 	setColumns: function() {
  7024 		var prev = this.columns,
  7396 		var prev = this.columns,
  7025 			width = this.$el.width();
  7397 			width = this.$el.width();
  7026 
  7398 
  7034 	},
  7406 	},
  7035 
  7407 
  7036 	/**
  7408 	/**
  7037 	 * Initializes jQuery sortable on the attachment list.
  7409 	 * Initializes jQuery sortable on the attachment list.
  7038 	 *
  7410 	 *
  7039 	 * Fails gracefully if jQuery sortable doesn't exist or isn't passed in the
  7411 	 * Fails gracefully if jQuery sortable doesn't exist or isn't passed
  7040 	 * options.
  7412 	 * in the options.
  7041 	 *
  7413 	 *
  7042 	 * @since 3.5.0
  7414 	 * @since 3.5.0
  7043 	 *
  7415 	 *
  7044 	 * @fires collection:reset
  7416 	 * @fires collection:reset
  7045 	 *
  7417 	 *
  7046 	 * @returns {void}
  7418 	 * @return {void}
  7047 	 */
  7419 	 */
  7048 	initSortable: function() {
  7420 	initSortable: function() {
  7049 		var collection = this.collection;
  7421 		var collection = this.collection;
  7050 
  7422 
  7051 		if ( ! this.options.sortable || ! $.fn.sortable ) {
  7423 		if ( ! this.options.sortable || ! $.fn.sortable ) {
  7055 		this.$el.sortable( _.extend({
  7427 		this.$el.sortable( _.extend({
  7056 			// If the `collection` has a `comparator`, disable sorting.
  7428 			// If the `collection` has a `comparator`, disable sorting.
  7057 			disabled: !! collection.comparator,
  7429 			disabled: !! collection.comparator,
  7058 
  7430 
  7059 			/*
  7431 			/*
  7060 			 * Change the position of the attachment as soon as the mouse pointer overlaps a
  7432 			 * Change the position of the attachment as soon as the mouse pointer
  7061 			 * thumbnail.
  7433 			 * overlaps a thumbnail.
  7062 			 */
  7434 			 */
  7063 			tolerance: 'pointer',
  7435 			tolerance: 'pointer',
  7064 
  7436 
  7065 			// Record the initial `index` of the dragged model.
  7437 			// Record the initial `index` of the dragged model.
  7066 			start: function( event, ui ) {
  7438 			start: function( event, ui ) {
  7098 				collection.saveMenuOrder();
  7470 				collection.saveMenuOrder();
  7099 			}
  7471 			}
  7100 		}, this.options.sortable ) );
  7472 		}, this.options.sortable ) );
  7101 
  7473 
  7102 		/*
  7474 		/*
  7103 		 * If the `orderby` property is changed on the `collection`, check to see if we
  7475 		 * If the `orderby` property is changed on the `collection`,
  7104 		 * have a `comparator`. If so, disable sorting.
  7476 		 * check to see if we have a `comparator`. If so, disable sorting.
  7105 		 */
  7477 		 */
  7106 		collection.props.on( 'change:orderby', function() {
  7478 		collection.props.on( 'change:orderby', function() {
  7107 			this.$el.sortable( 'option', 'disabled', !! collection.comparator );
  7479 			this.$el.sortable( 'option', 'disabled', !! collection.comparator );
  7108 		}, this );
  7480 		}, this );
  7109 
  7481 
  7115 	 * Disables jQuery sortable if collection has a comparator or collection.orderby
  7487 	 * Disables jQuery sortable if collection has a comparator or collection.orderby
  7116 	 * equals menuOrder.
  7488 	 * equals menuOrder.
  7117 	 *
  7489 	 *
  7118 	 * @since 3.5.0
  7490 	 * @since 3.5.0
  7119 	 *
  7491 	 *
  7120 	 * @returns {void}
  7492 	 * @return {void}
  7121 	 */
  7493 	 */
  7122 	refreshSortable: function() {
  7494 	refreshSortable: function() {
  7123 		if ( ! this.options.sortable || ! $.fn.sortable ) {
  7495 		if ( ! this.options.sortable || ! $.fn.sortable ) {
  7124 			return;
  7496 			return;
  7125 		}
  7497 		}
  7136 	 *
  7508 	 *
  7137 	 * @since 3.5.0
  7509 	 * @since 3.5.0
  7138 	 *
  7510 	 *
  7139 	 * @param {wp.media.model.Attachment} attachment
  7511 	 * @param {wp.media.model.Attachment} attachment
  7140 	 *
  7512 	 *
  7141 	 * @returns {wp.media.View} The created view.
  7513 	 * @return {wp.media.View} The created view.
  7142 	 */
  7514 	 */
  7143 	createAttachmentView: function( attachment ) {
  7515 	createAttachmentView: function( attachment ) {
  7144 		var view = new this.options.AttachmentView({
  7516 		var view = new this.options.AttachmentView({
  7145 			controller:           this.controller,
  7517 			controller:           this.controller,
  7146 			model:                attachment,
  7518 			model:                attachment,
  7157 	 * Creates views for every attachment in collection if the collection is not
  7529 	 * Creates views for every attachment in collection if the collection is not
  7158 	 * empty, otherwise clears all views and loads more attachments.
  7530 	 * empty, otherwise clears all views and loads more attachments.
  7159 	 *
  7531 	 *
  7160 	 * @since 3.5.0
  7532 	 * @since 3.5.0
  7161 	 *
  7533 	 *
  7162 	 * @returns {void}
  7534 	 * @return {void}
  7163 	 */
  7535 	 */
  7164 	prepare: function() {
  7536 	prepare: function() {
  7165 		if ( this.collection.length ) {
  7537 		if ( this.collection.length ) {
  7166 			this.views.set( this.collection.map( this.createAttachmentView, this ) );
  7538 			this.views.set( this.collection.map( this.createAttachmentView, this ) );
  7167 		} else {
  7539 		} else {
  7174 	 * Triggers the scroll function to check if we should query for additional
  7546 	 * Triggers the scroll function to check if we should query for additional
  7175 	 * attachments right away.
  7547 	 * attachments right away.
  7176 	 *
  7548 	 *
  7177 	 * @since 3.5.0
  7549 	 * @since 3.5.0
  7178 	 *
  7550 	 *
  7179 	 * @returns {void}
  7551 	 * @return {void}
  7180 	 */
  7552 	 */
  7181 	ready: function() {
  7553 	ready: function() {
  7182 		this.scroll();
  7554 		this.scroll();
  7183 	},
  7555 	},
  7184 
  7556 
  7188 	 * Shows the spinner if we're close to the bottom. Loads more attachments from
  7560 	 * Shows the spinner if we're close to the bottom. Loads more attachments from
  7189 	 * server if we're {refreshThreshold} times away from the bottom.
  7561 	 * server if we're {refreshThreshold} times away from the bottom.
  7190 	 *
  7562 	 *
  7191 	 * @since 3.5.0
  7563 	 * @since 3.5.0
  7192 	 *
  7564 	 *
  7193 	 * @returns {void}
  7565 	 * @return {void}
  7194 	 */
  7566 	 */
  7195 	scroll: function() {
  7567 	scroll: function() {
  7196 		var view = this,
  7568 		var view = this,
  7197 			el = this.options.scrollElement,
  7569 			el = this.options.scrollElement,
  7198 			scrollTop = el.scrollTop,
  7570 			scrollTop = el.scrollTop,
  7232 
  7604 
  7233 /***/ }),
  7605 /***/ }),
  7234 /* 77 */
  7606 /* 77 */
  7235 /***/ (function(module, exports) {
  7607 /***/ (function(module, exports) {
  7236 
  7608 
  7237 var l10n = wp.media.view.l10n,
  7609 var Search;
  7238 	Search;
       
  7239 
  7610 
  7240 /**
  7611 /**
  7241  * wp.media.view.Search
  7612  * wp.media.view.Search
  7242  *
  7613  *
  7243  * @memberOf wp.media.view
  7614  * @memberOf wp.media.view
  7251 	tagName:   'input',
  7622 	tagName:   'input',
  7252 	className: 'search',
  7623 	className: 'search',
  7253 	id:        'media-search-input',
  7624 	id:        'media-search-input',
  7254 
  7625 
  7255 	attributes: {
  7626 	attributes: {
  7256 		type:        'search',
  7627 		type: 'search'
  7257 		placeholder: l10n.searchMediaPlaceholder
       
  7258 	},
  7628 	},
  7259 
  7629 
  7260 	events: {
  7630 	events: {
  7261 		'input':  'search',
  7631 		'input': 'search'
  7262 		'keyup':  'search'
  7632 	},
  7263 	},
  7633 
  7264 
  7634 	/**
  7265 	/**
  7635 	 * @return {wp.media.view.Search} Returns itself to allow chaining.
  7266 	 * @returns {wp.media.view.Search} Returns itself to allow chaining
       
  7267 	 */
  7636 	 */
  7268 	render: function() {
  7637 	render: function() {
  7269 		this.el.value = this.model.escape('search');
  7638 		this.el.value = this.model.escape('search');
  7270 		return this;
  7639 		return this;
  7271 	},
  7640 	},
  7272 
  7641 
  7273 	search: _.debounce( function( event ) {
  7642 	search: _.debounce( function( event ) {
  7274 		if ( event.target.value ) {
  7643 		var searchTerm = event.target.value.trim();
  7275 			this.model.set( 'search', event.target.value );
  7644 
       
  7645 		// Trigger the search only after 2 ASCII characters.
       
  7646 		if ( searchTerm && searchTerm.length > 1 ) {
       
  7647 			this.model.set( 'search', searchTerm );
  7276 		} else {
  7648 		} else {
  7277 			this.model.unset('search');
  7649 			this.model.unset( 'search' );
  7278 		}
  7650 		}
  7279 	}, 300 )
  7651 	}, 500 )
  7280 });
  7652 });
  7281 
  7653 
  7282 module.exports = Search;
  7654 module.exports = Search;
  7283 
  7655 
  7284 
  7656 
  7663 		if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
  8035 		if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
  7664 			this.createSidebar();
  8036 			this.createSidebar();
  7665 		}
  8037 		}
  7666 
  8038 
  7667 		/*
  8039 		/*
  7668 		 * For accessibility reasons, place the Inline Uploader before other sections.
  8040 		 * In the grid mode (the Media Library), place the Inline Uploader before
  7669 		 * This way, in the Media Library, it's right after the Add New button, see ticket #37188.
  8041 		 * other sections so that the visual order and the DOM order match. This way,
       
  8042 		 * the Inline Uploader in the Media Library is right after the "Add New"
       
  8043 		 * button, see ticket #37188.
  7670 		 */
  8044 		 */
  7671 		this.createUploader();
  8045 		if ( this.controller.isModeActive( 'grid' ) ) {
  7672 
  8046 			this.createUploader();
  7673 		/*
  8047 
  7674 		 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
  8048 			/*
  7675 		 * and also for other things, for example the "Drag and drop to reorder" and
  8049 			 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
  7676 		 * "Suggested dimensions" info in the media modal.
  8050 			 * and also for other things, for example the "Drag and drop to reorder" and
  7677 		 */
  8051 			 * "Suggested dimensions" info in the media modal.
  7678 		this.createToolbar();
  8052 			 */
       
  8053 			this.createToolbar();
       
  8054 		} else {
       
  8055 			this.createToolbar();
       
  8056 			this.createUploader();
       
  8057 		}
       
  8058 
  7679 
  8059 
  7680 		// Add a heading before the attachments list.
  8060 		// Add a heading before the attachments list.
  7681 		this.createAttachmentsHeading();
  8061 		this.createAttachmentsHeading();
  7682 
  8062 
  7683 		// Create the list of attachments.
  8063 		// Create the list of attachments.
  7697 				this.$el.addClass( 'sidebar-for-errors' );
  8077 				this.$el.addClass( 'sidebar-for-errors' );
  7698 			}
  8078 			}
  7699 		}
  8079 		}
  7700 
  8080 
  7701 		this.collection.on( 'add remove reset', this.updateContent, this );
  8081 		this.collection.on( 'add remove reset', this.updateContent, this );
  7702 	},
  8082 
       
  8083 		// The non-cached or cached attachments query has completed.
       
  8084 		this.collection.on( 'attachments:received', this.announceSearchResults, this );
       
  8085 	},
       
  8086 
       
  8087 	/**
       
  8088 	 * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate
       
  8089 	 * the number of search results to screen reader users. This function is
       
  8090 	 * debounced because the collection updates multiple times.
       
  8091 	 *
       
  8092 	 * @since 5.3.0
       
  8093 	 *
       
  8094 	 * @return {void}
       
  8095 	 */
       
  8096 	announceSearchResults: _.debounce( function() {
       
  8097 		var count;
       
  8098 
       
  8099 		if ( this.collection.mirroring.args.s ) {
       
  8100 			count = this.collection.length;
       
  8101 
       
  8102 			if ( 0 === count ) {
       
  8103 				wp.a11y.speak( l10n.noMediaTryNewSearch );
       
  8104 				return;
       
  8105 			}
       
  8106 
       
  8107 			if ( this.collection.hasMore() ) {
       
  8108 				wp.a11y.speak( l10n.mediaFoundHasMoreResults.replace( '%d', count ) );
       
  8109 				return;
       
  8110 			}
       
  8111 
       
  8112 			wp.a11y.speak( l10n.mediaFound.replace( '%d', count ) );
       
  8113 		}
       
  8114 	}, 200 ),
  7703 
  8115 
  7704 	editSelection: function( modal ) {
  8116 	editSelection: function( modal ) {
       
  8117 		// When editing a selection, move focus to the "Return to library" button.
  7705 		modal.$( '.media-button-backToLibrary' ).focus();
  8118 		modal.$( '.media-button-backToLibrary' ).focus();
  7706 	},
  8119 	},
  7707 
  8120 
  7708 	/**
  8121 	/**
  7709 	 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
  8122 	 * @return {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining.
  7710 	 */
  8123 	 */
  7711 	dispose: function() {
  8124 	dispose: function() {
  7712 		this.options.selection.off( null, null, this );
  8125 		this.options.selection.off( null, null, this );
  7713 		View.prototype.dispose.apply( this, arguments );
  8126 		View.prototype.dispose.apply( this, arguments );
  7714 		return this;
  8127 		return this;
  7715 	},
  8128 	},
  7716 
  8129 
  7717 	createToolbar: function() {
  8130 	createToolbar: function() {
  7718 		var LibraryViewSwitcher, Filters, toolbarOptions;
  8131 		var LibraryViewSwitcher, Filters, toolbarOptions,
       
  8132 			showFilterByType = -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] );
  7719 
  8133 
  7720 		toolbarOptions = {
  8134 		toolbarOptions = {
  7721 			controller: this.controller
  8135 			controller: this.controller
  7722 		};
  8136 		};
  7723 
  8137 
  7731 		this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
  8145 		this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
  7732 
  8146 
  7733 		this.views.add( this.toolbar );
  8147 		this.views.add( this.toolbar );
  7734 
  8148 
  7735 		this.toolbar.set( 'spinner', new wp.media.view.Spinner({
  8149 		this.toolbar.set( 'spinner', new wp.media.view.Spinner({
  7736 			priority: -60
  8150 			priority: -20
  7737 		}) );
  8151 		}) );
  7738 
  8152 
  7739 		if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
  8153 		if ( showFilterByType || this.options.date ) {
  7740 			// "Filters" will return a <select>, need to render
  8154 			/*
  7741 			// screen reader text before
  8155 			 * Create a h2 heading before the select elements that filter attachments.
       
  8156 			 * This heading is visible in the modal and visually hidden in the grid.
       
  8157 			 */
       
  8158 			this.toolbar.set( 'filters-heading', new wp.media.view.Heading( {
       
  8159 				priority:   -100,
       
  8160 				text:       l10n.filterAttachments,
       
  8161 				level:      'h2',
       
  8162 				className:  'media-attachments-filter-heading'
       
  8163 			}).render() );
       
  8164 		}
       
  8165 
       
  8166 		if ( showFilterByType ) {
       
  8167 			// "Filters" is a <select>, a visually hidden label element needs to be rendered before.
  7742 			this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
  8168 			this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
  7743 				value: l10n.filterByType,
  8169 				value: l10n.filterByType,
  7744 				attributes: {
  8170 				attributes: {
  7745 					'for':  'media-attachment-filters'
  8171 					'for':  'media-attachment-filters'
  7746 				},
  8172 				},
  7762 
  8188 
  7763 				this.toolbar.set( 'filters', Filters.render() );
  8189 				this.toolbar.set( 'filters', Filters.render() );
  7764 			}
  8190 			}
  7765 		}
  8191 		}
  7766 
  8192 
  7767 		// Feels odd to bring the global media library switcher into the Attachment
  8193 		/*
  7768 		// browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
  8194 		 * Feels odd to bring the global media library switcher into the Attachment browser view.
  7769 		// which the controller can tap into and add this view?
  8195 		 * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
       
  8196 		 * which the controller can tap into and add this view?
       
  8197 		 */
  7770 		if ( this.controller.isModeActive( 'grid' ) ) {
  8198 		if ( this.controller.isModeActive( 'grid' ) ) {
  7771 			LibraryViewSwitcher = View.extend({
  8199 			LibraryViewSwitcher = View.extend({
  7772 				className: 'view-switch media-grid-view-switch',
  8200 				className: 'view-switch media-grid-view-switch',
  7773 				template: wp.template( 'media-library-view-switcher')
  8201 				template: wp.template( 'media-library-view-switcher')
  7774 			});
  8202 			});
  7776 			this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
  8204 			this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
  7777 				controller: this.controller,
  8205 				controller: this.controller,
  7778 				priority: -90
  8206 				priority: -90
  7779 			}).render() );
  8207 			}).render() );
  7780 
  8208 
  7781 			// DateFilter is a <select>, screen reader text needs to be rendered before
  8209 			// DateFilter is a <select>, a visually hidden label element needs to be rendered before.
  7782 			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
  8210 			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
  7783 				value: l10n.filterByDate,
  8211 				value: l10n.filterByDate,
  7784 				attributes: {
  8212 				attributes: {
  7785 					'for': 'media-attachment-date-filters'
  8213 					'for': 'media-attachment-date-filters'
  7786 				},
  8214 				},
  7790 				controller: this.controller,
  8218 				controller: this.controller,
  7791 				model:      this.collection.props,
  8219 				model:      this.collection.props,
  7792 				priority: -75
  8220 				priority: -75
  7793 			}).render() );
  8221 			}).render() );
  7794 
  8222 
  7795 			// BulkSelection is a <div> with subviews, including screen reader text
  8223 			// BulkSelection is a <div> with subviews, including screen reader text.
  7796 			this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
  8224 			this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
  7797 				text: l10n.bulkSelect,
  8225 				text: l10n.bulkSelect,
  7798 				controller: this.controller,
  8226 				controller: this.controller,
  7799 				priority: -70
  8227 				priority: -70
  7800 			}).render() );
  8228 			}).render() );
  7898 					}
  8326 					}
  7899 				}).render() );
  8327 				}).render() );
  7900 			}
  8328 			}
  7901 
  8329 
  7902 		} else if ( this.options.date ) {
  8330 		} else if ( this.options.date ) {
  7903 			// DateFilter is a <select>, screen reader text needs to be rendered before
  8331 			// DateFilter is a <select>, a visually hidden label element needs to be rendered before.
  7904 			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
  8332 			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
  7905 				value: l10n.filterByDate,
  8333 				value: l10n.filterByDate,
  7906 				attributes: {
  8334 				attributes: {
  7907 					'for': 'media-attachment-date-filters'
  8335 					'for': 'media-attachment-date-filters'
  7908 				},
  8336 				},
  7914 				priority: -75
  8342 				priority: -75
  7915 			}).render() );
  8343 			}).render() );
  7916 		}
  8344 		}
  7917 
  8345 
  7918 		if ( this.options.search ) {
  8346 		if ( this.options.search ) {
  7919 			// Search is an input, screen reader text needs to be rendered before
  8347 			// Search is an input, a visually hidden label element needs to be rendered before.
  7920 			this.toolbar.set( 'searchLabel', new wp.media.view.Label({
  8348 			this.toolbar.set( 'searchLabel', new wp.media.view.Label({
  7921 				value: l10n.searchMediaLabel,
  8349 				value: l10n.searchLabel,
       
  8350 				className: 'media-search-input-label',
  7922 				attributes: {
  8351 				attributes: {
  7923 					'for': 'media-search-input'
  8352 					'for': 'media-search-input'
  7924 				},
  8353 				},
  7925 				priority:   60
  8354 				priority:   60
  7926 			}).render() );
  8355 			}).render() );
  8004 
  8433 
  8005 			// The single `Attachment` view to be used in the `Attachments` view.
  8434 			// The single `Attachment` view to be used in the `Attachments` view.
  8006 			AttachmentView: this.options.AttachmentView
  8435 			AttachmentView: this.options.AttachmentView
  8007 		});
  8436 		});
  8008 
  8437 
  8009 		// Add keydown listener to the instance of the Attachments view
  8438 		// Add keydown listener to the instance of the Attachments view.
  8010 		this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
  8439 		this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
  8011 		this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
  8440 		this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
  8012 
  8441 
  8013 		this.views.add( this.attachments );
  8442 		this.views.add( this.attachments );
  8014 
  8443 
  8083 				priority:     160,
  8512 				priority:     160,
  8084 				userSettings: this.model.get('displayUserSettings')
  8513 				userSettings: this.model.get('displayUserSettings')
  8085 			}) );
  8514 			}) );
  8086 		}
  8515 		}
  8087 
  8516 
  8088 		// Show the sidebar on mobile
  8517 		// Show the sidebar on mobile.
  8089 		if ( this.model.id === 'insert' ) {
  8518 		if ( this.model.id === 'insert' ) {
  8090 			sidebar.$el.addClass( 'visible' );
  8519 			sidebar.$el.addClass( 'visible' );
  8091 		}
  8520 		}
  8092 	},
  8521 	},
  8093 
  8522 
  8094 	disposeSingle: function() {
  8523 	disposeSingle: function() {
  8095 		var sidebar = this.sidebar;
  8524 		var sidebar = this.sidebar;
  8096 		sidebar.unset('details');
  8525 		sidebar.unset('details');
  8097 		sidebar.unset('compat');
  8526 		sidebar.unset('compat');
  8098 		sidebar.unset('display');
  8527 		sidebar.unset('display');
  8099 		// Hide the sidebar on mobile
  8528 		// Hide the sidebar on mobile.
  8100 		sidebar.$el.removeClass( 'visible' );
  8529 		sidebar.$el.removeClass( 'visible' );
  8101 	}
  8530 	}
  8102 });
  8531 });
  8103 
  8532 
  8104 module.exports = AttachmentsBrowser;
  8533 module.exports = AttachmentsBrowser;
  8106 
  8535 
  8107 /***/ }),
  8536 /***/ }),
  8108 /* 83 */
  8537 /* 83 */
  8109 /***/ (function(module, exports) {
  8538 /***/ (function(module, exports) {
  8110 
  8539 
  8111 var l10n = wp.media.view.l10n,
  8540 var _n = wp.i18n._n,
       
  8541 	sprintf = wp.i18n.sprintf,
  8112 	Selection;
  8542 	Selection;
  8113 
  8543 
  8114 /**
  8544 /**
  8115  * wp.media.view.Selection
  8545  * wp.media.view.Selection
  8116  *
  8546  *
  8168 		// If nothing is selected, display nothing.
  8598 		// If nothing is selected, display nothing.
  8169 		this.$el.toggleClass( 'empty', ! collection.length );
  8599 		this.$el.toggleClass( 'empty', ! collection.length );
  8170 		this.$el.toggleClass( 'one', 1 === collection.length );
  8600 		this.$el.toggleClass( 'one', 1 === collection.length );
  8171 		this.$el.toggleClass( 'editing', editing );
  8601 		this.$el.toggleClass( 'editing', editing );
  8172 
  8602 
  8173 		this.$('.count').text( l10n.selected.replace('%d', collection.length) );
  8603 		this.$( '.count' ).text(
       
  8604 			/* translators: %s: Number of selected media attachments. */
       
  8605 			sprintf( _n( '%s item selected', '%s items selected', collection.length ), collection.length )
       
  8606 		);
  8174 	},
  8607 	},
  8175 
  8608 
  8176 	edit: function( event ) {
  8609 	edit: function( event ) {
  8177 		event.preventDefault();
  8610 		event.preventDefault();
  8178 		if ( this.options.editable ) {
  8611 		if ( this.options.editable ) {
  8182 
  8615 
  8183 	clear: function( event ) {
  8616 	clear: function( event ) {
  8184 		event.preventDefault();
  8617 		event.preventDefault();
  8185 		this.collection.reset();
  8618 		this.collection.reset();
  8186 
  8619 
  8187 		// Keep focus inside media modal
  8620 		// Move focus to the modal.
  8188 		// after clear link is selected
       
  8189 		this.controller.modal.focusManager.focus();
  8621 		this.controller.modal.focusManager.focus();
  8190 	}
  8622 	}
  8191 });
  8623 });
  8192 
  8624 
  8193 module.exports = Selection;
  8625 module.exports = Selection;
  8317 		return _.defaults({
  8749 		return _.defaults({
  8318 			model: this.model.toJSON()
  8750 			model: this.model.toJSON()
  8319 		}, this.options );
  8751 		}, this.options );
  8320 	},
  8752 	},
  8321 	/**
  8753 	/**
  8322 	 * @returns {wp.media.view.Settings} Returns itself to allow chaining
  8754 	 * @return {wp.media.view.Settings} Returns itself to allow chaining.
  8323 	 */
  8755 	 */
  8324 	render: function() {
  8756 	render: function() {
  8325 		View.prototype.render.apply( this, arguments );
  8757 		View.prototype.render.apply( this, arguments );
  8326 		// Select the correct values.
  8758 		// Select the correct values.
  8327 		_( this.model.attributes ).chain().keys().each( this.update, this );
  8759 		_( this.model.attributes ).chain().keys().each( this.update, this );
  8355 				this.model.set( key, $setting.find(':selected').val() );
  8787 				this.model.set( key, $setting.find(':selected').val() );
  8356 			}
  8788 			}
  8357 
  8789 
  8358 		// Handle button groups.
  8790 		// Handle button groups.
  8359 		} else if ( $setting.hasClass('button-group') ) {
  8791 		} else if ( $setting.hasClass('button-group') ) {
  8360 			$buttons = $setting.find('button').removeClass('active');
  8792 			$buttons = $setting.find( 'button' )
  8361 			$buttons.filter( '[value="' + value + '"]' ).addClass('active');
  8793 				.removeClass( 'active' )
       
  8794 				.attr( 'aria-pressed', 'false' );
       
  8795 			$buttons.filter( '[value="' + value + '"]' )
       
  8796 				.addClass( 'active' )
       
  8797 				.attr( 'aria-pressed', 'true' );
  8362 
  8798 
  8363 		// Handle text inputs and textareas.
  8799 		// Handle text inputs and textareas.
  8364 		} else if ( $setting.is('input[type="text"], textarea') ) {
  8800 		} else if ( $setting.is('input[type="text"], textarea') ) {
  8365 			if ( ! $setting.is(':focus') ) {
  8801 			if ( ! $setting.is(':focus') ) {
  8366 				$setting.val( value );
  8802 				$setting.val( value );
  8392 		// Update the corresponding setting.
  8828 		// Update the corresponding setting.
  8393 		this.model.set( $setting.data('setting'), value );
  8829 		this.model.set( $setting.data('setting'), value );
  8394 
  8830 
  8395 		// If the setting has a corresponding user setting,
  8831 		// If the setting has a corresponding user setting,
  8396 		// update that as well.
  8832 		// update that as well.
  8397 		if ( userSetting = $setting.data('userSetting') ) {
  8833 		userSetting = $setting.data('userSetting');
       
  8834 		if ( userSetting ) {
  8398 			window.setUserSetting( userSetting, value );
  8835 			window.setUserSetting( userSetting, value );
  8399 		}
  8836 		}
  8400 	},
  8837 	},
  8401 
  8838 
  8402 	updateChanges: function( model ) {
  8839 	updateChanges: function( model ) {
  8455 		 * call 'dispose' directly on the parent class
  8892 		 * call 'dispose' directly on the parent class
  8456 		 */
  8893 		 */
  8457 		Settings.prototype.dispose.apply( this, arguments );
  8894 		Settings.prototype.dispose.apply( this, arguments );
  8458 	},
  8895 	},
  8459 	/**
  8896 	/**
  8460 	 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
  8897 	 * @return {wp.media.view.AttachmentDisplay} Returns itself to allow chaining.
  8461 	 */
  8898 	 */
  8462 	render: function() {
  8899 	render: function() {
  8463 		var attachment = this.options.attachment;
  8900 		var attachment = this.options.attachment;
  8464 		if ( attachment ) {
  8901 		if ( attachment ) {
  8465 			_.extend( this.options, {
  8902 			_.extend( this.options, {
  8479 		var linkTo = this.model.get('link'),
  8916 		var linkTo = this.model.get('link'),
  8480 			$input = this.$('.link-to-custom'),
  8917 			$input = this.$('.link-to-custom'),
  8481 			attachment = this.options.attachment;
  8918 			attachment = this.options.attachment;
  8482 
  8919 
  8483 		if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
  8920 		if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
  8484 			$input.addClass( 'hidden' );
  8921 			$input.closest( '.setting' ).addClass( 'hidden' );
  8485 			return;
  8922 			return;
  8486 		}
  8923 		}
  8487 
  8924 
  8488 		if ( attachment ) {
  8925 		if ( attachment ) {
  8489 			if ( 'post' === linkTo ) {
  8926 			if ( 'post' === linkTo ) {
  8495 			}
  8932 			}
  8496 
  8933 
  8497 			$input.prop( 'readonly', 'custom' !== linkTo );
  8934 			$input.prop( 'readonly', 'custom' !== linkTo );
  8498 		}
  8935 		}
  8499 
  8936 
  8500 		$input.removeClass( 'hidden' );
  8937 		$input.closest( '.setting' ).removeClass( 'hidden' );
  8501 
  8938 		if ( $input.length ) {
  8502 		// If the input is visible, focus and select its contents.
  8939 			$input[0].scrollIntoView();
  8503 		if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
       
  8504 			$input.focus()[0].select();
       
  8505 		}
  8940 		}
  8506 	}
  8941 	}
  8507 });
  8942 });
  8508 
  8943 
  8509 module.exports = AttachmentDisplay;
  8944 module.exports = AttachmentDisplay;
  8557 
  8992 
  8558 /***/ }),
  8993 /***/ }),
  8559 /* 91 */
  8994 /* 91 */
  8560 /***/ (function(module, exports) {
  8995 /***/ (function(module, exports) {
  8561 
  8996 
       
  8997 /* global ClipboardJS */
  8562 var Attachment = wp.media.view.Attachment,
  8998 var Attachment = wp.media.view.Attachment,
  8563 	l10n = wp.media.view.l10n,
  8999 	l10n = wp.media.view.l10n,
  8564 	Details;
  9000 	$ = jQuery,
  8565 
  9001 	Details,
  8566 /**
  9002 	__ = wp.i18n.__;
  8567  * wp.media.view.Attachment.Details
  9003 
  8568  *
       
  8569  * @memberOf wp.media.view.Attachment
       
  8570  *
       
  8571  * @class
       
  8572  * @augments wp.media.view.Attachment
       
  8573  * @augments wp.media.View
       
  8574  * @augments wp.Backbone.View
       
  8575  * @augments Backbone.View
       
  8576  */
       
  8577 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
  9004 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
  8578 	tagName:   'div',
  9005 	tagName:   'div',
  8579 	className: 'attachment-details',
  9006 	className: 'attachment-details',
  8580 	template:  wp.template('attachment-details'),
  9007 	template:  wp.template('attachment-details'),
  8581 
  9008 
  8595 		'click .untrash-attachment':      'untrashAttachment',
  9022 		'click .untrash-attachment':      'untrashAttachment',
  8596 		'click .edit-attachment':         'editAttachment',
  9023 		'click .edit-attachment':         'editAttachment',
  8597 		'keydown':                        'toggleSelectionHandler'
  9024 		'keydown':                        'toggleSelectionHandler'
  8598 	},
  9025 	},
  8599 
  9026 
       
  9027 	/**
       
  9028 	 * Copies the attachment URL to the clipboard.
       
  9029 	 *
       
  9030 	 * @since 5.5.0
       
  9031 	 *
       
  9032 	 * @param {MouseEvent} event A click event.
       
  9033 	 *
       
  9034 	 * @return {void}
       
  9035 	 */
       
  9036 	 copyAttachmentDetailsURLClipboard: function() {
       
  9037 		var clipboard = new ClipboardJS( '.copy-attachment-url' ),
       
  9038 			successTimeout;
       
  9039 
       
  9040 		clipboard.on( 'success', function( event ) {
       
  9041 			var triggerElement = $( event.trigger ),
       
  9042 				successElement = $( '.success', triggerElement.closest( '.copy-to-clipboard-container' ) );
       
  9043 
       
  9044 			// Clear the selection and move focus back to the trigger.
       
  9045 			event.clearSelection();
       
  9046 			// Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680
       
  9047 			triggerElement.focus();
       
  9048 
       
  9049 			// Show success visual feedback.
       
  9050 			clearTimeout( successTimeout );
       
  9051 			successElement.removeClass( 'hidden' );
       
  9052 
       
  9053 			// Hide success visual feedback after 3 seconds since last success.
       
  9054 			successTimeout = setTimeout( function() {
       
  9055 				successElement.addClass( 'hidden' );
       
  9056 			}, 3000 );
       
  9057 
       
  9058 			// Handle success audible feedback.
       
  9059 			wp.a11y.speak( __( 'The file URL has been copied to your clipboard' ) );
       
  9060 		} );
       
  9061 	 },
       
  9062 
       
  9063 	/**
       
  9064 	 * Shows the details of an attachment.
       
  9065 	 *
       
  9066 	 * @since 3.5.0
       
  9067 	 *
       
  9068 	 * @constructs wp.media.view.Attachment.Details
       
  9069 	 * @augments wp.media.view.Attachment
       
  9070 	 *
       
  9071 	 * @return {void}
       
  9072 	 */
  8600 	initialize: function() {
  9073 	initialize: function() {
  8601 		this.options = _.defaults( this.options, {
  9074 		this.options = _.defaults( this.options, {
  8602 			rerenderOnModelChange: false
  9075 			rerenderOnModelChange: false
  8603 		});
  9076 		});
  8604 
  9077 
  8605 		// Call 'initialize' directly on the parent class.
  9078 		// Call 'initialize' directly on the parent class.
  8606 		Attachment.prototype.initialize.apply( this, arguments );
  9079 		Attachment.prototype.initialize.apply( this, arguments );
  8607 	},
  9080 
  8608 
  9081 		this.copyAttachmentDetailsURLClipboard();
  8609 	/**
  9082 	},
  8610 	 * @param {Object} event
  9083 
       
  9084 	/**
       
  9085 	 * Gets the focusable elements to move focus to.
       
  9086 	 *
       
  9087 	 * @since 5.3.0
       
  9088 	 */
       
  9089 	getFocusableElements: function() {
       
  9090 		var editedAttachment = $( 'li[data-id="' + this.model.id + '"]' );
       
  9091 
       
  9092 		this.previousAttachment = editedAttachment.prev();
       
  9093 		this.nextAttachment = editedAttachment.next();
       
  9094 	},
       
  9095 
       
  9096 	/**
       
  9097 	 * Moves focus to the previous or next attachment in the grid.
       
  9098 	 * Fallbacks to the upload button or media frame when there are no attachments.
       
  9099 	 *
       
  9100 	 * @since 5.3.0
       
  9101 	 */
       
  9102 	moveFocus: function() {
       
  9103 		if ( this.previousAttachment.length ) {
       
  9104 			this.previousAttachment.focus();
       
  9105 			return;
       
  9106 		}
       
  9107 
       
  9108 		if ( this.nextAttachment.length ) {
       
  9109 			this.nextAttachment.focus();
       
  9110 			return;
       
  9111 		}
       
  9112 
       
  9113 		// Fallback: move focus to the "Select Files" button in the media modal.
       
  9114 		if ( this.controller.uploader && this.controller.uploader.$browser ) {
       
  9115 			this.controller.uploader.$browser.focus();
       
  9116 			return;
       
  9117 		}
       
  9118 
       
  9119 		// Last fallback.
       
  9120 		this.moveFocusToLastFallback();
       
  9121 	},
       
  9122 
       
  9123 	/**
       
  9124 	 * Moves focus to the media frame as last fallback.
       
  9125 	 *
       
  9126 	 * @since 5.3.0
       
  9127 	 */
       
  9128 	moveFocusToLastFallback: function() {
       
  9129 		// Last fallback: make the frame focusable and move focus to it.
       
  9130 		$( '.media-frame' )
       
  9131 			.attr( 'tabindex', '-1' )
       
  9132 			.focus();
       
  9133 	},
       
  9134 
       
  9135 	/**
       
  9136 	 * Deletes an attachment.
       
  9137 	 *
       
  9138 	 * Deletes an attachment after asking for confirmation. After deletion,
       
  9139 	 * keeps focus in the modal.
       
  9140 	 *
       
  9141 	 * @since 3.5.0
       
  9142 	 *
       
  9143 	 * @param {MouseEvent} event A click event.
       
  9144 	 *
       
  9145 	 * @return {void}
  8611 	 */
  9146 	 */
  8612 	deleteAttachment: function( event ) {
  9147 	deleteAttachment: function( event ) {
  8613 		event.preventDefault();
  9148 		event.preventDefault();
  8614 
  9149 
       
  9150 		this.getFocusableElements();
       
  9151 
  8615 		if ( window.confirm( l10n.warnDelete ) ) {
  9152 		if ( window.confirm( l10n.warnDelete ) ) {
  8616 			this.model.destroy();
  9153 			this.model.destroy();
  8617 			// Keep focus inside media modal
  9154 			this.moveFocus();
  8618 			// after image is deleted
  9155 		}
  8619 			this.controller.modal.focusManager.focus();
  9156 	},
  8620 		}
  9157 
  8621 	},
  9158 	/**
  8622 	/**
  9159 	 * Sets the Trash state on an attachment, or destroys the model itself.
  8623 	 * @param {Object} event
  9160 	 *
       
  9161 	 * If the mediaTrash setting is set to true, trashes the attachment.
       
  9162 	 * Otherwise, the model itself is destroyed.
       
  9163 	 *
       
  9164 	 * @since 3.9.0
       
  9165 	 *
       
  9166 	 * @param {MouseEvent} event A click event.
       
  9167 	 *
       
  9168 	 * @return {void}
  8624 	 */
  9169 	 */
  8625 	trashAttachment: function( event ) {
  9170 	trashAttachment: function( event ) {
  8626 		var library = this.controller.library;
  9171 		var library = this.controller.library,
       
  9172 			self = this;
  8627 		event.preventDefault();
  9173 		event.preventDefault();
  8628 
  9174 
       
  9175 		this.getFocusableElements();
       
  9176 
       
  9177 		// When in the Media Library and the Media Trash is enabled.
  8629 		if ( wp.media.view.settings.mediaTrash &&
  9178 		if ( wp.media.view.settings.mediaTrash &&
  8630 			'edit-metadata' === this.controller.content.mode() ) {
  9179 			'edit-metadata' === this.controller.content.mode() ) {
  8631 
  9180 
  8632 			this.model.set( 'status', 'trash' );
  9181 			this.model.set( 'status', 'trash' );
  8633 			this.model.save().done( function() {
  9182 			this.model.save().done( function() {
  8634 				library._requery( true );
  9183 				library._requery( true );
       
  9184 				/*
       
  9185 				 * @todo We need to move focus back to the previous, next, or first
       
  9186 				 * attachment but the library gets re-queried and refreshed.
       
  9187 				 * Thus, the references to the previous attachments are lost.
       
  9188 				 * We need an alternate method.
       
  9189 				 */
       
  9190 				self.moveFocusToLastFallback();
  8635 			} );
  9191 			} );
  8636 		}  else {
  9192 		} else {
  8637 			this.model.destroy();
  9193 			this.model.destroy();
  8638 		}
  9194 			this.moveFocus();
  8639 	},
  9195 		}
  8640 	/**
  9196 	},
  8641 	 * @param {Object} event
  9197 	/**
       
  9198 	 * Untrashes an attachment.
       
  9199 	 *
       
  9200 	 * @since 4.0.0
       
  9201 	 *
       
  9202 	 * @param {MouseEvent} event A click event.
       
  9203 	 *
       
  9204 	 * @return {void}
  8642 	 */
  9205 	 */
  8643 	untrashAttachment: function( event ) {
  9206 	untrashAttachment: function( event ) {
  8644 		var library = this.controller.library;
  9207 		var library = this.controller.library;
  8645 		event.preventDefault();
  9208 		event.preventDefault();
  8646 
  9209 
  8647 		this.model.set( 'status', 'inherit' );
  9210 		this.model.set( 'status', 'inherit' );
  8648 		this.model.save().done( function() {
  9211 		this.model.save().done( function() {
  8649 			library._requery( true );
  9212 			library._requery( true );
  8650 		} );
  9213 		} );
  8651 	},
  9214 	},
  8652 	/**
  9215 
  8653 	 * @param {Object} event
  9216 	/**
       
  9217 	 * Opens the edit page for a specific attachment.
       
  9218 	 *
       
  9219 	 * @since 3.5.0
       
  9220 	 *
       
  9221 	 * @param {MouseEvent} event A click event.
       
  9222 	 *
       
  9223 	 * @return {void}
  8654 	 */
  9224 	 */
  8655 	editAttachment: function( event ) {
  9225 	editAttachment: function( event ) {
  8656 		var editState = this.controller.states.get( 'edit-image' );
  9226 		var editState = this.controller.states.get( 'edit-image' );
  8657 		if ( window.imageEdit && editState ) {
  9227 		if ( window.imageEdit && editState ) {
  8658 			event.preventDefault();
  9228 			event.preventDefault();
  8661 			this.controller.setState( 'edit-image' );
  9231 			this.controller.setState( 'edit-image' );
  8662 		} else {
  9232 		} else {
  8663 			this.$el.addClass('needs-refresh');
  9233 			this.$el.addClass('needs-refresh');
  8664 		}
  9234 		}
  8665 	},
  9235 	},
  8666 	/**
  9236 
  8667 	 * When reverse tabbing(shift+tab) out of the right details panel, deliver
  9237 	/**
  8668 	 * the focus to the item in the list that was being edited.
  9238 	 * Triggers an event on the controller when reverse tabbing (shift+tab).
  8669 	 *
  9239 	 *
  8670 	 * @param {Object} event
  9240 	 * This event can be used to make sure to move the focus correctly.
       
  9241 	 *
       
  9242 	 * @since 4.0.0
       
  9243 	 *
       
  9244 	 * @fires wp.media.controller.MediaLibrary#attachment:details:shift-tab
       
  9245 	 * @fires wp.media.controller.MediaLibrary#attachment:keydown:arrow
       
  9246 	 *
       
  9247 	 * @param {KeyboardEvent} event A keyboard event.
       
  9248 	 *
       
  9249 	 * @return {boolean|void} Returns false or undefined.
  8671 	 */
  9250 	 */
  8672 	toggleSelectionHandler: function( event ) {
  9251 	toggleSelectionHandler: function( event ) {
  8673 		if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
  9252 		if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
  8674 			this.controller.trigger( 'attachment:details:shift-tab', event );
  9253 			this.controller.trigger( 'attachment:details:shift-tab', event );
  8675 			return false;
  9254 			return false;
  8676 		}
       
  8677 
       
  8678 		if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
       
  8679 			this.controller.trigger( 'attachment:keydown:arrow', event );
       
  8680 			return;
       
  8681 		}
  9255 		}
  8682 	}
  9256 	}
  8683 });
  9257 });
  8684 
  9258 
  8685 module.exports = Details;
  9259 module.exports = Details;
  8717 
  9291 
  8718 	initialize: function() {
  9292 	initialize: function() {
  8719 		this.listenTo( this.model, 'change:compat', this.render );
  9293 		this.listenTo( this.model, 'change:compat', this.render );
  8720 	},
  9294 	},
  8721 	/**
  9295 	/**
  8722 	 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
  9296 	 * @return {wp.media.view.AttachmentCompat} Returns itself to allow chaining.
  8723 	 */
  9297 	 */
  8724 	dispose: function() {
  9298 	dispose: function() {
  8725 		if ( this.$(':focus').length ) {
  9299 		if ( this.$(':focus').length ) {
  8726 			this.save();
  9300 			this.save();
  8727 		}
  9301 		}
  8729 		 * call 'dispose' directly on the parent class
  9303 		 * call 'dispose' directly on the parent class
  8730 		 */
  9304 		 */
  8731 		return View.prototype.dispose.apply( this, arguments );
  9305 		return View.prototype.dispose.apply( this, arguments );
  8732 	},
  9306 	},
  8733 	/**
  9307 	/**
  8734 	 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
  9308 	 * @return {wp.media.view.AttachmentCompat} Returns itself to allow chaining.
  8735 	 */
  9309 	 */
  8736 	render: function() {
  9310 	render: function() {
  8737 		var compat = this.model.get('compat');
  9311 		var compat = this.model.get('compat');
  8738 		if ( ! compat || ! compat.item ) {
  9312 		if ( ! compat || ! compat.item ) {
  8739 			return;
  9313 			return;
  8791  * @augments Backbone.View
  9365  * @augments Backbone.View
  8792  */
  9366  */
  8793 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
  9367 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
  8794 	className: 'media-iframe',
  9368 	className: 'media-iframe',
  8795 	/**
  9369 	/**
  8796 	 * @returns {wp.media.view.Iframe} Returns itself to allow chaining
  9370 	 * @return {wp.media.view.Iframe} Returns itself to allow chaining.
  8797 	 */
  9371 	 */
  8798 	render: function() {
  9372 	render: function() {
  8799 		this.views.detach();
  9373 		this.views.detach();
  8800 		this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
  9374 		this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
  8801 		this.views.render();
  9375 		this.views.render();
  8912 /* 96 */
  9486 /* 96 */
  8913 /***/ (function(module, exports) {
  9487 /***/ (function(module, exports) {
  8914 
  9488 
  8915 var View = wp.media.View,
  9489 var View = wp.media.View,
  8916 	$ = jQuery,
  9490 	$ = jQuery,
       
  9491 	l10n = wp.media.view.l10n,
  8917 	EmbedUrl;
  9492 	EmbedUrl;
  8918 
  9493 
  8919 /**
  9494 /**
  8920  * wp.media.view.EmbedUrl
  9495  * wp.media.view.EmbedUrl
  8921  *
  9496  *
  8925  * @augments wp.media.View
  9500  * @augments wp.media.View
  8926  * @augments wp.Backbone.View
  9501  * @augments wp.Backbone.View
  8927  * @augments Backbone.View
  9502  * @augments Backbone.View
  8928  */
  9503  */
  8929 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
  9504 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
  8930 	tagName:   'label',
  9505 	tagName:   'span',
  8931 	className: 'embed-url',
  9506 	className: 'embed-url',
  8932 
  9507 
  8933 	events: {
  9508 	events: {
  8934 		'input':  'url',
  9509 		'input': 'url'
  8935 		'keyup':  'url',
       
  8936 		'change': 'url'
       
  8937 	},
  9510 	},
  8938 
  9511 
  8939 	initialize: function() {
  9512 	initialize: function() {
  8940 		this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
  9513 		this.$input = $( '<input id="embed-url-field" type="url" />' )
       
  9514 			.attr( 'aria-label', l10n.insertFromUrlTitle )
       
  9515 			.val( this.model.get('url') );
  8941 		this.input = this.$input[0];
  9516 		this.input = this.$input[0];
  8942 
  9517 
  8943 		this.spinner = $('<span class="spinner" />')[0];
  9518 		this.spinner = $('<span class="spinner" />')[0];
  8944 		this.$el.append([ this.input, this.spinner ]);
  9519 		this.$el.append([ this.input, this.spinner ]);
  8945 
  9520 
  8950 				this.model.trigger( 'change:url' );
  9525 				this.model.trigger( 'change:url' );
  8951 			}, this ), 500 );
  9526 			}, this ), 500 );
  8952 		}
  9527 		}
  8953 	},
  9528 	},
  8954 	/**
  9529 	/**
  8955 	 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
  9530 	 * @return {wp.media.view.EmbedUrl} Returns itself to allow chaining.
  8956 	 */
  9531 	 */
  8957 	render: function() {
  9532 	render: function() {
  8958 		var $input = this.$input;
  9533 		var $input = this.$input;
  8959 
  9534 
  8960 		if ( $input.is(':focus') ) {
  9535 		if ( $input.is(':focus') ) {
  8967 		 */
  9542 		 */
  8968 		View.prototype.render.apply( this, arguments );
  9543 		View.prototype.render.apply( this, arguments );
  8969 		return this;
  9544 		return this;
  8970 	},
  9545 	},
  8971 
  9546 
  8972 	ready: function() {
       
  8973 		if ( ! wp.media.isTouchDevice ) {
       
  8974 			this.focus();
       
  8975 		}
       
  8976 	},
       
  8977 
       
  8978 	url: function( event ) {
  9547 	url: function( event ) {
  8979 		this.model.set( 'url', $.trim( event.target.value ) );
  9548 		this.model.set( 'url', $.trim( event.target.value ) );
  8980 	},
       
  8981 
       
  8982 	/**
       
  8983 	 * If the input is visible, focus and select its contents.
       
  8984 	 */
       
  8985 	focus: function() {
       
  8986 		var $input = this.$input;
       
  8987 		if ( $input.is(':visible') ) {
       
  8988 			$input.focus()[0].select();
       
  8989 		}
       
  8990 	}
  9549 	}
  8991 });
  9550 });
  8992 
  9551 
  8993 module.exports = EmbedUrl;
  9552 module.exports = EmbedUrl;
  8994 
  9553 
  9020 	},
  9579 	},
  9021 
  9580 
  9022 	updateoEmbed: _.debounce( function() {
  9581 	updateoEmbed: _.debounce( function() {
  9023 		var url = this.model.get( 'url' );
  9582 		var url = this.model.get( 'url' );
  9024 
  9583 
  9025 		// clear out previous results
  9584 		// Clear out previous results.
  9026 		this.$('.embed-container').hide().find('.embed-preview').empty();
  9585 		this.$('.embed-container').hide().find('.embed-preview').empty();
  9027 		this.$( '.setting' ).hide();
  9586 		this.$( '.setting' ).hide();
  9028 
  9587 
  9029 		// only proceed with embed if the field contains more than 11 characters
  9588 		// Only proceed with embed if the field contains more than 11 characters.
  9030 		// Example: http://a.io is 11 chars
  9589 		// Example: http://a.io is 11 chars
  9031 		if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
  9590 		if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
  9032 			return;
  9591 			return;
  9033 		}
  9592 		}
  9034 
  9593 
  9036 	}, wp.media.controller.Embed.sensitivity ),
  9595 	}, wp.media.controller.Embed.sensitivity ),
  9037 
  9596 
  9038 	fetch: function() {
  9597 	fetch: function() {
  9039 		var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
  9598 		var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
  9040 
  9599 
  9041 		// check if they haven't typed in 500 ms
  9600 		// Check if they haven't typed in 500 ms.
  9042 		if ( $('#embed-url-field').val() !== url ) {
  9601 		if ( $('#embed-url-field').val() !== url ) {
  9043 			return;
  9602 			return;
  9044 		}
  9603 		}
  9045 
  9604 
  9046 		if ( this.dfd && 'pending' === this.dfd.state() ) {
  9605 		if ( this.dfd && 'pending' === this.dfd.state() ) {
  9166 		'change [data-setting="customHeight"]': 'onCustomSize',
  9725 		'change [data-setting="customHeight"]': 'onCustomSize',
  9167 		'keyup [data-setting="customWidth"]': 'onCustomSize',
  9726 		'keyup [data-setting="customWidth"]': 'onCustomSize',
  9168 		'keyup [data-setting="customHeight"]': 'onCustomSize'
  9727 		'keyup [data-setting="customHeight"]': 'onCustomSize'
  9169 	} ),
  9728 	} ),
  9170 	initialize: function() {
  9729 	initialize: function() {
  9171 		// used in AttachmentDisplay.prototype.updateLinkTo
  9730 		// Used in AttachmentDisplay.prototype.updateLinkTo.
  9172 		this.options.attachment = this.model.attachment;
  9731 		this.options.attachment = this.model.attachment;
  9173 		this.listenTo( this.model, 'change:url', this.updateUrl );
  9732 		this.listenTo( this.model, 'change:url', this.updateUrl );
  9174 		this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
  9733 		this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
  9175 		this.listenTo( this.model, 'change:size', this.toggleCustomSize );
  9734 		this.listenTo( this.model, 'change:size', this.toggleCustomSize );
  9176 
  9735 
  9210 
  9769 
  9211 		return this;
  9770 		return this;
  9212 	},
  9771 	},
  9213 
  9772 
  9214 	postRender: function() {
  9773 	postRender: function() {
  9215 		setTimeout( _.bind( this.resetFocus, this ), 10 );
  9774 		setTimeout( _.bind( this.scrollToTop, this ), 10 );
  9216 		this.toggleLinkSettings();
  9775 		this.toggleLinkSettings();
  9217 		if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
  9776 		if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
  9218 			this.toggleAdvanced( true );
  9777 			this.toggleAdvanced( true );
  9219 		}
  9778 		}
  9220 		this.trigger( 'post-render' );
  9779 		this.trigger( 'post-render' );
  9221 	},
  9780 	},
  9222 
  9781 
  9223 	resetFocus: function() {
  9782 	scrollToTop: function() {
  9224 		this.$( '.link-to-custom' ).blur();
       
  9225 		this.$( '.embed-media-settings' ).scrollTop( 0 );
  9783 		this.$( '.embed-media-settings' ).scrollTop( 0 );
  9226 	},
  9784 	},
  9227 
  9785 
  9228 	updateUrl: function() {
  9786 	updateUrl: function() {
  9229 		this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
  9787 		this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
  9249 	onCustomSize: function( event ) {
  9807 	onCustomSize: function( event ) {
  9250 		var dimension = $( event.target ).data('setting'),
  9808 		var dimension = $( event.target ).data('setting'),
  9251 			num = $( event.target ).val(),
  9809 			num = $( event.target ).val(),
  9252 			value;
  9810 			value;
  9253 
  9811 
  9254 		// Ignore bogus input
  9812 		// Ignore bogus input.
  9255 		if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
  9813 		if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
  9256 			event.preventDefault();
  9814 			event.preventDefault();
  9257 			return;
  9815 			return;
  9258 		}
  9816 		}
  9259 
  9817 
  9551 	prepare: function() {
 10109 	prepare: function() {
  9552 		return this.model.toJSON();
 10110 		return this.model.toJSON();
  9553 	},
 10111 	},
  9554 
 10112 
  9555 	loadEditor: function() {
 10113 	loadEditor: function() {
  9556 		var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
 10114 		this.editor.open( this.model.get( 'id' ), this.model.get( 'nonces' ).edit, this );
  9557 		dfd.done( _.bind( this.focus, this ) );
       
  9558 	},
       
  9559 
       
  9560 	focus: function() {
       
  9561 		this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
       
  9562 	},
 10115 	},
  9563 
 10116 
  9564 	back: function() {
 10117 	back: function() {
  9565 		var lastState = this.controller.lastState();
 10118 		var lastState = this.controller.lastState();
  9566 		this.controller.setState( lastState );
 10119 		this.controller.setState( lastState );