X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/fa11948979fd6a4ea5705dc613b239699a459db3..0f684a09c03e547d3132bddc970cbeafcd1102f4:/wp-includes/js/media-models.js diff --git a/wp-includes/js/media-models.js b/wp-includes/js/media-models.js index 78489d07..1dd71968 100644 --- a/wp-includes/js/media-models.js +++ b/wp-includes/js/media-models.js @@ -1,7 +1,8 @@ +/* global _wpMediaModelsL10n:false */ window.wp = window.wp || {}; (function($){ - var Attachment, Attachments, Query, compare, l10n, media; + var Attachment, Attachments, Query, PostImage, compare, l10n, media; /** * wp.media( attributes ) @@ -11,26 +12,36 @@ window.wp = window.wp || {}; * Does nothing if the controllers do not exist. * * @param {object} attributes The properties passed to the main media controller. - * @return {object} A media workflow. + * @return {wp.media.view.MediaFrame} A media workflow. */ media = wp.media = function( attributes ) { var MediaFrame = media.view.MediaFrame, frame; - if ( ! MediaFrame ) + if ( ! MediaFrame ) { return; + } attributes = _.defaults( attributes || {}, { frame: 'select' }); - if ( 'select' === attributes.frame && MediaFrame.Select ) + if ( 'select' === attributes.frame && MediaFrame.Select ) { frame = new MediaFrame.Select( attributes ); - else if ( 'post' === attributes.frame && MediaFrame.Post ) + } else if ( 'post' === attributes.frame && MediaFrame.Post ) { frame = new MediaFrame.Post( attributes ); + } else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) { + frame = new MediaFrame.ImageDetails( attributes ); + } else if ( 'audio' === attributes.frame && MediaFrame.AudioDetails ) { + frame = new MediaFrame.AudioDetails( attributes ); + } else if ( 'video' === attributes.frame && MediaFrame.VideoDetails ) { + frame = new MediaFrame.VideoDetails( attributes ); + } delete attributes.frame; + media.frame = frame; + return frame; }; @@ -61,10 +72,11 @@ window.wp = window.wp || {}; * 1: b should come before a. */ compare = function( a, b, ac, bc ) { - if ( _.isEqual( a, b ) ) + if ( _.isEqual( a, b ) ) { return ac === bc ? 0 : (ac > bc ? -1 : 1); - else + } else { return a > b ? -1 : 1; + } }; _.extend( media, { @@ -73,6 +85,8 @@ window.wp = window.wp || {}; * * Fetches a template by id. * See wp.template() in `wp-includes/js/wp-util.js`. + * + * @borrows wp.template as template */ template: wp.template, @@ -81,6 +95,8 @@ window.wp = window.wp || {}; * * Sends a POST request to WordPress. * See wp.ajax.post() in `wp-includes/js/wp-util.js`. + * + * @borrows wp.ajax.post as post */ post: wp.ajax.post, @@ -89,10 +105,17 @@ window.wp = window.wp || {}; * * Sends an XHR request to WordPress. * See wp.ajax.send() in `wp-includes/js/wp-util.js`. + * + * @borrows wp.ajax.send as ajax */ ajax: wp.ajax.send, - // Scales a set of dimensions to fit within bounding dimensions. + /** + * Scales a set of dimensions to fit within bounding dimensions. + * + * @param {Object} dimensions + * @returns {Object} + */ fit: function( dimensions ) { var width = dimensions.width, height = dimensions.height, @@ -130,43 +153,66 @@ window.wp = window.wp || {}; }; } }, - - // Truncates a string by injecting an ellipsis into the middle. - // Useful for filenames. + /** + * Truncates a string by injecting an ellipsis into the middle. + * Useful for filenames. + * + * @param {String} string + * @param {Number} [length=30] + * @param {String} [replacement=…] + * @returns {String} The string, unless length is greater than string.length. + */ truncate: function( string, length, replacement ) { length = length || 30; replacement = replacement || '…'; - if ( string.length <= length ) + if ( string.length <= length ) { return string; + } return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 ); } }); - /** * ======================================================================== * MODELS * ======================================================================== */ - - /** - * wp.media.attachment - */ - media.attachment = function( id ) { + /** + * wp.media.attachment + * + * @static + * @param {String} id A string used to identify a model. + * @returns {wp.media.model.Attachment} + */ + media.attachment = function( id ) { return Attachment.get( id ); - }; + }; /** * wp.media.model.Attachment + * + * @constructor + * @augments Backbone.Model */ Attachment = media.model.Attachment = Backbone.Model.extend({ + /** + * Triggered when attachment details change + * Overrides Backbone.Model.sync + * + * @param {string} method + * @param {wp.media.model.Attachment} model + * @param {Object} [options={}] + * + * @returns {Promise} + */ sync: function( method, model, options ) { // If the attachment does not yet have an `id`, return an instantly // rejected promise. Otherwise, all of our requests will fail. - if ( _.isUndefined( this.id ) ) + if ( _.isUndefined( this.id ) ) { return $.Deferred().rejectWith( this ).promise(); + } // Overload the `read` request so Attachment.fetch() functions correctly. if ( 'read' === method ) { @@ -181,8 +227,9 @@ window.wp = window.wp || {}; // Overload the `update` request so properties can be saved. } else if ( 'update' === method ) { // If we do not have the necessary nonce, fail immeditately. - if ( ! this.get('nonces') || ! this.get('nonces').update ) + if ( ! this.get('nonces') || ! this.get('nonces').update ) { return $.Deferred().rejectWith( this ).promise(); + } options = options || {}; options.context = this; @@ -211,8 +258,9 @@ window.wp = window.wp || {}; } else if ( 'delete' === method ) { options = options || {}; - if ( ! options.wait ) + if ( ! options.wait ) { this.destroyed = true; + } options.context = this; options.data = _.extend( options.data || {}, { @@ -229,26 +277,43 @@ window.wp = window.wp || {}; // Otherwise, fall back to `Backbone.sync()`. } else { + /** + * Call `sync` directly on Backbone.Model + */ return Backbone.Model.prototype.sync.apply( this, arguments ); } }, - - parse: function( resp, xhr ) { - if ( ! resp ) + /** + * Convert date strings into Date objects. + * + * @param {Object} resp The raw response object, typically returned by fetch() + * @returns {Object} The modified response object, which is the attributes hash + * to be set on the model. + */ + parse: function( resp ) { + if ( ! resp ) { return resp; + } - // Convert date strings into Date objects. resp.date = new Date( resp.date ); resp.modified = new Date( resp.modified ); return resp; }, - + /** + * @param {Object} data The properties to be saved. + * @param {Object} options Sync options. e.g. patch, wait, success, error. + * + * @this Backbone.Model + * + * @returns {Promise} + */ saveCompat: function( data, options ) { var model = this; // If we do not have the necessary nonce, fail immeditately. - if ( ! this.get('nonces') || ! this.get('nonces').update ) + if ( ! this.get('nonces') || ! this.get('nonces').update ) { return $.Deferred().rejectWith( this ).promise(); + } return media.post( 'save-attachment-compat', _.defaults({ id: this.id, @@ -259,21 +324,187 @@ window.wp = window.wp || {}; }); } }, { + /** + * Add a model to the end of the static 'all' collection and return it. + * + * @static + * @param {Object} attrs + * @returns {wp.media.model.Attachment} + */ create: function( attrs ) { return Attachments.all.push( attrs ); }, - + /** + * Retrieve a model, or add it to the end of the static 'all' collection before returning it. + * + * @static + * @param {string} id A string used to identify a model. + * @param {Backbone.Model|undefined} attachment + * @returns {wp.media.model.Attachment} + */ get: _.memoize( function( id, attachment ) { return Attachments.all.push( attachment || { id: id } ); }) }); + /** + * wp.media.model.PostImage + * + * @constructor + * @augments Backbone.Model + **/ + PostImage = media.model.PostImage = Backbone.Model.extend({ + + initialize: function( attributes ) { + this.attachment = false; + + if ( attributes.attachment_id ) { + this.attachment = Attachment.get( attributes.attachment_id ); + if ( this.attachment.get( 'url' ) ) { + this.dfd = $.Deferred(); + this.dfd.resolve(); + } else { + this.dfd = this.attachment.fetch(); + } + this.bindAttachmentListeners(); + } + + // keep url in sync with changes to the type of link + this.on( 'change:link', this.updateLinkUrl, this ); + this.on( 'change:size', this.updateSize, this ); + + this.setLinkTypeFromUrl(); + this.setAspectRatio(); + + this.set( 'originalUrl', attributes.url ); + }, + + bindAttachmentListeners: function() { + this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl ); + this.listenTo( this.attachment, 'sync', this.setAspectRatio ); + this.listenTo( this.attachment, 'change', this.updateSize ); + }, + + changeAttachment: function( attachment, props ) { + this.stopListening( this.attachment ); + this.attachment = attachment; + this.bindAttachmentListeners(); + + this.set( 'attachment_id', this.attachment.get( 'id' ) ); + this.set( 'caption', this.attachment.get( 'caption' ) ); + this.set( 'alt', this.attachment.get( 'alt' ) ); + this.set( 'size', props.get( 'size' ) ); + this.set( 'align', props.get( 'align' ) ); + this.set( 'link', props.get( 'link' ) ); + this.updateLinkUrl(); + this.updateSize(); + }, + + setLinkTypeFromUrl: function() { + var linkUrl = this.get( 'linkUrl' ), + type; + + if ( ! linkUrl ) { + this.set( 'link', 'none' ); + return; + } + + // default to custom if there is a linkUrl + type = 'custom'; + + if ( this.attachment ) { + if ( this.attachment.get( 'url' ) === linkUrl ) { + type = 'file'; + } else if ( this.attachment.get( 'link' ) === linkUrl ) { + type = 'post'; + } + } else { + if ( this.get( 'url' ) === linkUrl ) { + type = 'file'; + } + } + + this.set( 'link', type ); + }, + + updateLinkUrl: function() { + var link = this.get( 'link' ), + url; + + switch( link ) { + case 'file': + if ( this.attachment ) { + url = this.attachment.get( 'url' ); + } else { + url = this.get( 'url' ); + } + this.set( 'linkUrl', url ); + break; + case 'post': + this.set( 'linkUrl', this.attachment.get( 'link' ) ); + break; + case 'none': + this.set( 'linkUrl', '' ); + break; + } + }, + + updateSize: function() { + var size; + + if ( ! this.attachment ) { + return; + } + + if ( this.get( 'size' ) === 'custom' ) { + this.set( 'width', this.get( 'customWidth' ) ); + this.set( 'height', this.get( 'customHeight' ) ); + this.set( 'url', this.get( 'originalUrl' ) ); + return; + } + + size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ]; + + if ( ! size ) { + return; + } + + this.set( 'url', size.url ); + this.set( 'width', size.width ); + this.set( 'height', size.height ); + }, + + setAspectRatio: function() { + var full; + + if ( this.attachment && this.attachment.get( 'sizes' ) ) { + full = this.attachment.get( 'sizes' ).full; + + if ( full ) { + this.set( 'aspectRatio', full.width / full.height ); + return; + } + } + + this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) ); + } + }); + /** * wp.media.model.Attachments + * + * @constructor + * @augments Backbone.Collection */ Attachments = media.model.Attachments = Backbone.Collection.extend({ + /** + * @type {wp.media.model.Attachment} + */ model: Attachment, - + /** + * @param {Array} [models=[]] Array of models used to populate the collection. + * @param {Object} [options={}] + */ initialize: function( models, options ) { options = options || {}; @@ -291,30 +522,49 @@ window.wp = window.wp || {}; this.props.set( _.defaults( options.props || {} ) ); // Observe another `Attachments` collection if one is provided. - if ( options.observe ) + if ( options.observe ) { this.observe( options.observe ); + } }, - - // Automatically sort the collection when the order changes. - _changeOrder: function( model, order ) { - if ( this.comparator ) + /** + * Automatically sort the collection when the order changes. + * + * @access private + */ + _changeOrder: function() { + if ( this.comparator ) { this.sort(); + } }, - - // Set the default comparator only when the `orderby` property is set. + /** + * Set the default comparator only when the `orderby` property is set. + * + * @access private + * + * @param {Backbone.Model} model + * @param {string} orderby + */ _changeOrderby: function( model, orderby ) { // If a different comparator is defined, bail. - if ( this.comparator && this.comparator !== Attachments.comparator ) + if ( this.comparator && this.comparator !== Attachments.comparator ) { return; + } - if ( orderby && 'post__in' !== orderby ) + if ( orderby && 'post__in' !== orderby ) { this.comparator = Attachments.comparator; - else + } else { delete this.comparator; + } }, - - // If the `query` property is set to true, query the server using - // the `props` values, and sync the results to this collection. + /** + * If the `query` property is set to true, query the server using + * the `props` values, and sync the results to this collection. + * + * @access private + * + * @param {Backbone.Model} model + * @param {Boolean} query + */ _changeQuery: function( model, query ) { if ( query ) { this.props.on( 'change', this._requery, this ); @@ -323,65 +573,91 @@ window.wp = window.wp || {}; this.props.off( 'change', this._requery, this ); } }, - - _changeFilteredProps: function( model, options ) { + /** + * @access private + * + * @param {Backbone.Model} model + */ + _changeFilteredProps: function( model ) { // If this is a query, updating the collection will be handled by // `this._requery()`. - if ( this.props.get('query') ) + if ( this.props.get('query') ) { return; + } var changed = _.chain( model.changed ).map( function( t, prop ) { var filter = Attachments.filters[ prop ], term = model.get( prop ); - if ( ! filter ) + if ( ! filter ) { return; + } - if ( term && ! this.filters[ prop ] ) + if ( term && ! this.filters[ prop ] ) { this.filters[ prop ] = filter; - else if ( ! term && this.filters[ prop ] === filter ) + } else if ( ! term && this.filters[ prop ] === filter ) { delete this.filters[ prop ]; - else + } else { return; + } // Record the change. return true; }, this ).any().value(); - if ( ! changed ) + if ( ! changed ) { return; + } // If no `Attachments` model is provided to source the searches // from, then automatically generate a source from the existing // models. - if ( ! this._source ) + if ( ! this._source ) { this._source = new Attachments( this.models ); + } this.reset( this._source.filter( this.validator, this ) ); }, validateDestroyed: false, - + /** + * @param {wp.media.model.Attachment} attachment + * @returns {Boolean} + */ validator: function( attachment ) { - if ( ! this.validateDestroyed && attachment.destroyed ) + if ( ! this.validateDestroyed && attachment.destroyed ) { return false; - return _.all( this.filters, function( filter, key ) { + } + return _.all( this.filters, function( filter ) { return !! filter.call( this, attachment ); }, this ); }, - + /** + * @param {wp.media.model.Attachment} attachment + * @param {Object} options + * @returns {wp.media.model.Attachments} Returns itself to allow chaining + */ validate: function( attachment, options ) { var valid = this.validator( attachment ), hasAttachment = !! this.get( attachment.cid ); - if ( ! valid && hasAttachment ) + if ( ! valid && hasAttachment ) { this.remove( attachment, options ); - else if ( valid && ! hasAttachment ) + } else if ( valid && ! hasAttachment ) { this.add( attachment, options ); + } return this; }, + /** + * @param {wp.media.model.Attachments} attachments + * @param {object} [options={}] + * + * @fires wp.media.model.Attachments#reset + * + * @returns {wp.media.model.Attachments} Returns itself to allow chaining + */ validateAll: function( attachments, options ) { options = options || {}; @@ -389,12 +665,15 @@ window.wp = window.wp || {}; this.validate( attachment, { silent: true }); }, this ); - if ( ! options.silent ) + if ( ! options.silent ) { this.trigger( 'reset', this, options ); - + } return this; }, - + /** + * @param {wp.media.model.Attachments} attachments + * @returns {wp.media.model.Attachments} Returns itself to allow chaining + */ observe: function( attachments ) { this.observers = this.observers || []; this.observers.push( attachments ); @@ -404,7 +683,10 @@ window.wp = window.wp || {}; this.validateAll( attachments ); return this; }, - + /** + * @param {wp.media.model.Attachments} attachments + * @returns {wp.media.model.Attachments} Returns itself to allow chaining + */ unobserve: function( attachments ) { if ( attachments ) { attachments.off( null, null, this ); @@ -419,7 +701,15 @@ window.wp = window.wp || {}; return this; }, - + /** + * @access private + * + * @param {wp.media.model.Attachments} attachment + * @param {wp.media.model.Attachments} attachments + * @param {Object} options + * + * @returns {wp.media.model.Attachments} Returns itself to allow chaining + */ _validateHandler: function( attachment, attachments, options ) { // If we're not mirroring this `attachments` collection, // only retain the `silent` option. @@ -429,14 +719,24 @@ window.wp = window.wp || {}; return this.validate( attachment, options ); }, - + /** + * @access private + * + * @param {wp.media.model.Attachments} attachments + * @param {Object} options + * @returns {wp.media.model.Attachments} Returns itself to allow chaining + */ _validateAllHandler: function( attachments, options ) { return this.validateAll( attachments, options ); }, - + /** + * @param {wp.media.model.Attachments} attachments + * @returns {wp.media.model.Attachments} Returns itself to allow chaining + */ mirror: function( attachments ) { - if ( this.mirroring && this.mirroring === attachments ) + if ( this.mirroring && this.mirroring === attachments ) { return this; + } this.unmirror(); this.mirroring = attachments; @@ -448,23 +748,26 @@ window.wp = window.wp || {}; return this; }, - unmirror: function() { - if ( ! this.mirroring ) + if ( ! this.mirroring ) { return; + } this.unobserve( this.mirroring ); delete this.mirroring; }, - + /** + * @param {Object} options + * @returns {Promise} + */ more: function( options ) { var deferred = $.Deferred(), mirroring = this.mirroring, attachments = this; - if ( ! mirroring || ! mirroring.more ) + if ( ! mirroring || ! mirroring.more ) { return deferred.resolveWith( this ).promise(); - + } // If we're mirroring another collection, forward `more` to // the mirrored collection. Account for a race condition by // checking if we're still mirroring that collection when @@ -476,14 +779,23 @@ window.wp = window.wp || {}; return deferred.promise(); }, - + /** + * @returns {Boolean} + */ hasMore: function() { return this.mirroring ? this.mirroring.hasMore() : false; }, - + /** + * Overrides Backbone.Collection.parse + * + * @param {Object|Array} resp The raw response Object/Array. + * @param {Object} xhr + * @returns {Array} The array of model attributes to be added to the collection + */ parse: function( resp, xhr ) { - if ( ! _.isArray( resp ) ) + if ( ! _.isArray( resp ) ) { resp = [resp]; + } return _.map( resp, function( attrs ) { var id, attachment, newAttributes; @@ -498,23 +810,31 @@ window.wp = window.wp || {}; attachment = Attachment.get( id ); newAttributes = attachment.parse( attrs, xhr ); - if ( ! _.isEqual( attachment.attributes, newAttributes ) ) + if ( ! _.isEqual( attachment.attributes, newAttributes ) ) { attachment.set( newAttributes ); + } return attachment; }); }, - + /** + * @access private + */ _requery: function() { - if ( this.props.get('query') ) + if ( this.props.get('query') ) { this.mirror( Query.get( this.props.toJSON() ) ); + } }, - - // If this collection is sorted by `menuOrder`, recalculates and saves - // the menu order to the database. + /** + * If this collection is sorted by `menuOrder`, recalculates and saves + * the menu order to the database. + * + * @returns {undefined|Promise} + */ saveMenuOrder: function() { - if ( 'menuOrder' !== this.props.get('orderby') ) + if ( 'menuOrder' !== this.props.get('orderby') ) { return; + } // Removes any uploading attachments, updates each attachment's // menu order, and returns an object with an { id: menuOrder } @@ -528,8 +848,9 @@ window.wp = window.wp || {}; return [ attachment.id, index ]; }).object().value(); - if ( _.isEmpty( attachments ) ) + if ( _.isEmpty( attachments ) ) { return; + } return media.post( 'save-attachment-order', { nonce: media.model.settings.post.nonce, @@ -538,6 +859,17 @@ window.wp = window.wp || {}; }); } }, { + /** + * @static + * Overrides Backbone.Collection.comparator + * + * @param {Backbone.Model} a + * @param {Backbone.Model} b + * @param {Object} options + * @returns {Number} -1 if the first model should come before the second, + * 0 if they are of the same rank and + * 1 if the first model should come after. + */ comparator: function( a, b, options ) { var key = this.props.get('orderby'), order = this.props.get('order') || 'DESC', @@ -553,44 +885,79 @@ window.wp = window.wp || {}; } // If `options.ties` is set, don't enforce the `cid` tiebreaker. - if ( options && options.ties ) + if ( options && options.ties ) { ac = bc = null; + } return ( 'DESC' === order ) ? compare( a, b, ac, bc ) : compare( b, a, bc, ac ); }, - + /** + * @namespace + */ filters: { - // Note that this client-side searching is *not* equivalent - // to our server-side searching. + /** + * @static + * Note that this client-side searching is *not* equivalent + * to our server-side searching. + * + * @param {wp.media.model.Attachment} attachment + * + * @this wp.media.model.Attachments + * + * @returns {Boolean} + */ search: function( attachment ) { - if ( ! this.props.get('search') ) + if ( ! this.props.get('search') ) { return true; + } return _.any(['title','filename','description','caption','name'], function( key ) { var value = attachment.get( key ); return value && -1 !== value.search( this.props.get('search') ); }, this ); }, - + /** + * @static + * @param {wp.media.model.Attachment} attachment + * + * @this wp.media.model.Attachments + * + * @returns {Boolean} + */ type: function( attachment ) { var type = this.props.get('type'); return ! type || -1 !== type.indexOf( attachment.get('type') ); }, - + /** + * @static + * @param {wp.media.model.Attachment} attachment + * + * @this wp.media.model.Attachments + * + * @returns {Boolean} + */ uploadedTo: function( attachment ) { var uploadedTo = this.props.get('uploadedTo'); - if ( _.isUndefined( uploadedTo ) ) + if ( _.isUndefined( uploadedTo ) ) { return true; + } return uploadedTo === attachment.get('uploadedTo'); } } }); + /** + * @static + * @member {wp.media.model.Attachments} + */ Attachments.all = new Attachments(); /** * wp.media.query + * + * @static + * @returns {wp.media.model.Attachments} */ media.query = function( props ) { return new Attachments( null, { @@ -606,8 +973,18 @@ window.wp = window.wp || {}; * * Note: Do NOT change this.args after the query has been initialized. * Things will break. + * + * @constructor + * @augments wp.media.model.Attachments + * @augments Backbone.Collection */ Query = media.model.Query = Attachments.extend({ + /** + * @global wp.Uploader + * + * @param {Array} [models=[]] Array of models used to populate the collection. + * @param {Object} [options={}] + */ initialize: function( models, options ) { var allowed; @@ -622,8 +999,9 @@ window.wp = window.wp || {}; var orderby = this.props.get('orderby'), order = this.props.get('order'); - if ( ! this.comparator ) + if ( ! this.comparator ) { return true; + } // We want any items that can be placed before the last // item in the set. If we add any items after the last @@ -654,32 +1032,49 @@ window.wp = window.wp || {}; // are no filters for other properties, so observing will result in // false positives in those queries. allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ]; - if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) + if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { this.observe( wp.Uploader.queue ); + } }, - + /** + * @returns {Boolean} + */ hasMore: function() { return this._hasMore; }, - + /** + * @param {Object} [options={}] + * @returns {Promise} + */ more: function( options ) { var query = this; - if ( this._more && 'pending' === this._more.state() ) + if ( this._more && 'pending' === this._more.state() ) { return this._more; + } - if ( ! this.hasMore() ) + if ( ! this.hasMore() ) { return $.Deferred().resolveWith( this ).promise(); + } options = options || {}; options.remove = false; return this._more = this.fetch( options ).done( function( resp ) { - if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) + if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { query._hasMore = false; + } }); }, - + /** + * Overrides Backbone.Collection.sync + * Overrides wp.media.model.Attachments.sync + * + * @param {String} method + * @param {Backbone.Model} model + * @param {Object} [options={}] + * @returns {Promise} + */ sync: function( method, model, options ) { var args, fallback; @@ -696,28 +1091,39 @@ window.wp = window.wp || {}; args = _.clone( this.args ); // Determine which page to query. - if ( -1 !== args.posts_per_page ) + if ( -1 !== args.posts_per_page ) { args.paged = Math.floor( this.length / args.posts_per_page ) + 1; + } options.data.query = args; return media.ajax( options ); // Otherwise, fall back to Backbone.sync() } else { + /** + * Call wp.media.model.Attachments.sync or Backbone.sync + */ fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; return fallback.sync.apply( this, arguments ); } } }, { + /** + * @readonly + */ defaultProps: { orderby: 'date', order: 'DESC' }, - + /** + * @readonly + */ defaultArgs: { posts_per_page: 40 }, - + /** + * @readonly + */ orderby: { allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ], valuemap: { @@ -726,7 +1132,9 @@ window.wp = window.wp || {}; 'menuOrder': 'menu_order ID' } }, - + /** + * @readonly + */ propmap: { 'search': 's', 'type': 'post_mime_type', @@ -734,11 +1142,25 @@ window.wp = window.wp || {}; 'menuOrder': 'menu_order', 'uploadedTo': 'post_parent' }, - + /** + * @static + * @method + * + * @returns {wp.media.model.Query} A new query. + */ // Caches query objects so queries can be easily reused. get: (function(){ + /** + * @static + * @type Array + */ var queries = []; + /** + * @param {Object} props + * @param {Object} options + * @returns {Query} + */ return function( props, options ) { var args = {}, orderby = Query.orderby, @@ -754,18 +1176,21 @@ window.wp = window.wp || {}; // Normalize the order. props.order = props.order.toUpperCase(); - if ( 'DESC' !== props.order && 'ASC' !== props.order ) + if ( 'DESC' !== props.order && 'ASC' !== props.order ) { props.order = defaults.order.toUpperCase(); + } // Ensure we have a valid orderby value. - if ( ! _.contains( orderby.allowed, props.orderby ) ) + if ( ! _.contains( orderby.allowed, props.orderby ) ) { props.orderby = defaults.orderby; + } // Generate the query `args` object. // Correct any differing property names. _.each( props, function( value, prop ) { - if ( _.isNull( value ) ) + if ( _.isNull( value ) ) { return; + } args[ Query.propmap[ prop ] || prop ] = value; }); @@ -800,38 +1225,73 @@ window.wp = window.wp || {}; * wp.media.model.Selection * * Used to manage a selection of attachments in the views. + * + * @constructor + * @augments wp.media.model.Attachments + * @augments Backbone.Collection */ media.model.Selection = Attachments.extend({ + /** + * Refresh the `single` model whenever the selection changes. + * Binds `single` instead of using the context argument to ensure + * it receives no parameters. + * + * @param {Array} [models=[]] Array of models used to populate the collection. + * @param {Object} [options={}] + */ initialize: function( models, options ) { + /** + * call 'initialize' directly on the parent class + */ Attachments.prototype.initialize.apply( this, arguments ); this.multiple = options && options.multiple; - // Refresh the `single` model whenever the selection changes. - // Binds `single` instead of using the context argument to ensure - // it receives no parameters. this.on( 'add remove reset', _.bind( this.single, this, false ) ); }, - // Override the selection's add method. - // If the workflow does not support multiple - // selected attachments, reset the selection. + /** + * Override the selection's add method. + * If the workflow does not support multiple + * selected attachments, reset the selection. + * + * Overrides Backbone.Collection.add + * Overrides wp.media.model.Attachments.add + * + * @param {Array} models + * @param {Object} options + * @returns {wp.media.model.Attachment[]} + */ add: function( models, options ) { - if ( ! this.multiple ) + if ( ! this.multiple ) { this.remove( this.models ); - + } + /** + * call 'add' directly on the parent class + */ return Attachments.prototype.add.call( this, models, options ); }, + /** + * Triggered when toggling (clicking on) an attachment in the modal + * + * @param {undefined|boolean|wp.media.model.Attachment} model + * + * @fires wp.media.model.Selection#selection:single + * @fires wp.media.model.Selection#selection:unsingle + * + * @returns {Backbone.Model} + */ single: function( model ) { var previous = this._single; // If a `model` is provided, use it as the single model. - if ( model ) + if ( model ) { this._single = model; - + } // If the single model isn't in the selection, remove it. - if ( this._single && ! this.get( this._single.cid ) ) + if ( this._single && ! this.get( this._single.cid ) ) { delete this._single; + } this._single = this._single || this.last(); @@ -842,11 +1302,13 @@ window.wp = window.wp || {}; // If the model was already removed, trigger the collection // event manually. - if ( ! this.get( previous.cid ) ) + if ( ! this.get( previous.cid ) ) { this.trigger( 'selection:unsingle', previous, this ); + } } - if ( this._single ) + if ( this._single ) { this._single.trigger( 'selection:single', this._single, this ); + } } // Return the single model, or the last model as a fallback. @@ -859,4 +1321,4 @@ window.wp = window.wp || {}; window.wp = null; }); -}(jQuery)); \ No newline at end of file +}(jQuery));