home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / js / media-models.js < prev    next >
Encoding:
JavaScript  |  2017-10-04  |  42.8 KB  |  1,617 lines

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