wp/wp-includes/js/media-grid.js
changeset 5 5e2f62d02dcd
child 7 cf61fcea0001
equal deleted inserted replaced
4:346c88efed21 5:5e2f62d02dcd
       
     1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
       
     2 /*globals wp */
       
     3 
       
     4 /**
       
     5  * wp.media.controller.EditAttachmentMetadata
       
     6  *
       
     7  * A state for editing an attachment's metadata.
       
     8  *
       
     9  * @class
       
    10  * @augments wp.media.controller.State
       
    11  * @augments Backbone.Model
       
    12  */
       
    13 var l10n = wp.media.view.l10n,
       
    14 	EditAttachmentMetadata;
       
    15 
       
    16 EditAttachmentMetadata = wp.media.controller.State.extend({
       
    17 	defaults: {
       
    18 		id:      'edit-attachment',
       
    19 		// Title string passed to the frame's title region view.
       
    20 		title:   l10n.attachmentDetails,
       
    21 		// Region mode defaults.
       
    22 		content: 'edit-metadata',
       
    23 		menu:    false,
       
    24 		toolbar: false,
       
    25 		router:  false
       
    26 	}
       
    27 });
       
    28 
       
    29 module.exports = EditAttachmentMetadata;
       
    30 
       
    31 },{}],2:[function(require,module,exports){
       
    32 /*globals wp */
       
    33 
       
    34 var media = wp.media;
       
    35 
       
    36 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' );
       
    37 media.view.MediaFrame.Manage = require( './views/frame/manage.js' );
       
    38 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' );
       
    39 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' );
       
    40 media.view.EditImage.Details = require( './views/edit-image-details.js' );
       
    41 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' );
       
    42 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' );
       
    43 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' );
       
    44 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' );
       
    45 
       
    46 },{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js":3,"./views/attachment/details-two-column.js":4,"./views/button/delete-selected-permanently.js":5,"./views/button/delete-selected.js":6,"./views/button/select-mode-toggle.js":7,"./views/edit-image-details.js":8,"./views/frame/edit-attachments.js":9,"./views/frame/manage.js":10}],3:[function(require,module,exports){
       
    47 /*globals wp, Backbone */
       
    48 
       
    49 /**
       
    50  * wp.media.view.MediaFrame.Manage.Router
       
    51  *
       
    52  * A router for handling the browser history and application state.
       
    53  *
       
    54  * @class
       
    55  * @augments Backbone.Router
       
    56  */
       
    57 var Router = Backbone.Router.extend({
       
    58 	routes: {
       
    59 		'upload.php?item=:slug':    'showItem',
       
    60 		'upload.php?search=:query': 'search'
       
    61 	},
       
    62 
       
    63 	// Map routes against the page URL
       
    64 	baseUrl: function( url ) {
       
    65 		return 'upload.php' + url;
       
    66 	},
       
    67 
       
    68 	// Respond to the search route by filling the search field and trigggering the input event
       
    69 	search: function( query ) {
       
    70 		jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
       
    71 	},
       
    72 
       
    73 	// Show the modal with a specific item
       
    74 	showItem: function( query ) {
       
    75 		var media = wp.media,
       
    76 			library = media.frame.state().get('library'),
       
    77 			item;
       
    78 
       
    79 		// Trigger the media frame to open the correct item
       
    80 		item = library.findWhere( { id: parseInt( query, 10 ) } );
       
    81 		if ( item ) {
       
    82 			media.frame.trigger( 'edit:attachment', item );
       
    83 		} else {
       
    84 			item = media.attachment( query );
       
    85 			media.frame.listenTo( item, 'change', function( model ) {
       
    86 				media.frame.stopListening( item );
       
    87 				media.frame.trigger( 'edit:attachment', model );
       
    88 			} );
       
    89 			item.fetch();
       
    90 		}
       
    91 	}
       
    92 });
       
    93 
       
    94 module.exports = Router;
       
    95 
       
    96 },{}],4:[function(require,module,exports){
       
    97 /*globals wp */
       
    98 
       
    99 /**
       
   100  * wp.media.view.Attachment.Details.TwoColumn
       
   101  *
       
   102  * A similar view to media.view.Attachment.Details
       
   103  * for use in the Edit Attachment modal.
       
   104  *
       
   105  * @class
       
   106  * @augments wp.media.view.Attachment.Details
       
   107  * @augments wp.media.view.Attachment
       
   108  * @augments wp.media.View
       
   109  * @augments wp.Backbone.View
       
   110  * @augments Backbone.View
       
   111  */
       
   112 var Details = wp.media.view.Attachment.Details,
       
   113 	TwoColumn;
       
   114 
       
   115 TwoColumn = Details.extend({
       
   116 	template: wp.template( 'attachment-details-two-column' ),
       
   117 
       
   118 	editAttachment: function( event ) {
       
   119 		event.preventDefault();
       
   120 		this.controller.content.mode( 'edit-image' );
       
   121 	},
       
   122 
       
   123 	/**
       
   124 	 * Noop this from parent class, doesn't apply here.
       
   125 	 */
       
   126 	toggleSelectionHandler: function() {},
       
   127 
       
   128 	render: function() {
       
   129 		Details.prototype.render.apply( this, arguments );
       
   130 
       
   131 		wp.media.mixin.removeAllPlayers();
       
   132 		this.$( 'audio, video' ).each( function (i, elem) {
       
   133 			var el = wp.media.view.MediaDetails.prepareSrc( elem );
       
   134 			new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
       
   135 		} );
       
   136 	}
       
   137 });
       
   138 
       
   139 module.exports = TwoColumn;
       
   140 
       
   141 },{}],5:[function(require,module,exports){
       
   142 /*globals wp */
       
   143 
       
   144 /**
       
   145  * wp.media.view.DeleteSelectedPermanentlyButton
       
   146  *
       
   147  * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
       
   148  *
       
   149  * @class
       
   150  * @augments wp.media.view.DeleteSelectedButton
       
   151  * @augments wp.media.view.Button
       
   152  * @augments wp.media.View
       
   153  * @augments wp.Backbone.View
       
   154  * @augments Backbone.View
       
   155  */
       
   156 var Button = wp.media.view.Button,
       
   157 	DeleteSelected = wp.media.view.DeleteSelectedButton,
       
   158 	DeleteSelectedPermanently;
       
   159 
       
   160 DeleteSelectedPermanently = DeleteSelected.extend({
       
   161 	initialize: function() {
       
   162 		DeleteSelected.prototype.initialize.apply( this, arguments );
       
   163 		this.listenTo( this.controller, 'select:activate', this.selectActivate );
       
   164 		this.listenTo( this.controller, 'select:deactivate', this.selectDeactivate );
       
   165 	},
       
   166 
       
   167 	filterChange: function( model ) {
       
   168 		this.canShow = ( 'trash' === model.get( 'status' ) );
       
   169 	},
       
   170 
       
   171 	selectActivate: function() {
       
   172 		this.toggleDisabled();
       
   173 		this.$el.toggleClass( 'hidden', ! this.canShow );
       
   174 	},
       
   175 
       
   176 	selectDeactivate: function() {
       
   177 		this.toggleDisabled();
       
   178 		this.$el.addClass( 'hidden' );
       
   179 	},
       
   180 
       
   181 	render: function() {
       
   182 		Button.prototype.render.apply( this, arguments );
       
   183 		this.selectActivate();
       
   184 		return this;
       
   185 	}
       
   186 });
       
   187 
       
   188 module.exports = DeleteSelectedPermanently;
       
   189 
       
   190 },{}],6:[function(require,module,exports){
       
   191 /*globals wp */
       
   192 
       
   193 /**
       
   194  * wp.media.view.DeleteSelectedButton
       
   195  *
       
   196  * A button that handles bulk Delete/Trash logic
       
   197  *
       
   198  * @class
       
   199  * @augments wp.media.view.Button
       
   200  * @augments wp.media.View
       
   201  * @augments wp.Backbone.View
       
   202  * @augments Backbone.View
       
   203  */
       
   204 var Button = wp.media.view.Button,
       
   205 	l10n = wp.media.view.l10n,
       
   206 	DeleteSelected;
       
   207 
       
   208 DeleteSelected = Button.extend({
       
   209 	initialize: function() {
       
   210 		Button.prototype.initialize.apply( this, arguments );
       
   211 		if ( this.options.filters ) {
       
   212 			this.listenTo( this.options.filters.model, 'change', this.filterChange );
       
   213 		}
       
   214 		this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
       
   215 	},
       
   216 
       
   217 	filterChange: function( model ) {
       
   218 		if ( 'trash' === model.get( 'status' ) ) {
       
   219 			this.model.set( 'text', l10n.untrashSelected );
       
   220 		} else if ( wp.media.view.settings.mediaTrash ) {
       
   221 			this.model.set( 'text', l10n.trashSelected );
       
   222 		} else {
       
   223 			this.model.set( 'text', l10n.deleteSelected );
       
   224 		}
       
   225 	},
       
   226 
       
   227 	toggleDisabled: function() {
       
   228 		this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
       
   229 	},
       
   230 
       
   231 	render: function() {
       
   232 		Button.prototype.render.apply( this, arguments );
       
   233 		if ( this.controller.isModeActive( 'select' ) ) {
       
   234 			this.$el.addClass( 'delete-selected-button' );
       
   235 		} else {
       
   236 			this.$el.addClass( 'delete-selected-button hidden' );
       
   237 		}
       
   238 		this.toggleDisabled();
       
   239 		return this;
       
   240 	}
       
   241 });
       
   242 
       
   243 module.exports = DeleteSelected;
       
   244 
       
   245 },{}],7:[function(require,module,exports){
       
   246 /*globals wp */
       
   247 
       
   248 /**
       
   249  * wp.media.view.SelectModeToggleButton
       
   250  *
       
   251  * @class
       
   252  * @augments wp.media.view.Button
       
   253  * @augments wp.media.View
       
   254  * @augments wp.Backbone.View
       
   255  * @augments Backbone.View
       
   256  */
       
   257 var Button = wp.media.view.Button,
       
   258 	l10n = wp.media.view.l10n,
       
   259 	SelectModeToggle;
       
   260 
       
   261 SelectModeToggle = Button.extend({
       
   262 	initialize: function() {
       
   263 		Button.prototype.initialize.apply( this, arguments );
       
   264 		this.listenTo( this.controller, 'select:activate select:deactivate', this.toggleBulkEditHandler );
       
   265 		this.listenTo( this.controller, 'selection:action:done', this.back );
       
   266 	},
       
   267 
       
   268 	back: function () {
       
   269 		this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
       
   270 	},
       
   271 
       
   272 	click: function() {
       
   273 		Button.prototype.click.apply( this, arguments );
       
   274 		if ( this.controller.isModeActive( 'select' ) ) {
       
   275 			this.back();
       
   276 		} else {
       
   277 			this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
       
   278 		}
       
   279 	},
       
   280 
       
   281 	render: function() {
       
   282 		Button.prototype.render.apply( this, arguments );
       
   283 		this.$el.addClass( 'select-mode-toggle-button' );
       
   284 		return this;
       
   285 	},
       
   286 
       
   287 	toggleBulkEditHandler: function() {
       
   288 		var toolbar = this.controller.content.get().toolbar, children;
       
   289 
       
   290 		children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
       
   291 
       
   292 		// TODO: the Frame should be doing all of this.
       
   293 		if ( this.controller.isModeActive( 'select' ) ) {
       
   294 			this.model.set( 'text', l10n.cancelSelection );
       
   295 			children.not( '.media-button' ).hide();
       
   296 			this.$el.show();
       
   297 			toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
       
   298 		} else {
       
   299 			this.model.set( 'text', l10n.bulkSelect );
       
   300 			this.controller.content.get().$el.removeClass( 'fixed' );
       
   301 			toolbar.$el.css( 'width', '' );
       
   302 			toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
       
   303 			children.not( '.spinner, .media-button' ).show();
       
   304 			this.controller.state().get( 'selection' ).reset();
       
   305 		}
       
   306 	}
       
   307 });
       
   308 
       
   309 module.exports = SelectModeToggle;
       
   310 
       
   311 },{}],8:[function(require,module,exports){
       
   312 /*globals wp, _ */
       
   313 
       
   314 /**
       
   315  * wp.media.view.EditImage.Details
       
   316  *
       
   317  * @class
       
   318  * @augments wp.media.view.EditImage
       
   319  * @augments wp.media.View
       
   320  * @augments wp.Backbone.View
       
   321  * @augments Backbone.View
       
   322  */
       
   323 var View = wp.media.View,
       
   324 	EditImage = wp.media.view.EditImage,
       
   325 	Details;
       
   326 
       
   327 Details = EditImage.extend({
       
   328 	initialize: function( options ) {
       
   329 		this.editor = window.imageEdit;
       
   330 		this.frame = options.frame;
       
   331 		this.controller = options.controller;
       
   332 		View.prototype.initialize.apply( this, arguments );
       
   333 	},
       
   334 
       
   335 	back: function() {
       
   336 		this.frame.content.mode( 'edit-metadata' );
       
   337 	},
       
   338 
       
   339 	save: function() {
       
   340 		this.model.fetch().done( _.bind( function() {
       
   341 			this.frame.content.mode( 'edit-metadata' );
       
   342 		}, this ) );
       
   343 	}
       
   344 });
       
   345 
       
   346 module.exports = Details;
       
   347 
       
   348 },{}],9:[function(require,module,exports){
       
   349 /*globals wp, _, jQuery */
       
   350 
       
   351 /**
       
   352  * wp.media.view.MediaFrame.EditAttachments
       
   353  *
       
   354  * A frame for editing the details of a specific media item.
       
   355  *
       
   356  * Opens in a modal by default.
       
   357  *
       
   358  * Requires an attachment model to be passed in the options hash under `model`.
       
   359  *
       
   360  * @class
       
   361  * @augments wp.media.view.Frame
       
   362  * @augments wp.media.View
       
   363  * @augments wp.Backbone.View
       
   364  * @augments Backbone.View
       
   365  * @mixes wp.media.controller.StateMachine
       
   366  */
       
   367 var Frame = wp.media.view.Frame,
       
   368 	MediaFrame = wp.media.view.MediaFrame,
       
   369 
       
   370 	$ = jQuery,
       
   371 	EditAttachments;
       
   372 
       
   373 EditAttachments = MediaFrame.extend({
       
   374 
       
   375 	className: 'edit-attachment-frame',
       
   376 	template:  wp.template( 'edit-attachment-frame' ),
       
   377 	regions:   [ 'title', 'content' ],
       
   378 
       
   379 	events: {
       
   380 		'click .left':  'previousMediaItem',
       
   381 		'click .right': 'nextMediaItem'
       
   382 	},
       
   383 
       
   384 	initialize: function() {
       
   385 		Frame.prototype.initialize.apply( this, arguments );
       
   386 
       
   387 		_.defaults( this.options, {
       
   388 			modal: true,
       
   389 			state: 'edit-attachment'
       
   390 		});
       
   391 
       
   392 		this.controller = this.options.controller;
       
   393 		this.gridRouter = this.controller.gridRouter;
       
   394 		this.library = this.options.library;
       
   395 
       
   396 		if ( this.options.model ) {
       
   397 			this.model = this.options.model;
       
   398 		}
       
   399 
       
   400 		this.bindHandlers();
       
   401 		this.createStates();
       
   402 		this.createModal();
       
   403 
       
   404 		this.title.mode( 'default' );
       
   405 		this.toggleNav();
       
   406 	},
       
   407 
       
   408 	bindHandlers: function() {
       
   409 		// Bind default title creation.
       
   410 		this.on( 'title:create:default', this.createTitle, this );
       
   411 
       
   412 		// Close the modal if the attachment is deleted.
       
   413 		this.listenTo( this.model, 'change:status destroy', this.close, this );
       
   414 
       
   415 		this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
       
   416 		this.on( 'content:create:edit-image', this.editImageMode, this );
       
   417 		this.on( 'content:render:edit-image', this.editImageModeRender, this );
       
   418 		this.on( 'close', this.detach );
       
   419 	},
       
   420 
       
   421 	createModal: function() {
       
   422 		// Initialize modal container view.
       
   423 		if ( this.options.modal ) {
       
   424 			this.modal = new wp.media.view.Modal({
       
   425 				controller: this,
       
   426 				title:      this.options.title
       
   427 			});
       
   428 
       
   429 			this.modal.on( 'open', _.bind( function () {
       
   430 				$( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
       
   431 			}, this ) );
       
   432 
       
   433 			// Completely destroy the modal DOM element when closing it.
       
   434 			this.modal.on( 'close', _.bind( function() {
       
   435 				this.modal.remove();
       
   436 				$( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
       
   437 				// Restore the original focus item if possible
       
   438 				$( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
       
   439 				this.resetRoute();
       
   440 			}, this ) );
       
   441 
       
   442 			// Set this frame as the modal's content.
       
   443 			this.modal.content( this );
       
   444 			this.modal.open();
       
   445 		}
       
   446 	},
       
   447 
       
   448 	/**
       
   449 	 * Add the default states to the frame.
       
   450 	 */
       
   451 	createStates: function() {
       
   452 		this.states.add([
       
   453 			new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
       
   454 		]);
       
   455 	},
       
   456 
       
   457 	/**
       
   458 	 * Content region rendering callback for the `edit-metadata` mode.
       
   459 	 *
       
   460 	 * @param {Object} contentRegion Basic object with a `view` property, which
       
   461 	 *                               should be set with the proper region view.
       
   462 	 */
       
   463 	editMetadataMode: function( contentRegion ) {
       
   464 		contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
       
   465 			controller: this,
       
   466 			model:      this.model
       
   467 		});
       
   468 
       
   469 		/**
       
   470 		 * Attach a subview to display fields added via the
       
   471 		 * `attachment_fields_to_edit` filter.
       
   472 		 */
       
   473 		contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
       
   474 			controller: this,
       
   475 			model:      this.model
       
   476 		}) );
       
   477 
       
   478 		// Update browser url when navigating media details
       
   479 		if ( this.model ) {
       
   480 			this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
       
   481 		}
       
   482 	},
       
   483 
       
   484 	/**
       
   485 	 * Render the EditImage view into the frame's content region.
       
   486 	 *
       
   487 	 * @param {Object} contentRegion Basic object with a `view` property, which
       
   488 	 *                               should be set with the proper region view.
       
   489 	 */
       
   490 	editImageMode: function( contentRegion ) {
       
   491 		var editImageController = new wp.media.controller.EditImage( {
       
   492 			model: this.model,
       
   493 			frame: this
       
   494 		} );
       
   495 		// Noop some methods.
       
   496 		editImageController._toolbar = function() {};
       
   497 		editImageController._router = function() {};
       
   498 		editImageController._menu = function() {};
       
   499 
       
   500 		contentRegion.view = new wp.media.view.EditImage.Details( {
       
   501 			model: this.model,
       
   502 			frame: this,
       
   503 			controller: editImageController
       
   504 		} );
       
   505 	},
       
   506 
       
   507 	editImageModeRender: function( view ) {
       
   508 		view.on( 'ready', view.loadEditor );
       
   509 	},
       
   510 
       
   511 	toggleNav: function() {
       
   512 		this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() );
       
   513 		this.$('.right').toggleClass( 'disabled', ! this.hasNext() );
       
   514 	},
       
   515 
       
   516 	/**
       
   517 	 * Rerender the view.
       
   518 	 */
       
   519 	rerender: function() {
       
   520 		// Only rerender the `content` region.
       
   521 		if ( this.content.mode() !== 'edit-metadata' ) {
       
   522 			this.content.mode( 'edit-metadata' );
       
   523 		} else {
       
   524 			this.content.render();
       
   525 		}
       
   526 
       
   527 		this.toggleNav();
       
   528 	},
       
   529 
       
   530 	/**
       
   531 	 * Click handler to switch to the previous media item.
       
   532 	 */
       
   533 	previousMediaItem: function() {
       
   534 		if ( ! this.hasPrevious() ) {
       
   535 			this.$( '.left' ).blur();
       
   536 			return;
       
   537 		}
       
   538 		this.model = this.library.at( this.getCurrentIndex() - 1 );
       
   539 		this.rerender();
       
   540 		this.$( '.left' ).focus();
       
   541 	},
       
   542 
       
   543 	/**
       
   544 	 * Click handler to switch to the next media item.
       
   545 	 */
       
   546 	nextMediaItem: function() {
       
   547 		if ( ! this.hasNext() ) {
       
   548 			this.$( '.right' ).blur();
       
   549 			return;
       
   550 		}
       
   551 		this.model = this.library.at( this.getCurrentIndex() + 1 );
       
   552 		this.rerender();
       
   553 		this.$( '.right' ).focus();
       
   554 	},
       
   555 
       
   556 	getCurrentIndex: function() {
       
   557 		return this.library.indexOf( this.model );
       
   558 	},
       
   559 
       
   560 	hasNext: function() {
       
   561 		return ( this.getCurrentIndex() + 1 ) < this.library.length;
       
   562 	},
       
   563 
       
   564 	hasPrevious: function() {
       
   565 		return ( this.getCurrentIndex() - 1 ) > -1;
       
   566 	},
       
   567 	/**
       
   568 	 * Respond to the keyboard events: right arrow, left arrow, except when
       
   569 	 * focus is in a textarea or input field.
       
   570 	 */
       
   571 	keyEvent: function( event ) {
       
   572 		if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
       
   573 			return;
       
   574 		}
       
   575 
       
   576 		// The right arrow key
       
   577 		if ( 39 === event.keyCode ) {
       
   578 			this.nextMediaItem();
       
   579 		}
       
   580 		// The left arrow key
       
   581 		if ( 37 === event.keyCode ) {
       
   582 			this.previousMediaItem();
       
   583 		}
       
   584 	},
       
   585 
       
   586 	resetRoute: function() {
       
   587 		this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
       
   588 	}
       
   589 });
       
   590 
       
   591 module.exports = EditAttachments;
       
   592 
       
   593 },{}],10:[function(require,module,exports){
       
   594 /*globals wp, _, Backbone */
       
   595 
       
   596 /**
       
   597  * wp.media.view.MediaFrame.Manage
       
   598  *
       
   599  * A generic management frame workflow.
       
   600  *
       
   601  * Used in the media grid view.
       
   602  *
       
   603  * @class
       
   604  * @augments wp.media.view.MediaFrame
       
   605  * @augments wp.media.view.Frame
       
   606  * @augments wp.media.View
       
   607  * @augments wp.Backbone.View
       
   608  * @augments Backbone.View
       
   609  * @mixes wp.media.controller.StateMachine
       
   610  */
       
   611 var MediaFrame = wp.media.view.MediaFrame,
       
   612 	Library = wp.media.controller.Library,
       
   613 
       
   614 	$ = Backbone.$,
       
   615 	Manage;
       
   616 
       
   617 Manage = MediaFrame.extend({
       
   618 	/**
       
   619 	 * @global wp.Uploader
       
   620 	 */
       
   621 	initialize: function() {
       
   622 		_.defaults( this.options, {
       
   623 			title:     '',
       
   624 			modal:     false,
       
   625 			selection: [],
       
   626 			library:   {}, // Options hash for the query to the media library.
       
   627 			multiple:  'add',
       
   628 			state:     'library',
       
   629 			uploader:  true,
       
   630 			mode:      [ 'grid', 'edit' ]
       
   631 		});
       
   632 
       
   633 		this.$body = $( document.body );
       
   634 		this.$window = $( window );
       
   635 		this.$adminBar = $( '#wpadminbar' );
       
   636 		this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
       
   637 		$( document ).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) );
       
   638 
       
   639 		// Ensure core and media grid view UI is enabled.
       
   640 		this.$el.addClass('wp-core-ui');
       
   641 
       
   642 		// Force the uploader off if the upload limit has been exceeded or
       
   643 		// if the browser isn't supported.
       
   644 		if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
       
   645 			this.options.uploader = false;
       
   646 		}
       
   647 
       
   648 		// Initialize a window-wide uploader.
       
   649 		if ( this.options.uploader ) {
       
   650 			this.uploader = new wp.media.view.UploaderWindow({
       
   651 				controller: this,
       
   652 				uploader: {
       
   653 					dropzone:  document.body,
       
   654 					container: document.body
       
   655 				}
       
   656 			}).render();
       
   657 			this.uploader.ready();
       
   658 			$('body').append( this.uploader.el );
       
   659 
       
   660 			this.options.uploader = false;
       
   661 		}
       
   662 
       
   663 		this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
       
   664 
       
   665 		// Call 'initialize' directly on the parent class.
       
   666 		MediaFrame.prototype.initialize.apply( this, arguments );
       
   667 
       
   668 		// Append the frame view directly the supplied container.
       
   669 		this.$el.appendTo( this.options.container );
       
   670 
       
   671 		this.createStates();
       
   672 		this.bindRegionModeHandlers();
       
   673 		this.render();
       
   674 		this.bindSearchHandler();
       
   675 	},
       
   676 
       
   677 	bindSearchHandler: function() {
       
   678 		var search = this.$( '#media-search-input' ),
       
   679 			currentSearch = this.options.container.data( 'search' ),
       
   680 			searchView = this.browserView.toolbar.get( 'search' ).$el,
       
   681 			listMode = this.$( '.view-list' ),
       
   682 
       
   683 			input  = _.debounce( function (e) {
       
   684 				var val = $( e.currentTarget ).val(),
       
   685 					url = '';
       
   686 
       
   687 				if ( val ) {
       
   688 					url += '?search=' + val;
       
   689 				}
       
   690 				this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
       
   691 			}, 1000 );
       
   692 
       
   693 		// Update the URL when entering search string (at most once per second)
       
   694 		search.on( 'input', _.bind( input, this ) );
       
   695 		searchView.val( currentSearch ).trigger( 'input' );
       
   696 
       
   697 		this.gridRouter.on( 'route:search', function () {
       
   698 			var href = window.location.href;
       
   699 			if ( href.indexOf( 'mode=' ) > -1 ) {
       
   700 				href = href.replace( /mode=[^&]+/g, 'mode=list' );
       
   701 			} else {
       
   702 				href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
       
   703 			}
       
   704 			href = href.replace( 'search=', 's=' );
       
   705 			listMode.prop( 'href', href );
       
   706 		} );
       
   707 	},
       
   708 
       
   709 	/**
       
   710 	 * Create the default states for the frame.
       
   711 	 */
       
   712 	createStates: function() {
       
   713 		var options = this.options;
       
   714 
       
   715 		if ( this.options.states ) {
       
   716 			return;
       
   717 		}
       
   718 
       
   719 		// Add the default states.
       
   720 		this.states.add([
       
   721 			new Library({
       
   722 				library:            wp.media.query( options.library ),
       
   723 				multiple:           options.multiple,
       
   724 				title:              options.title,
       
   725 				content:            'browse',
       
   726 				toolbar:            'select',
       
   727 				contentUserSetting: false,
       
   728 				filterable:         'all',
       
   729 				autoSelect:         false
       
   730 			})
       
   731 		]);
       
   732 	},
       
   733 
       
   734 	/**
       
   735 	 * Bind region mode activation events to proper handlers.
       
   736 	 */
       
   737 	bindRegionModeHandlers: function() {
       
   738 		this.on( 'content:create:browse', this.browseContent, this );
       
   739 
       
   740 		// Handle a frame-level event for editing an attachment.
       
   741 		this.on( 'edit:attachment', this.openEditAttachmentModal, this );
       
   742 
       
   743 		this.on( 'select:activate', this.bindKeydown, this );
       
   744 		this.on( 'select:deactivate', this.unbindKeydown, this );
       
   745 	},
       
   746 
       
   747 	handleKeydown: function( e ) {
       
   748 		if ( 27 === e.which ) {
       
   749 			e.preventDefault();
       
   750 			this.deactivateMode( 'select' ).activateMode( 'edit' );
       
   751 		}
       
   752 	},
       
   753 
       
   754 	bindKeydown: function() {
       
   755 		this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
       
   756 	},
       
   757 
       
   758 	unbindKeydown: function() {
       
   759 		this.$body.off( 'keydown.select' );
       
   760 	},
       
   761 
       
   762 	fixPosition: function() {
       
   763 		var $browser, $toolbar;
       
   764 		if ( ! this.isModeActive( 'select' ) ) {
       
   765 			return;
       
   766 		}
       
   767 
       
   768 		$browser = this.$('.attachments-browser');
       
   769 		$toolbar = $browser.find('.media-toolbar');
       
   770 
       
   771 		// Offset doesn't appear to take top margin into account, hence +16
       
   772 		if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
       
   773 			$browser.addClass( 'fixed' );
       
   774 			$toolbar.css('width', $browser.width() + 'px');
       
   775 		} else {
       
   776 			$browser.removeClass( 'fixed' );
       
   777 			$toolbar.css('width', '');
       
   778 		}
       
   779 	},
       
   780 
       
   781 	/**
       
   782 	 * Click handler for the `Add New` button.
       
   783 	 */
       
   784 	addNewClickHandler: function( event ) {
       
   785 		event.preventDefault();
       
   786 		this.trigger( 'toggle:upload:attachment' );
       
   787 	},
       
   788 
       
   789 	/**
       
   790 	 * Open the Edit Attachment modal.
       
   791 	 */
       
   792 	openEditAttachmentModal: function( model ) {
       
   793 		// Create a new EditAttachment frame, passing along the library and the attachment model.
       
   794 		wp.media( {
       
   795 			frame:       'edit-attachments',
       
   796 			controller:  this,
       
   797 			library:     this.state().get('library'),
       
   798 			model:       model
       
   799 		} );
       
   800 	},
       
   801 
       
   802 	/**
       
   803 	 * Create an attachments browser view within the content region.
       
   804 	 *
       
   805 	 * @param {Object} contentRegion Basic object with a `view` property, which
       
   806 	 *                               should be set with the proper region view.
       
   807 	 * @this wp.media.controller.Region
       
   808 	 */
       
   809 	browseContent: function( contentRegion ) {
       
   810 		var state = this.state();
       
   811 
       
   812 		// Browse our library of attachments.
       
   813 		this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
       
   814 			controller: this,
       
   815 			collection: state.get('library'),
       
   816 			selection:  state.get('selection'),
       
   817 			model:      state,
       
   818 			sortable:   state.get('sortable'),
       
   819 			search:     state.get('searchable'),
       
   820 			filters:    state.get('filterable'),
       
   821 			date:       state.get('date'),
       
   822 			display:    state.get('displaySettings'),
       
   823 			dragInfo:   state.get('dragInfo'),
       
   824 			sidebar:    'errors',
       
   825 
       
   826 			suggestedWidth:  state.get('suggestedWidth'),
       
   827 			suggestedHeight: state.get('suggestedHeight'),
       
   828 
       
   829 			AttachmentView: state.get('AttachmentView'),
       
   830 
       
   831 			scrollElement: document
       
   832 		});
       
   833 		this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
       
   834 
       
   835 		this.errors = wp.Uploader.errors;
       
   836 		this.errors.on( 'add remove reset', this.sidebarVisibility, this );
       
   837 	},
       
   838 
       
   839 	sidebarVisibility: function() {
       
   840 		this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
       
   841 	},
       
   842 
       
   843 	bindDeferred: function() {
       
   844 		if ( ! this.browserView.dfd ) {
       
   845 			return;
       
   846 		}
       
   847 		this.browserView.dfd.done( _.bind( this.startHistory, this ) );
       
   848 	},
       
   849 
       
   850 	startHistory: function() {
       
   851 		// Verify pushState support and activate
       
   852 		if ( window.history && window.history.pushState ) {
       
   853 			Backbone.history.start( {
       
   854 				root: window._wpMediaGridSettings.adminUrl,
       
   855 				pushState: true
       
   856 			} );
       
   857 		}
       
   858 	}
       
   859 });
       
   860 
       
   861 module.exports = Manage;
       
   862 
       
   863 },{}]},{},[2]);