+ click: function() {
+ library.reset( library.toArray().reverse() );
+ }
+ });
+ }
+ });
+
+ /**
+ * wp.media.controller.CollectionAdd
+ *
+ * @constructor
+ * @augments wp.media.controller.Library
+ * @augments wp.media.controller.State
+ * @augments Backbone.Model
+ */
+ media.controller.CollectionAdd = media.controller.Library.extend({
+ defaults: _.defaults( {
+ // Selection defaults. @see media.model.Selection
+ multiple: 'add',
+ // Attachments browser defaults. @see media.view.AttachmentsBrowser
+ filterable: 'uploaded',
+
+ priority: 100,
+ syncSelection: false
+ }, media.controller.Library.prototype.defaults ),
+
+ initialize: function() {
+ var collectionType = this.get('collectionType');
+
+ if ( 'video' === this.get( 'type' ) ) {
+ collectionType = 'video-' + collectionType;
+ }
+
+ this.set( 'id', collectionType + '-library' );
+ this.set( 'toolbar', collectionType + '-add' );
+ this.set( 'menu', collectionType );
+
+ // If we haven't been provided a `library`, create a `Selection`.
+ if ( ! this.get('library') ) {
+ this.set( 'library', media.query({ type: this.get('type') }) );
+ }
+ media.controller.Library.prototype.initialize.apply( this, arguments );
+ },
+
+ activate: function() {
+ var library = this.get('library'),
+ editLibrary = this.get('editLibrary'),
+ edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
+
+ if ( editLibrary && editLibrary !== edit ) {
+ library.unobserve( editLibrary );
+ }
+
+ // Accepts attachments that exist in the original library and
+ // that do not exist in gallery's library.
+ library.validator = function( attachment ) {
+ return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && media.model.Selection.prototype.validator.apply( this, arguments );
+ };
+
+ // Reset the library to ensure that all attachments are re-added
+ // to the collection. Do so silently, as calling `observe` will
+ // trigger the `reset` event.
+ library.reset( library.mirroring.models, { silent: true });
+ library.observe( edit );
+ this.set('editLibrary', edit);
+
+ media.controller.Library.prototype.activate.apply( this, arguments );
+ }
+ });
+
+ /**
+ * A state for selecting a featured image for a post.
+ *
+ * @constructor
+ * @augments wp.media.controller.Library
+ * @augments wp.media.controller.State
+ * @augments Backbone.Model
+ */
+ media.controller.FeaturedImage = media.controller.Library.extend({
+ defaults: _.defaults({
+ id: 'featured-image',
+ title: l10n.setFeaturedImageTitle,
+ // Selection defaults. @see media.model.Selection
+ multiple: false,
+ // Attachments browser defaults. @see media.view.AttachmentsBrowser
+ filterable: 'uploaded',
+ // Region mode defaults.
+ toolbar: 'featured-image',
+
+ priority: 60,
+ syncSelection: true
+ }, media.controller.Library.prototype.defaults ),
+
+ initialize: function() {
+ var library, comparator;
+
+ // If we haven't been provided a `library`, create a `Selection`.
+ if ( ! this.get('library') ) {
+ this.set( 'library', media.query({ type: 'image' }) );
+ }
+
+ media.controller.Library.prototype.initialize.apply( this, arguments );
+
+ library = this.get('library');
+ comparator = library.comparator;
+
+ // Overload the library's comparator to push items that are not in
+ // the mirrored query to the front of the aggregate collection.
+ library.comparator = function( a, b ) {
+ var aInQuery = !! this.mirroring.get( a.cid ),
+ bInQuery = !! this.mirroring.get( b.cid );
+
+ if ( ! aInQuery && bInQuery ) {
+ return -1;
+ } else if ( aInQuery && ! bInQuery ) {
+ return 1;
+ } else {
+ return comparator.apply( this, arguments );
+ }
+ };
+
+ // Add all items in the selection to the library, so any featured
+ // images that are not initially loaded still appear.
+ library.observe( this.get('selection') );
+ },
+
+ activate: function() {
+ this.updateSelection();
+ this.frame.on( 'open', this.updateSelection, this );
+
+ media.controller.Library.prototype.activate.apply( this, arguments );
+ },
+
+ deactivate: function() {
+ this.frame.off( 'open', this.updateSelection, this );
+
+ media.controller.Library.prototype.deactivate.apply( this, arguments );
+ },
+
+ updateSelection: function() {
+ var selection = this.get('selection'),
+ id = media.view.settings.post.featuredImageId,
+ attachment;
+
+ if ( '' !== id && -1 !== id ) {
+ attachment = media.model.Attachment.get( id );
+ attachment.fetch();
+ }
+
+ selection.reset( attachment ? [ attachment ] : [] );
+ }
+ });
+
+ /**
+ * A state for replacing an image.
+ *
+ * @constructor
+ * @augments wp.media.controller.Library
+ * @augments wp.media.controller.State
+ * @augments Backbone.Model
+ */
+ media.controller.ReplaceImage = media.controller.Library.extend({
+ defaults: _.defaults({
+ id: 'replace-image',
+ title: l10n.replaceImageTitle,
+ // Selection defaults. @see media.model.Selection
+ multiple: false,
+ // Attachments browser defaults. @see media.view.AttachmentsBrowser
+ filterable: 'uploaded',
+ // Region mode defaults.
+ toolbar: 'replace',
+ menu: false,
+
+ priority: 60,
+ syncSelection: true
+ }, media.controller.Library.prototype.defaults ),
+
+ initialize: function( options ) {
+ var library, comparator;
+
+ this.image = options.image;
+ // If we haven't been provided a `library`, create a `Selection`.
+ if ( ! this.get('library') ) {
+ this.set( 'library', media.query({ type: 'image' }) );
+ }
+
+ media.controller.Library.prototype.initialize.apply( this, arguments );
+
+ library = this.get('library');
+ comparator = library.comparator;
+
+ // Overload the library's comparator to push items that are not in
+ // the mirrored query to the front of the aggregate collection.
+ library.comparator = function( a, b ) {
+ var aInQuery = !! this.mirroring.get( a.cid ),
+ bInQuery = !! this.mirroring.get( b.cid );
+
+ if ( ! aInQuery && bInQuery ) {
+ return -1;
+ } else if ( aInQuery && ! bInQuery ) {
+ return 1;
+ } else {
+ return comparator.apply( this, arguments );
+ }
+ };
+
+ // Add all items in the selection to the library, so any featured
+ // images that are not initially loaded still appear.
+ library.observe( this.get('selection') );
+ },
+
+ activate: function() {
+ this.updateSelection();
+ media.controller.Library.prototype.activate.apply( this, arguments );
+ },
+
+ updateSelection: function() {
+ var selection = this.get('selection'),
+ attachment = this.image.attachment;
+
+ selection.reset( attachment ? [ attachment ] : [] );
+ }
+ });
+
+ /**
+ * A state for editing (cropping, etc.) an image.
+ *
+ * @constructor
+ * @augments wp.media.controller.State
+ * @augments Backbone.Model
+ */
+ media.controller.EditImage = media.controller.State.extend({
+ defaults: {
+ id: 'edit-image',
+ title: l10n.editImage,
+ // Region mode defaults.
+ menu: false,
+ toolbar: 'edit-image',
+ content: 'edit-image',
+
+ url: ''
+ },
+
+ activate: function() {
+ this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
+ },
+
+ deactivate: function() {
+ this.stopListening( this.frame );
+ },
+
+ toolbar: function() {
+ var frame = this.frame,
+ lastState = frame.lastState(),
+ previous = lastState && lastState.id;
+
+ frame.toolbar.set( new media.view.Toolbar({
+ controller: frame,
+ items: {
+ back: {
+ style: 'primary',
+ text: l10n.back,
+ priority: 20,
+ click: function() {
+ if ( previous ) {
+ frame.setState( previous );
+ } else {
+ frame.close();
+ }
+ }
+ }
+ }
+ }) );
+ }
+ });
+
+ /**
+ * wp.media.controller.MediaLibrary
+ *
+ * @constructor
+ * @augments wp.media.controller.Library
+ * @augments wp.media.controller.State
+ * @augments Backbone.Model
+ */
+ media.controller.MediaLibrary = media.controller.Library.extend({
+ defaults: _.defaults({
+ // Attachments browser defaults. @see media.view.AttachmentsBrowser
+ filterable: 'uploaded',
+
+ displaySettings: false,
+ priority: 80,
+ syncSelection: false
+ }, media.controller.Library.prototype.defaults ),
+
+ initialize: function( options ) {
+ this.media = options.media;
+ this.type = options.type;
+ this.set( 'library', media.query({ type: this.type }) );
+
+ media.controller.Library.prototype.initialize.apply( this, arguments );
+ },
+
+ activate: function() {
+ if ( media.frame.lastMime ) {
+ this.set( 'library', media.query({ type: media.frame.lastMime }) );
+ delete media.frame.lastMime;
+ }
+ media.controller.Library.prototype.activate.apply( this, arguments );
+ }
+ });
+
+ /**
+ * wp.media.controller.Embed
+ *
+ * @constructor
+ * @augments wp.media.controller.State
+ * @augments Backbone.Model
+ */
+ media.controller.Embed = media.controller.State.extend({
+ defaults: {
+ id: 'embed',
+ title: l10n.insertFromUrlTitle,
+ // Region mode defaults.
+ content: 'embed',
+ menu: 'default',
+ toolbar: 'main-embed',
+
+ priority: 120,
+ type: 'link',
+ url: '',
+ metadata: {}
+ },
+
+ // The amount of time used when debouncing the scan.
+ sensitivity: 200,
+
+ initialize: function(options) {
+ this.metadata = options.metadata;
+ this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
+ this.props = new Backbone.Model( this.metadata || { url: '' });
+ this.props.on( 'change:url', this.debouncedScan, this );
+ this.props.on( 'change:url', this.refresh, this );
+ this.on( 'scan', this.scanImage, this );
+ },
+
+ /**
+ * @fires wp.media.controller.Embed#scan
+ */
+ scan: function() {
+ var scanners,
+ embed = this,