PageRenderTime 77ms CodeModel.GetById 2ms app.highlight 61ms RepoModel.GetById 1ms app.codeStats 1ms

/wp-includes/js/media-models.js

http://github.com/wordpress/wordpress
JavaScript | 1677 lines | 828 code | 195 blank | 654 comment | 213 complexity | 4c9720b69a4695c799544597419eb86a MD5 | raw file
   1/******/ (function(modules) { // webpackBootstrap
   2/******/ 	// The module cache
   3/******/ 	var installedModules = {};
   4/******/
   5/******/ 	// The require function
   6/******/ 	function __webpack_require__(moduleId) {
   7/******/
   8/******/ 		// Check if module is in cache
   9/******/ 		if(installedModules[moduleId]) {
  10/******/ 			return installedModules[moduleId].exports;
  11/******/ 		}
  12/******/ 		// Create a new module (and put it into the cache)
  13/******/ 		var module = installedModules[moduleId] = {
  14/******/ 			i: moduleId,
  15/******/ 			l: false,
  16/******/ 			exports: {}
  17/******/ 		};
  18/******/
  19/******/ 		// Execute the module function
  20/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  21/******/
  22/******/ 		// Flag the module as loaded
  23/******/ 		module.l = true;
  24/******/
  25/******/ 		// Return the exports of the module
  26/******/ 		return module.exports;
  27/******/ 	}
  28/******/
  29/******/
  30/******/ 	// expose the modules object (__webpack_modules__)
  31/******/ 	__webpack_require__.m = modules;
  32/******/
  33/******/ 	// expose the module cache
  34/******/ 	__webpack_require__.c = installedModules;
  35/******/
  36/******/ 	// define getter function for harmony exports
  37/******/ 	__webpack_require__.d = function(exports, name, getter) {
  38/******/ 		if(!__webpack_require__.o(exports, name)) {
  39/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
  40/******/ 		}
  41/******/ 	};
  42/******/
  43/******/ 	// define __esModule on exports
  44/******/ 	__webpack_require__.r = function(exports) {
  45/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  46/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  47/******/ 		}
  48/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
  49/******/ 	};
  50/******/
  51/******/ 	// create a fake namespace object
  52/******/ 	// mode & 1: value is a module id, require it
  53/******/ 	// mode & 2: merge all properties of value into the ns
  54/******/ 	// mode & 4: return value when already ns object
  55/******/ 	// mode & 8|1: behave like require
  56/******/ 	__webpack_require__.t = function(value, mode) {
  57/******/ 		if(mode & 1) value = __webpack_require__(value);
  58/******/ 		if(mode & 8) return value;
  59/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  60/******/ 		var ns = Object.create(null);
  61/******/ 		__webpack_require__.r(ns);
  62/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  63/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
  64/******/ 		return ns;
  65/******/ 	};
  66/******/
  67/******/ 	// getDefaultExport function for compatibility with non-harmony modules
  68/******/ 	__webpack_require__.n = function(module) {
  69/******/ 		var getter = module && module.__esModule ?
  70/******/ 			function getDefault() { return module['default']; } :
  71/******/ 			function getModuleExports() { return module; };
  72/******/ 		__webpack_require__.d(getter, 'a', getter);
  73/******/ 		return getter;
  74/******/ 	};
  75/******/
  76/******/ 	// Object.prototype.hasOwnProperty.call
  77/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  78/******/
  79/******/ 	// __webpack_public_path__
  80/******/ 	__webpack_require__.p = "";
  81/******/
  82/******/
  83/******/ 	// Load entry module and return exports
  84/******/ 	return __webpack_require__(__webpack_require__.s = 22);
  85/******/ })
  86/************************************************************************/
  87/******/ ({
  88
  89/***/ 22:
  90/***/ (function(module, exports, __webpack_require__) {
  91
  92module.exports = __webpack_require__(23);
  93
  94
  95/***/ }),
  96
  97/***/ 23:
  98/***/ (function(module, exports, __webpack_require__) {
  99
 100/**
 101 * @output wp-includes/js/media-models.js
 102 */
 103
 104var $ = jQuery,
 105	Attachment, Attachments, l10n, media;
 106
 107/** @namespace wp */
 108window.wp = window.wp || {};
 109
 110/**
 111 * Create and return a media frame.
 112 *
 113 * Handles the default media experience.
 114 *
 115 * @alias wp.media
 116 * @memberOf wp
 117 * @namespace
 118 *
 119 * @param  {object} attributes The properties passed to the main media controller.
 120 * @return {wp.media.view.MediaFrame} A media workflow.
 121 */
 122media = wp.media = function( attributes ) {
 123	var MediaFrame = media.view.MediaFrame,
 124		frame;
 125
 126	if ( ! MediaFrame ) {
 127		return;
 128	}
 129
 130	attributes = _.defaults( attributes || {}, {
 131		frame: 'select'
 132	});
 133
 134	if ( 'select' === attributes.frame && MediaFrame.Select ) {
 135		frame = new MediaFrame.Select( attributes );
 136	} else if ( 'post' === attributes.frame && MediaFrame.Post ) {
 137		frame = new MediaFrame.Post( attributes );
 138	} else if ( 'manage' === attributes.frame && MediaFrame.Manage ) {
 139		frame = new MediaFrame.Manage( attributes );
 140	} else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) {
 141		frame = new MediaFrame.ImageDetails( attributes );
 142	} else if ( 'audio' === attributes.frame && MediaFrame.AudioDetails ) {
 143		frame = new MediaFrame.AudioDetails( attributes );
 144	} else if ( 'video' === attributes.frame && MediaFrame.VideoDetails ) {
 145		frame = new MediaFrame.VideoDetails( attributes );
 146	} else if ( 'edit-attachments' === attributes.frame && MediaFrame.EditAttachments ) {
 147		frame = new MediaFrame.EditAttachments( attributes );
 148	}
 149
 150	delete attributes.frame;
 151
 152	media.frame = frame;
 153
 154	return frame;
 155};
 156
 157/** @namespace wp.media.model */
 158/** @namespace wp.media.view */
 159/** @namespace wp.media.controller */
 160/** @namespace wp.media.frames */
 161_.extend( media, { model: {}, view: {}, controller: {}, frames: {} });
 162
 163// Link any localized strings.
 164l10n = media.model.l10n = window._wpMediaModelsL10n || {};
 165
 166// Link any settings.
 167media.model.settings = l10n.settings || {};
 168delete l10n.settings;
 169
 170Attachment = media.model.Attachment = __webpack_require__( 24 );
 171Attachments = media.model.Attachments = __webpack_require__( 25 );
 172
 173media.model.Query = __webpack_require__( 26 );
 174media.model.PostImage = __webpack_require__( 27 );
 175media.model.Selection = __webpack_require__( 28 );
 176
 177/**
 178 * ========================================================================
 179 * UTILITIES
 180 * ========================================================================
 181 */
 182
 183/**
 184 * A basic equality comparator for Backbone models.
 185 *
 186 * Used to order models within a collection - @see wp.media.model.Attachments.comparator().
 187 *
 188 * @param  {mixed}  a  The primary parameter to compare.
 189 * @param  {mixed}  b  The primary parameter to compare.
 190 * @param  {string} ac The fallback parameter to compare, a's cid.
 191 * @param  {string} bc The fallback parameter to compare, b's cid.
 192 * @return {number}    -1: a should come before b.
 193 *                      0: a and b are of the same rank.
 194 *                      1: b should come before a.
 195 */
 196media.compare = function( a, b, ac, bc ) {
 197	if ( _.isEqual( a, b ) ) {
 198		return ac === bc ? 0 : (ac > bc ? -1 : 1);
 199	} else {
 200		return a > b ? -1 : 1;
 201	}
 202};
 203
 204_.extend( media, /** @lends wp.media */{
 205	/**
 206	 * media.template( id )
 207	 *
 208	 * Fetch a JavaScript template for an id, and return a templating function for it.
 209	 *
 210	 * See wp.template() in `wp-includes/js/wp-util.js`.
 211	 *
 212	 * @borrows wp.template as template
 213	 */
 214	template: wp.template,
 215
 216	/**
 217	 * media.post( [action], [data] )
 218	 *
 219	 * Sends a POST request to WordPress.
 220	 * See wp.ajax.post() in `wp-includes/js/wp-util.js`.
 221	 *
 222	 * @borrows wp.ajax.post as post
 223	 */
 224	post: wp.ajax.post,
 225
 226	/**
 227	 * media.ajax( [action], [options] )
 228	 *
 229	 * Sends an XHR request to WordPress.
 230	 * See wp.ajax.send() in `wp-includes/js/wp-util.js`.
 231	 *
 232	 * @borrows wp.ajax.send as ajax
 233	 */
 234	ajax: wp.ajax.send,
 235
 236	/**
 237	 * Scales a set of dimensions to fit within bounding dimensions.
 238	 *
 239	 * @param {Object} dimensions
 240	 * @return {Object}
 241	 */
 242	fit: function( dimensions ) {
 243		var width     = dimensions.width,
 244			height    = dimensions.height,
 245			maxWidth  = dimensions.maxWidth,
 246			maxHeight = dimensions.maxHeight,
 247			constraint;
 248
 249		/*
 250		 * Compare ratios between the two values to determine
 251		 * which max to constrain by. If a max value doesn't exist,
 252		 * then the opposite side is the constraint.
 253		 */
 254		if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
 255			constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
 256		} else if ( _.isUndefined( maxHeight ) ) {
 257			constraint = 'width';
 258		} else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
 259			constraint = 'height';
 260		}
 261
 262		// If the value of the constrained side is larger than the max,
 263		// then scale the values. Otherwise return the originals; they fit.
 264		if ( 'width' === constraint && width > maxWidth ) {
 265			return {
 266				width : maxWidth,
 267				height: Math.round( maxWidth * height / width )
 268			};
 269		} else if ( 'height' === constraint && height > maxHeight ) {
 270			return {
 271				width : Math.round( maxHeight * width / height ),
 272				height: maxHeight
 273			};
 274		} else {
 275			return {
 276				width : width,
 277				height: height
 278			};
 279		}
 280	},
 281	/**
 282	 * Truncates a string by injecting an ellipsis into the middle.
 283	 * Useful for filenames.
 284	 *
 285	 * @param {String} string
 286	 * @param {Number} [length=30]
 287	 * @param {String} [replacement=…]
 288	 * @return {String} The string, unless length is greater than string.length.
 289	 */
 290	truncate: function( string, length, replacement ) {
 291		length = length || 30;
 292		replacement = replacement || '…';
 293
 294		if ( string.length <= length ) {
 295			return string;
 296		}
 297
 298		return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 );
 299	}
 300});
 301
 302/**
 303 * ========================================================================
 304 * MODELS
 305 * ========================================================================
 306 */
 307/**
 308 * wp.media.attachment
 309 *
 310 * @static
 311 * @param {String} id A string used to identify a model.
 312 * @return {wp.media.model.Attachment}
 313 */
 314media.attachment = function( id ) {
 315	return Attachment.get( id );
 316};
 317
 318/**
 319 * A collection of all attachments that have been fetched from the server.
 320 *
 321 * @static
 322 * @member {wp.media.model.Attachments}
 323 */
 324Attachments.all = new Attachments();
 325
 326/**
 327 * wp.media.query
 328 *
 329 * Shorthand for creating a new Attachments Query.
 330 *
 331 * @param {object} [props]
 332 * @return {wp.media.model.Attachments}
 333 */
 334media.query = function( props ) {
 335	return new Attachments( null, {
 336		props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
 337	});
 338};
 339
 340// Clean up. Prevents mobile browsers caching.
 341$(window).on('unload', function(){
 342	window.wp = null;
 343});
 344
 345
 346/***/ }),
 347
 348/***/ 24:
 349/***/ (function(module, exports) {
 350
 351var $ = Backbone.$,
 352	Attachment;
 353
 354/**
 355 * wp.media.model.Attachment
 356 *
 357 * @memberOf wp.media.model
 358 *
 359 * @class
 360 * @augments Backbone.Model
 361 */
 362Attachment = Backbone.Model.extend(/** @lends wp.media.model.Attachment.prototype */{
 363	/**
 364	 * Triggered when attachment details change
 365	 * Overrides Backbone.Model.sync
 366	 *
 367	 * @param {string} method
 368	 * @param {wp.media.model.Attachment} model
 369	 * @param {Object} [options={}]
 370	 *
 371	 * @return {Promise}
 372	 */
 373	sync: function( method, model, options ) {
 374		// If the attachment does not yet have an `id`, return an instantly
 375		// rejected promise. Otherwise, all of our requests will fail.
 376		if ( _.isUndefined( this.id ) ) {
 377			return $.Deferred().rejectWith( this ).promise();
 378		}
 379
 380		// Overload the `read` request so Attachment.fetch() functions correctly.
 381		if ( 'read' === method ) {
 382			options = options || {};
 383			options.context = this;
 384			options.data = _.extend( options.data || {}, {
 385				action: 'get-attachment',
 386				id: this.id
 387			});
 388			return wp.media.ajax( options );
 389
 390		// Overload the `update` request so properties can be saved.
 391		} else if ( 'update' === method ) {
 392			// If we do not have the necessary nonce, fail immediately.
 393			if ( ! this.get('nonces') || ! this.get('nonces').update ) {
 394				return $.Deferred().rejectWith( this ).promise();
 395			}
 396
 397			options = options || {};
 398			options.context = this;
 399
 400			// Set the action and ID.
 401			options.data = _.extend( options.data || {}, {
 402				action:  'save-attachment',
 403				id:      this.id,
 404				nonce:   this.get('nonces').update,
 405				post_id: wp.media.model.settings.post.id
 406			});
 407
 408			// Record the values of the changed attributes.
 409			if ( model.hasChanged() ) {
 410				options.data.changes = {};
 411
 412				_.each( model.changed, function( value, key ) {
 413					options.data.changes[ key ] = this.get( key );
 414				}, this );
 415			}
 416
 417			return wp.media.ajax( options );
 418
 419		// Overload the `delete` request so attachments can be removed.
 420		// This will permanently delete an attachment.
 421		} else if ( 'delete' === method ) {
 422			options = options || {};
 423
 424			if ( ! options.wait ) {
 425				this.destroyed = true;
 426			}
 427
 428			options.context = this;
 429			options.data = _.extend( options.data || {}, {
 430				action:   'delete-post',
 431				id:       this.id,
 432				_wpnonce: this.get('nonces')['delete']
 433			});
 434
 435			return wp.media.ajax( options ).done( function() {
 436				this.destroyed = true;
 437			}).fail( function() {
 438				this.destroyed = false;
 439			});
 440
 441		// Otherwise, fall back to `Backbone.sync()`.
 442		} else {
 443			/**
 444			 * Call `sync` directly on Backbone.Model
 445			 */
 446			return Backbone.Model.prototype.sync.apply( this, arguments );
 447		}
 448	},
 449	/**
 450	 * Convert date strings into Date objects.
 451	 *
 452	 * @param {Object} resp The raw response object, typically returned by fetch()
 453	 * @return {Object} The modified response object, which is the attributes hash
 454	 *                  to be set on the model.
 455	 */
 456	parse: function( resp ) {
 457		if ( ! resp ) {
 458			return resp;
 459		}
 460
 461		resp.date = new Date( resp.date );
 462		resp.modified = new Date( resp.modified );
 463		return resp;
 464	},
 465	/**
 466	 * @param {Object} data The properties to be saved.
 467	 * @param {Object} options Sync options. e.g. patch, wait, success, error.
 468	 *
 469	 * @this Backbone.Model
 470	 *
 471	 * @return {Promise}
 472	 */
 473	saveCompat: function( data, options ) {
 474		var model = this;
 475
 476		// If we do not have the necessary nonce, fail immediately.
 477		if ( ! this.get('nonces') || ! this.get('nonces').update ) {
 478			return $.Deferred().rejectWith( this ).promise();
 479		}
 480
 481		return wp.media.post( 'save-attachment-compat', _.defaults({
 482			id:      this.id,
 483			nonce:   this.get('nonces').update,
 484			post_id: wp.media.model.settings.post.id
 485		}, data ) ).done( function( resp, status, xhr ) {
 486			model.set( model.parse( resp, xhr ), options );
 487		});
 488	}
 489},/** @lends wp.media.model.Attachment */{
 490	/**
 491	 * Create a new model on the static 'all' attachments collection and return it.
 492	 *
 493	 * @static
 494	 *
 495	 * @param {Object} attrs
 496	 * @return {wp.media.model.Attachment}
 497	 */
 498	create: function( attrs ) {
 499		var Attachments = wp.media.model.Attachments;
 500		return Attachments.all.push( attrs );
 501	},
 502	/**
 503	 * Create a new model on the static 'all' attachments collection and return it.
 504	 *
 505	 * If this function has already been called for the id,
 506	 * it returns the specified attachment.
 507	 *
 508	 * @static
 509	 * @param {string} id A string used to identify a model.
 510	 * @param {Backbone.Model|undefined} attachment
 511	 * @return {wp.media.model.Attachment}
 512	 */
 513	get: _.memoize( function( id, attachment ) {
 514		var Attachments = wp.media.model.Attachments;
 515		return Attachments.all.push( attachment || { id: id } );
 516	})
 517});
 518
 519module.exports = Attachment;
 520
 521
 522/***/ }),
 523
 524/***/ 25:
 525/***/ (function(module, exports) {
 526
 527/**
 528 * wp.media.model.Attachments
 529 *
 530 * A collection of attachments.
 531 *
 532 * This collection has no persistence with the server without supplying
 533 * 'options.props.query = true', which will mirror the collection
 534 * to an Attachments Query collection - @see wp.media.model.Attachments.mirror().
 535 *
 536 * @memberOf wp.media.model
 537 *
 538 * @class
 539 * @augments Backbone.Collection
 540 *
 541 * @param {array}  [models]                Models to initialize with the collection.
 542 * @param {object} [options]               Options hash for the collection.
 543 * @param {string} [options.props]         Options hash for the initial query properties.
 544 * @param {string} [options.props.order]   Initial order (ASC or DESC) for the collection.
 545 * @param {string} [options.props.orderby] Initial attribute key to order the collection by.
 546 * @param {string} [options.props.query]   Whether the collection is linked to an attachments query.
 547 * @param {string} [options.observe]
 548 * @param {string} [options.filters]
 549 *
 550 */
 551var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachments.prototype */{
 552	/**
 553	 * @type {wp.media.model.Attachment}
 554	 */
 555	model: wp.media.model.Attachment,
 556	/**
 557	 * @param {Array} [models=[]] Array of models used to populate the collection.
 558	 * @param {Object} [options={}]
 559	 */
 560	initialize: function( models, options ) {
 561		options = options || {};
 562
 563		this.props   = new Backbone.Model();
 564		this.filters = options.filters || {};
 565
 566		// Bind default `change` events to the `props` model.
 567		this.props.on( 'change', this._changeFilteredProps, this );
 568
 569		this.props.on( 'change:order',   this._changeOrder,   this );
 570		this.props.on( 'change:orderby', this._changeOrderby, this );
 571		this.props.on( 'change:query',   this._changeQuery,   this );
 572
 573		this.props.set( _.defaults( options.props || {} ) );
 574
 575		if ( options.observe ) {
 576			this.observe( options.observe );
 577		}
 578	},
 579	/**
 580	 * Sort the collection when the order attribute changes.
 581	 *
 582	 * @access private
 583	 */
 584	_changeOrder: function() {
 585		if ( this.comparator ) {
 586			this.sort();
 587		}
 588	},
 589	/**
 590	 * Set the default comparator only when the `orderby` property is set.
 591	 *
 592	 * @access private
 593	 *
 594	 * @param {Backbone.Model} model
 595	 * @param {string} orderby
 596	 */
 597	_changeOrderby: function( model, orderby ) {
 598		// If a different comparator is defined, bail.
 599		if ( this.comparator && this.comparator !== Attachments.comparator ) {
 600			return;
 601		}
 602
 603		if ( orderby && 'post__in' !== orderby ) {
 604			this.comparator = Attachments.comparator;
 605		} else {
 606			delete this.comparator;
 607		}
 608	},
 609	/**
 610	 * If the `query` property is set to true, query the server using
 611	 * the `props` values, and sync the results to this collection.
 612	 *
 613	 * @access private
 614	 *
 615	 * @param {Backbone.Model} model
 616	 * @param {Boolean} query
 617	 */
 618	_changeQuery: function( model, query ) {
 619		if ( query ) {
 620			this.props.on( 'change', this._requery, this );
 621			this._requery();
 622		} else {
 623			this.props.off( 'change', this._requery, this );
 624		}
 625	},
 626	/**
 627	 * @access private
 628	 *
 629	 * @param {Backbone.Model} model
 630	 */
 631	_changeFilteredProps: function( model ) {
 632		// If this is a query, updating the collection will be handled by
 633		// `this._requery()`.
 634		if ( this.props.get('query') ) {
 635			return;
 636		}
 637
 638		var changed = _.chain( model.changed ).map( function( t, prop ) {
 639			var filter = Attachments.filters[ prop ],
 640				term = model.get( prop );
 641
 642			if ( ! filter ) {
 643				return;
 644			}
 645
 646			if ( term && ! this.filters[ prop ] ) {
 647				this.filters[ prop ] = filter;
 648			} else if ( ! term && this.filters[ prop ] === filter ) {
 649				delete this.filters[ prop ];
 650			} else {
 651				return;
 652			}
 653
 654			// Record the change.
 655			return true;
 656		}, this ).any().value();
 657
 658		if ( ! changed ) {
 659			return;
 660		}
 661
 662		// If no `Attachments` model is provided to source the searches from,
 663		// then automatically generate a source from the existing models.
 664		if ( ! this._source ) {
 665			this._source = new Attachments( this.models );
 666		}
 667
 668		this.reset( this._source.filter( this.validator, this ) );
 669	},
 670
 671	validateDestroyed: false,
 672	/**
 673	 * Checks whether an attachment is valid.
 674	 *
 675	 * @param {wp.media.model.Attachment} attachment
 676	 * @return {Boolean}
 677	 */
 678	validator: function( attachment ) {
 679
 680		// Filter out contextually created attachments (e.g. headers, logos, etc.).
 681		if (
 682			! _.isUndefined( attachment.attributes.context ) &&
 683			'' !== attachment.attributes.context
 684		) {
 685			return false;
 686		}
 687
 688		if ( ! this.validateDestroyed && attachment.destroyed ) {
 689			return false;
 690		}
 691		return _.all( this.filters, function( filter ) {
 692			return !! filter.call( this, attachment );
 693		}, this );
 694	},
 695	/**
 696	 * Add or remove an attachment to the collection depending on its validity.
 697	 *
 698	 * @param {wp.media.model.Attachment} attachment
 699	 * @param {Object} options
 700	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
 701	 */
 702	validate: function( attachment, options ) {
 703		var valid = this.validator( attachment ),
 704			hasAttachment = !! this.get( attachment.cid );
 705
 706		if ( ! valid && hasAttachment ) {
 707			this.remove( attachment, options );
 708		} else if ( valid && ! hasAttachment ) {
 709			this.add( attachment, options );
 710		}
 711
 712		return this;
 713	},
 714
 715	/**
 716	 * Add or remove all attachments from another collection depending on each one's validity.
 717	 *
 718	 * @param {wp.media.model.Attachments} attachments
 719	 * @param {object} [options={}]
 720	 *
 721	 * @fires wp.media.model.Attachments#reset
 722	 *
 723	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
 724	 */
 725	validateAll: function( attachments, options ) {
 726		options = options || {};
 727
 728		_.each( attachments.models, function( attachment ) {
 729			this.validate( attachment, { silent: true });
 730		}, this );
 731
 732		if ( ! options.silent ) {
 733			this.trigger( 'reset', this, options );
 734		}
 735		return this;
 736	},
 737	/**
 738	 * Start observing another attachments collection change events
 739	 * and replicate them on this collection.
 740	 *
 741	 * @param {wp.media.model.Attachments} The attachments collection to observe.
 742	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
 743	 */
 744	observe: function( attachments ) {
 745		this.observers = this.observers || [];
 746		this.observers.push( attachments );
 747
 748		attachments.on( 'add change remove', this._validateHandler, this );
 749		attachments.on( 'reset', this._validateAllHandler, this );
 750		this.validateAll( attachments );
 751		return this;
 752	},
 753	/**
 754	 * Stop replicating collection change events from another attachments collection.
 755	 *
 756	 * @param {wp.media.model.Attachments} The attachments collection to stop observing.
 757	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
 758	 */
 759	unobserve: function( attachments ) {
 760		if ( attachments ) {
 761			attachments.off( null, null, this );
 762			this.observers = _.without( this.observers, attachments );
 763
 764		} else {
 765			_.each( this.observers, function( attachments ) {
 766				attachments.off( null, null, this );
 767			}, this );
 768			delete this.observers;
 769		}
 770
 771		return this;
 772	},
 773	/**
 774	 * @access private
 775	 *
 776	 * @param {wp.media.model.Attachments} attachment
 777	 * @param {wp.media.model.Attachments} attachments
 778	 * @param {Object} options
 779	 *
 780	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
 781	 */
 782	_validateHandler: function( attachment, attachments, options ) {
 783		// If we're not mirroring this `attachments` collection,
 784		// only retain the `silent` option.
 785		options = attachments === this.mirroring ? options : {
 786			silent: options && options.silent
 787		};
 788
 789		return this.validate( attachment, options );
 790	},
 791	/**
 792	 * @access private
 793	 *
 794	 * @param {wp.media.model.Attachments} attachments
 795	 * @param {Object} options
 796	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
 797	 */
 798	_validateAllHandler: function( attachments, options ) {
 799		return this.validateAll( attachments, options );
 800	},
 801	/**
 802	 * Start mirroring another attachments collection, clearing out any models already
 803	 * in the collection.
 804	 *
 805	 * @param {wp.media.model.Attachments} The attachments collection to mirror.
 806	 * @return {wp.media.model.Attachments} Returns itself to allow chaining.
 807	 */
 808	mirror: function( attachments ) {
 809		if ( this.mirroring && this.mirroring === attachments ) {
 810			return this;
 811		}
 812
 813		this.unmirror();
 814		this.mirroring = attachments;
 815
 816		// Clear the collection silently. A `reset` event will be fired
 817		// when `observe()` calls `validateAll()`.
 818		this.reset( [], { silent: true } );
 819		this.observe( attachments );
 820
 821		// Used for the search results.
 822		this.trigger( 'attachments:received', this );
 823		return this;
 824	},
 825	/**
 826	 * Stop mirroring another attachments collection.
 827	 */
 828	unmirror: function() {
 829		if ( ! this.mirroring ) {
 830			return;
 831		}
 832
 833		this.unobserve( this.mirroring );
 834		delete this.mirroring;
 835	},
 836	/**
 837	 * Retrieve more attachments from the server for the collection.
 838	 *
 839	 * Only works if the collection is mirroring a Query Attachments collection,
 840	 * and forwards to its `more` method. This collection class doesn't have
 841	 * server persistence by itself.
 842	 *
 843	 * @param {object} options
 844	 * @return {Promise}
 845	 */
 846	more: function( options ) {
 847		var deferred = jQuery.Deferred(),
 848			mirroring = this.mirroring,
 849			attachments = this;
 850
 851		if ( ! mirroring || ! mirroring.more ) {
 852			return deferred.resolveWith( this ).promise();
 853		}
 854		/*
 855		 * If we're mirroring another collection, forward `more` to
 856		 * the mirrored collection. Account for a race condition by
 857		 * checking if we're still mirroring that collection when
 858		 * the request resolves.
 859		 */
 860		mirroring.more( options ).done( function() {
 861			if ( this === attachments.mirroring ) {
 862				deferred.resolveWith( this );
 863			}
 864
 865			// Used for the search results.
 866			attachments.trigger( 'attachments:received', this );
 867		});
 868
 869		return deferred.promise();
 870	},
 871	/**
 872	 * Whether there are more attachments that haven't been sync'd from the server
 873	 * that match the collection's query.
 874	 *
 875	 * Only works if the collection is mirroring a Query Attachments collection,
 876	 * and forwards to its `hasMore` method. This collection class doesn't have
 877	 * server persistence by itself.
 878	 *
 879	 * @return {boolean}
 880	 */
 881	hasMore: function() {
 882		return this.mirroring ? this.mirroring.hasMore() : false;
 883	},
 884	/**
 885	 * A custom AJAX-response parser.
 886	 *
 887	 * See trac ticket #24753
 888	 *
 889	 * @param {Object|Array} resp The raw response Object/Array.
 890	 * @param {Object} xhr
 891	 * @return {Array} The array of model attributes to be added to the collection
 892	 */
 893	parse: function( resp, xhr ) {
 894		if ( ! _.isArray( resp ) ) {
 895			resp = [resp];
 896		}
 897
 898		return _.map( resp, function( attrs ) {
 899			var id, attachment, newAttributes;
 900
 901			if ( attrs instanceof Backbone.Model ) {
 902				id = attrs.get( 'id' );
 903				attrs = attrs.attributes;
 904			} else {
 905				id = attrs.id;
 906			}
 907
 908			attachment = wp.media.model.Attachment.get( id );
 909			newAttributes = attachment.parse( attrs, xhr );
 910
 911			if ( ! _.isEqual( attachment.attributes, newAttributes ) ) {
 912				attachment.set( newAttributes );
 913			}
 914
 915			return attachment;
 916		});
 917	},
 918	/**
 919	 * If the collection is a query, create and mirror an Attachments Query collection.
 920	 *
 921	 * @access private
 922	 */
 923	_requery: function( refresh ) {
 924		var props;
 925		if ( this.props.get('query') ) {
 926			props = this.props.toJSON();
 927			props.cache = ( true !== refresh );
 928			this.mirror( wp.media.model.Query.get( props ) );
 929		}
 930	},
 931	/**
 932	 * If this collection is sorted by `menuOrder`, recalculates and saves
 933	 * the menu order to the database.
 934	 *
 935	 * @return {undefined|Promise}
 936	 */
 937	saveMenuOrder: function() {
 938		if ( 'menuOrder' !== this.props.get('orderby') ) {
 939			return;
 940		}
 941
 942		/*
 943		 * Removes any uploading attachments, updates each attachment's
 944		 * menu order, and returns an object with an { id: menuOrder }
 945		 * mapping to pass to the request.
 946		 */
 947		var attachments = this.chain().filter( function( attachment ) {
 948			return ! _.isUndefined( attachment.id );
 949		}).map( function( attachment, index ) {
 950			// Indices start at 1.
 951			index = index + 1;
 952			attachment.set( 'menuOrder', index );
 953			return [ attachment.id, index ];
 954		}).object().value();
 955
 956		if ( _.isEmpty( attachments ) ) {
 957			return;
 958		}
 959
 960		return wp.media.post( 'save-attachment-order', {
 961			nonce:       wp.media.model.settings.post.nonce,
 962			post_id:     wp.media.model.settings.post.id,
 963			attachments: attachments
 964		});
 965	}
 966},/** @lends wp.media.model.Attachments */{
 967	/**
 968	 * A function to compare two attachment models in an attachments collection.
 969	 *
 970	 * Used as the default comparator for instances of wp.media.model.Attachments
 971	 * and its subclasses. @see wp.media.model.Attachments._changeOrderby().
 972	 *
 973	 * @param {Backbone.Model} a
 974	 * @param {Backbone.Model} b
 975	 * @param {Object} options
 976	 * @return {Number} -1 if the first model should come before the second,
 977	 *                   0 if they are of the same rank and
 978	 *                   1 if the first model should come after.
 979	 */
 980	comparator: function( a, b, options ) {
 981		var key   = this.props.get('orderby'),
 982			order = this.props.get('order') || 'DESC',
 983			ac    = a.cid,
 984			bc    = b.cid;
 985
 986		a = a.get( key );
 987		b = b.get( key );
 988
 989		if ( 'date' === key || 'modified' === key ) {
 990			a = a || new Date();
 991			b = b || new Date();
 992		}
 993
 994		// If `options.ties` is set, don't enforce the `cid` tiebreaker.
 995		if ( options && options.ties ) {
 996			ac = bc = null;
 997		}
 998
 999		return ( 'DESC' === order ) ? wp.media.compare( a, b, ac, bc ) : wp.media.compare( b, a, bc, ac );
1000	},
1001	/** @namespace wp.media.model.Attachments.filters */
1002	filters: {
1003		/**
1004		 * @static
1005		 * Note that this client-side searching is *not* equivalent
1006		 * to our server-side searching.
1007		 *
1008		 * @param {wp.media.model.Attachment} attachment
1009		 *
1010		 * @this wp.media.model.Attachments
1011		 *
1012		 * @return {Boolean}
1013		 */
1014		search: function( attachment ) {
1015			if ( ! this.props.get('search') ) {
1016				return true;
1017			}
1018
1019			return _.any(['title','filename','description','caption','name'], function( key ) {
1020				var value = attachment.get( key );
1021				return value && -1 !== value.search( this.props.get('search') );
1022			}, this );
1023		},
1024		/**
1025		 * @static
1026		 * @param {wp.media.model.Attachment} attachment
1027		 *
1028		 * @this wp.media.model.Attachments
1029		 *
1030		 * @return {Boolean}
1031		 */
1032		type: function( attachment ) {
1033			var type = this.props.get('type'), atts = attachment.toJSON(), mime, found;
1034
1035			if ( ! type || ( _.isArray( type ) && ! type.length ) ) {
1036				return true;
1037			}
1038
1039			mime = atts.mime || ( atts.file && atts.file.type ) || '';
1040
1041			if ( _.isArray( type ) ) {
1042				found = _.find( type, function (t) {
1043					return -1 !== mime.indexOf( t );
1044				} );
1045			} else {
1046				found = -1 !== mime.indexOf( type );
1047			}
1048
1049			return found;
1050		},
1051		/**
1052		 * @static
1053		 * @param {wp.media.model.Attachment} attachment
1054		 *
1055		 * @this wp.media.model.Attachments
1056		 *
1057		 * @return {Boolean}
1058		 */
1059		uploadedTo: function( attachment ) {
1060			var uploadedTo = this.props.get('uploadedTo');
1061			if ( _.isUndefined( uploadedTo ) ) {
1062				return true;
1063			}
1064
1065			return uploadedTo === attachment.get('uploadedTo');
1066		},
1067		/**
1068		 * @static
1069		 * @param {wp.media.model.Attachment} attachment
1070		 *
1071		 * @this wp.media.model.Attachments
1072		 *
1073		 * @return {Boolean}
1074		 */
1075		status: function( attachment ) {
1076			var status = this.props.get('status');
1077			if ( _.isUndefined( status ) ) {
1078				return true;
1079			}
1080
1081			return status === attachment.get('status');
1082		}
1083	}
1084});
1085
1086module.exports = Attachments;
1087
1088
1089/***/ }),
1090
1091/***/ 26:
1092/***/ (function(module, exports) {
1093
1094var Attachments = wp.media.model.Attachments,
1095	Query;
1096
1097/**
1098 * wp.media.model.Query
1099 *
1100 * A collection of attachments that match the supplied query arguments.
1101 *
1102 * Note: Do NOT change this.args after the query has been initialized.
1103 *       Things will break.
1104 *
1105 * @memberOf wp.media.model
1106 *
1107 * @class
1108 * @augments wp.media.model.Attachments
1109 * @augments Backbone.Collection
1110 *
1111 * @param {array}  [models]                      Models to initialize with the collection.
1112 * @param {object} [options]                     Options hash.
1113 * @param {object} [options.args]                Attachments query arguments.
1114 * @param {object} [options.args.posts_per_page]
1115 */
1116Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{
1117	/**
1118	 * @param {array}  [models=[]]  Array of initial models to populate the collection.
1119	 * @param {object} [options={}]
1120	 */
1121	initialize: function( models, options ) {
1122		var allowed;
1123
1124		options = options || {};
1125		Attachments.prototype.initialize.apply( this, arguments );
1126
1127		this.args     = options.args;
1128		this._hasMore = true;
1129		this.created  = new Date();
1130
1131		this.filters.order = function( attachment ) {
1132			var orderby = this.props.get('orderby'),
1133				order = this.props.get('order');
1134
1135			if ( ! this.comparator ) {
1136				return true;
1137			}
1138
1139			/*
1140			 * We want any items that can be placed before the last
1141			 * item in the set. If we add any items after the last
1142			 * item, then we can't guarantee the set is complete.
1143			 */
1144			if ( this.length ) {
1145				return 1 !== this.comparator( attachment, this.last(), { ties: true });
1146
1147			/*
1148			 * Handle the case where there are no items yet and
1149			 * we're sorting for recent items. In that case, we want
1150			 * changes that occurred after we created the query.
1151			 */
1152			} else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
1153				return attachment.get( orderby ) >= this.created;
1154
1155			// If we're sorting by menu order and we have no items,
1156			// accept any items that have the default menu order (0).
1157			} else if ( 'ASC' === order && 'menuOrder' === orderby ) {
1158				return attachment.get( orderby ) === 0;
1159			}
1160
1161			// Otherwise, we don't want any items yet.
1162			return false;
1163		};
1164
1165		/*
1166		 * Observe the central `wp.Uploader.queue` collection to watch for
1167		 * new matches for the query.
1168		 *
1169		 * Only observe when a limited number of query args are set. There
1170		 * are no filters for other properties, so observing will result in
1171		 * false positives in those queries.
1172		 */
1173		allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent', 'author' ];
1174		if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
1175			this.observe( wp.Uploader.queue );
1176		}
1177	},
1178	/**
1179	 * Whether there are more attachments that haven't been sync'd from the server
1180	 * that match the collection's query.
1181	 *
1182	 * @return {boolean}
1183	 */
1184	hasMore: function() {
1185		return this._hasMore;
1186	},
1187	/**
1188	 * Fetch more attachments from the server for the collection.
1189	 *
1190	 * @param   {object}  [options={}]
1191	 * @return {Promise}
1192	 */
1193	more: function( options ) {
1194		var query = this;
1195
1196		// If there is already a request pending, return early with the Deferred object.
1197		if ( this._more && 'pending' === this._more.state() ) {
1198			return this._more;
1199		}
1200
1201		if ( ! this.hasMore() ) {
1202			return jQuery.Deferred().resolveWith( this ).promise();
1203		}
1204
1205		options = options || {};
1206		options.remove = false;
1207
1208		return this._more = this.fetch( options ).done( function( resp ) {
1209			if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
1210				query._hasMore = false;
1211			}
1212		});
1213	},
1214	/**
1215	 * Overrides Backbone.Collection.sync
1216	 * Overrides wp.media.model.Attachments.sync
1217	 *
1218	 * @param {String} method
1219	 * @param {Backbone.Model} model
1220	 * @param {Object} [options={}]
1221	 * @return {Promise}
1222	 */
1223	sync: function( method, model, options ) {
1224		var args, fallback;
1225
1226		// Overload the read method so Attachment.fetch() functions correctly.
1227		if ( 'read' === method ) {
1228			options = options || {};
1229			options.context = this;
1230			options.data = _.extend( options.data || {}, {
1231				action:  'query-attachments',
1232				post_id: wp.media.model.settings.post.id
1233			});
1234
1235			// Clone the args so manipulation is non-destructive.
1236			args = _.clone( this.args );
1237
1238			// Determine which page to query.
1239			if ( -1 !== args.posts_per_page ) {
1240				args.paged = Math.round( this.length / args.posts_per_page ) + 1;
1241			}
1242
1243			options.data.query = args;
1244			return wp.media.ajax( options );
1245
1246		// Otherwise, fall back to `Backbone.sync()`.
1247		} else {
1248			/**
1249			 * Call wp.media.model.Attachments.sync or Backbone.sync
1250			 */
1251			fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
1252			return fallback.sync.apply( this, arguments );
1253		}
1254	}
1255}, /** @lends wp.media.model.Query */{
1256	/**
1257	 * @readonly
1258	 */
1259	defaultProps: {
1260		orderby: 'date',
1261		order:   'DESC'
1262	},
1263	/**
1264	 * @readonly
1265	 */
1266	defaultArgs: {
1267		posts_per_page: 40
1268	},
1269	/**
1270	 * @readonly
1271	 */
1272	orderby: {
1273		allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
1274		/**
1275		 * A map of JavaScript orderby values to their WP_Query equivalents.
1276		 * @type {Object}
1277		 */
1278		valuemap: {
1279			'id':         'ID',
1280			'uploadedTo': 'parent',
1281			'menuOrder':  'menu_order ID'
1282		}
1283	},
1284	/**
1285	 * A map of JavaScript query properties to their WP_Query equivalents.
1286	 *
1287	 * @readonly
1288	 */
1289	propmap: {
1290		'search':		's',
1291		'type':			'post_mime_type',
1292		'perPage':		'posts_per_page',
1293		'menuOrder':	'menu_order',
1294		'uploadedTo':	'post_parent',
1295		'status':		'post_status',
1296		'include':		'post__in',
1297		'exclude':		'post__not_in',
1298		'author':		'author'
1299	},
1300	/**
1301	 * Creates and returns an Attachments Query collection given the properties.
1302	 *
1303	 * Caches query objects and reuses where possible.
1304	 *
1305	 * @static
1306	 * @method
1307	 *
1308	 * @param {object} [props]
1309	 * @param {Object} [props.cache=true]   Whether to use the query cache or not.
1310	 * @param {Object} [props.order]
1311	 * @param {Object} [props.orderby]
1312	 * @param {Object} [props.include]
1313	 * @param {Object} [props.exclude]
1314	 * @param {Object} [props.s]
1315	 * @param {Object} [props.post_mime_type]
1316	 * @param {Object} [props.posts_per_page]
1317	 * @param {Object} [props.menu_order]
1318	 * @param {Object} [props.post_parent]
1319	 * @param {Object} [props.post_status]
1320	 * @param {Object} [props.author]
1321	 * @param {Object} [options]
1322	 *
1323	 * @return {wp.media.model.Query} A new Attachments Query collection.
1324	 */
1325	get: (function(){
1326		/**
1327		 * @static
1328		 * @type Array
1329		 */
1330		var queries = [];
1331
1332		/**
1333		 * @return {Query}
1334		 */
1335		return function( props, options ) {
1336			var args     = {},
1337				orderby  = Query.orderby,
1338				defaults = Query.defaultProps,
1339				query,
1340				cache    = !! props.cache || _.isUndefined( props.cache );
1341
1342			// Remove the `query` property. This isn't linked to a query,
1343			// this *is* the query.
1344			delete props.query;
1345			delete props.cache;
1346
1347			// Fill default args.
1348			_.defaults( props, defaults );
1349
1350			// Normalize the order.
1351			props.order = props.order.toUpperCase();
1352			if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
1353				props.order = defaults.order.toUpperCase();
1354			}
1355
1356			// Ensure we have a valid orderby value.
1357			if ( ! _.contains( orderby.allowed, props.orderby ) ) {
1358				props.orderby = defaults.orderby;
1359			}
1360
1361			_.each( [ 'include', 'exclude' ], function( prop ) {
1362				if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
1363					props[ prop ] = [ props[ prop ] ];
1364				}
1365			} );
1366
1367			// Generate the query `args` object.
1368			// Correct any differing property names.
1369			_.each( props, function( value, prop ) {
1370				if ( _.isNull( value ) ) {
1371					return;
1372				}
1373
1374				args[ Query.propmap[ prop ] || prop ] = value;
1375			});
1376
1377			// Fill any other default query args.
1378			_.defaults( args, Query.defaultArgs );
1379
1380			// `props.orderby` does not always map directly to `args.orderby`.
1381			// Substitute exceptions specified in orderby.keymap.
1382			args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
1383
1384			// Search the query cache for a matching query.
1385			if ( cache ) {
1386				query = _.find( queries, function( query ) {
1387					return _.isEqual( query.args, args );
1388				});
1389			} else {
1390				queries = [];
1391			}
1392
1393			// Otherwise, create a new query and add it to the cache.
1394			if ( ! query ) {
1395				query = new Query( [], _.extend( options || {}, {
1396					props: props,
1397					args:  args
1398				} ) );
1399				queries.push( query );
1400			}
1401
1402			return query;
1403		};
1404	}())
1405});
1406
1407module.exports = Query;
1408
1409
1410/***/ }),
1411
1412/***/ 27:
1413/***/ (function(module, exports) {
1414
1415/**
1416 * wp.media.model.PostImage
1417 *
1418 * An instance of an image that's been embedded into a post.
1419 *
1420 * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.
1421 *
1422 * @memberOf wp.media.model
1423 *
1424 * @class
1425 * @augments Backbone.Model
1426 *
1427 * @param {int} [attributes]               Initial model attributes.
1428 * @param {int} [attributes.attachment_id] ID of the attachment.
1429 **/
1430var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{
1431
1432	initialize: function( attributes ) {
1433		var Attachment = wp.media.model.Attachment;
1434		this.attachment = false;
1435
1436		if ( attributes.attachment_id ) {
1437			this.attachment = Attachment.get( attributes.attachment_id );
1438			if ( this.attachment.get( 'url' ) ) {
1439				this.dfd = jQuery.Deferred();
1440				this.dfd.resolve();
1441			} else {
1442				this.dfd = this.attachment.fetch();
1443			}
1444			this.bindAttachmentListeners();
1445		}
1446
1447		// Keep URL in sync with changes to the type of link.
1448		this.on( 'change:link', this.updateLinkUrl, this );
1449		this.on( 'change:size', this.updateSize, this );
1450
1451		this.setLinkTypeFromUrl();
1452		this.setAspectRatio();
1453
1454		this.set( 'originalUrl', attributes.url );
1455	},
1456
1457	bindAttachmentListeners: function() {
1458		this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
1459		this.listenTo( this.attachment, 'sync', this.setAspectRatio );
1460		this.listenTo( this.attachment, 'change', this.updateSize );
1461	},
1462
1463	changeAttachment: function( attachment, props ) {
1464		this.stopListening( this.attachment );
1465		this.attachment = attachment;
1466		this.bindAttachmentListeners();
1467
1468		this.set( 'attachment_id', this.attachment.get( 'id' ) );
1469		this.set( 'caption', this.attachment.get( 'caption' ) );
1470		this.set( 'alt', this.attachment.get( 'alt' ) );
1471		this.set( 'size', props.get( 'size' ) );
1472		this.set( 'align', props.get( 'align' ) );
1473		this.set( 'link', props.get( 'link' ) );
1474		this.updateLinkUrl();
1475		this.updateSize();
1476	},
1477
1478	setLinkTypeFromUrl: function() {
1479		var linkUrl = this.get( 'linkUrl' ),
1480			type;
1481
1482		if ( ! linkUrl ) {
1483			this.set( 'link', 'none' );
1484			return;
1485		}
1486
1487		// Default to custom if there is a linkUrl.
1488		type = 'custom';
1489
1490		if ( this.attachment ) {
1491			if ( this.attachment.get( 'url' ) === linkUrl ) {
1492				type = 'file';
1493			} else if ( this.attachment.get( 'link' ) === linkUrl ) {
1494				type = 'post';
1495			}
1496		} else {
1497			if ( this.get( 'url' ) === linkUrl ) {
1498				type = 'file';
1499			}
1500		}
1501
1502		this.set( 'link', type );
1503	},
1504
1505	updateLinkUrl: function() {
1506		var link = this.get( 'link' ),
1507			url;
1508
1509		switch( link ) {
1510			case 'file':
1511				if ( this.attachment ) {
1512					url = this.attachment.get( 'url' );
1513				} else {
1514					url = this.get( 'url' );
1515				}
1516				this.set( 'linkUrl', url );
1517				break;
1518			case 'post':
1519				this.set( 'linkUrl', this.attachment.get( 'link' ) );
1520				break;
1521			case 'none':
1522				this.set( 'linkUrl', '' );
1523				break;
1524		}
1525	},
1526
1527	updateSize: function() {
1528		var size;
1529
1530		if ( ! this.attachment ) {
1531			return;
1532		}
1533
1534		if ( this.get( 'size' ) === 'custom' ) {
1535			this.set( 'width', this.get( 'customWidth' ) );
1536			this.set( 'height', this.get( 'customHeight' ) );
1537			this.set( 'url', this.get( 'originalUrl' ) );
1538			return;
1539		}
1540
1541		size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
1542
1543		if ( ! size ) {
1544			return;
1545		}
1546
1547		this.set( 'url', size.url );
1548		this.set( 'width', size.width );
1549		this.set( 'height', size.height );
1550	},
1551
1552	setAspectRatio: function() {
1553		var full;
1554
1555		if ( this.attachment && this.attachment.get( 'sizes' ) ) {
1556			full = this.attachment.get( 'sizes' ).full;
1557
1558			if ( full ) {
1559				this.set( 'aspectRatio', full.width / full.height );
1560				return;
1561			}
1562		}
1563
1564		this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );
1565	}
1566});
1567
1568module.exports = PostImage;
1569
1570
1571/***/ }),
1572
1573/***/ 28:
1574/***/ (function(module, exports) {
1575
1576var Attachments = wp.media.model.Attachments,
1577	Selection;
1578
1579/**
1580 * wp.media.model.Selection
1581 *
1582 * A selection of attachments.
1583 *
1584 * @memberOf wp.media.model
1585 *
1586 * @class
1587 * @augments wp.media.model.Attachments
1588 * @augments Backbone.Collection
1589 */
1590Selection = Attachments.extend(/** @lends wp.media.model.Selection.prototype */{
1591	/**
1592	 * Refresh the `single` model whenever the selection changes.
1593	 * Binds `single` instead of using the context argument to ensure
1594	 * it receives no parameters.
1595	 *
1596	 * @param {Array} [models=[]] Array of models used to populate the collection.
1597	 * @param {Object} [options={}]
1598	 */
1599	initialize: function( models, options ) {
1600		/**
1601		 * call 'initialize' directly on the parent class
1602		 */
1603		Attachments.prototype.initialize.apply( this, arguments );
1604		this.multiple = options && options.multiple;
1605
1606		this.on( 'add remove reset', _.bind( this.single, this, false ) );
1607	},
1608
1609	/**
1610	 * If the workflow does not support multi-select, clear out the selection
1611	 * before adding a new attachment to it.
1612	 *
1613	 * @param {Array} models
1614	 * @param {Object} options
1615	 * @return {wp.media.model.Attachment[]}
1616	 */
1617	add: function( models, options ) {
1618		if ( ! this.multiple ) {
1619			this.remove( this.models );
1620		}
1621		/**
1622		 * call 'add' directly on the parent class
1623		 */
1624		return Attachments.prototype.add.call( this, models, options );
1625	},
1626
1627	/**
1628	 * Fired when toggling (clicking on) an attachment in the modal.
1629	 *
1630	 * @param {undefined|boolean|wp.media.model.Attachment} model
1631	 *
1632	 * @fires wp.media.model.Selection#selection:single
1633	 * @fires wp.media.model.Selection#selection:unsingle
1634	 *
1635	 * @return {Backbone.Model}
1636	 */
1637	single: function( model ) {
1638		var previous = this._single;
1639
1640		// If a `model` is provided, use it as the single model.
1641		if ( model ) {
1642			this._single = model;
1643		}
1644		// If the single model isn't in the selection, remove it.
1645		if ( this._single && ! this.get( this._single.cid ) ) {
1646			delete this._single;
1647		}
1648
1649		this._single = this._single || this.last();
1650
1651		// If single has changed, fire an event.
1652		if ( this._single !== previous ) {
1653			if ( previous ) {
1654				previous.trigger( 'selection:unsingle', previous, this );
1655
1656				// If the model was already removed, trigger the collection
1657				// event manually.
1658				if ( ! this.get( previous.cid ) ) {
1659					this.trigger( 'selection:unsingle', previous, this );
1660				}
1661			}
1662			if ( this._single ) {
1663				this._single.trigger( 'selection:single', this._single, this );
1664			}
1665		}
1666
1667		// Return the single model, or the last model as a fallback.
1668		return this._single;
1669	}
1670});
1671
1672module.exports = Selection;
1673
1674
1675/***/ })
1676
1677/******/ });