(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o` elements. this.$el.html( _.chain( this.filters ).map( function( filter, value ) { return { el: $( '' ).val( value ).html( filter.text )[0], priority: filter.priority || 50 }; }, this ).sortBy('priority').pluck('el').value() ); this.listenTo( this.model, 'change', this.select ); this.select(); }, /** * @abstract */ createFilters: function() { this.filters = {}; }, /** * When the selected filter changes, update the Attachment Query properties to match. */ change: function() { var filter = this.filters[ this.el.value ]; if ( filter ) { this.model.set( filter.props ); } }, select: function() { var model = this.model, value = 'all', props = model.toJSON(); _.find( this.filters, function( filter, id ) { var equal = _.all( filter.props, function( prop, key ) { return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); }); if ( equal ) { return value = id; } }); this.$el.val( value ); } }); module.exports = AttachmentFilters; },{"./view.js":51}],12:[function(require,module,exports){ /** * wp.media.view.AttachmentFilters.All * * @class * @augments wp.media.view.AttachmentFilters * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var AttachmentFilters = require( '../attachment-filters.js' ), l10n = wp.media.view.l10n, All; All = AttachmentFilters.extend({ createFilters: function() { var filters = {}; _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { filters[ key ] = { text: text, props: { status: null, type: key, uploadedTo: null, orderby: 'date', order: 'DESC' } }; }); filters.all = { text: l10n.allMediaItems, props: { status: null, type: null, uploadedTo: null, orderby: 'date', order: 'DESC' }, priority: 10 }; if ( wp.media.view.settings.post.id ) { filters.uploaded = { text: l10n.uploadedToThisPost, props: { status: null, type: null, uploadedTo: wp.media.view.settings.post.id, orderby: 'menuOrder', order: 'ASC' }, priority: 20 }; } filters.unattached = { text: l10n.unattached, props: { status: null, uploadedTo: 0, type: null, orderby: 'menuOrder', order: 'ASC' }, priority: 50 }; if ( wp.media.view.settings.mediaTrash && this.controller.isModeActive( 'grid' ) ) { filters.trash = { text: l10n.trash, props: { uploadedTo: null, status: 'trash', type: null, orderby: 'date', order: 'DESC' }, priority: 50 }; } this.filters = filters; } }); module.exports = All; },{"../attachment-filters.js":11}],13:[function(require,module,exports){ /** * A filter dropdown for month/dates. * * @class * @augments wp.media.view.AttachmentFilters * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var AttachmentFilters = require( '../attachment-filters.js' ), l10n = wp.media.view.l10n, DateFilter; DateFilter = AttachmentFilters.extend({ id: 'media-attachment-date-filters', createFilters: function() { var filters = {}; _.each( wp.media.view.settings.months || {}, function( value, index ) { filters[ index ] = { text: value.text, props: { year: value.year, monthnum: value.month } }; }); filters.all = { text: l10n.allDates, props: { monthnum: false, year: false }, priority: 10 }; this.filters = filters; } }); module.exports = DateFilter; },{"../attachment-filters.js":11}],14:[function(require,module,exports){ /** * wp.media.view.AttachmentFilters.Uploaded * * @class * @augments wp.media.view.AttachmentFilters * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var AttachmentFilters = require( '../attachment-filters.js' ), l10n = wp.media.view.l10n, Uploaded; Uploaded = AttachmentFilters.extend({ createFilters: function() { var type = this.model.get('type'), types = wp.media.view.settings.mimeTypes, text; if ( types && type ) { text = types[ type ]; } this.filters = { all: { text: text || l10n.allMediaItems, props: { uploadedTo: null, orderby: 'date', order: 'DESC' }, priority: 10 }, uploaded: { text: l10n.uploadedToThisPost, props: { uploadedTo: wp.media.view.settings.post.id, orderby: 'menuOrder', order: 'ASC' }, priority: 20 }, unattached: { text: l10n.unattached, props: { uploadedTo: 0, orderby: 'menuOrder', order: 'ASC' }, priority: 50 } }; } }); module.exports = Uploaded; },{"../attachment-filters.js":11}],15:[function(require,module,exports){ /** * wp.media.view.Attachment * * @class * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var View = require( './view.js' ), $ = jQuery, Attachment; Attachment = View.extend({ tagName: 'li', className: 'attachment', template: wp.template('attachment'), attributes: function() { return { 'tabIndex': 0, 'role': 'checkbox', 'aria-label': this.model.get( 'title' ), 'aria-checked': false, 'data-id': this.model.get( 'id' ) }; }, events: { 'click .js--select-attachment': 'toggleSelectionHandler', 'change [data-setting]': 'updateSetting', 'change [data-setting] input': 'updateSetting', 'change [data-setting] select': 'updateSetting', 'change [data-setting] textarea': 'updateSetting', 'click .close': 'removeFromLibrary', 'click .check': 'checkClickHandler', 'click a': 'preventDefault', 'keydown .close': 'removeFromLibrary', 'keydown': 'toggleSelectionHandler' }, buttons: {}, initialize: function() { var selection = this.options.selection, options = _.defaults( this.options, { rerenderOnModelChange: true } ); if ( options.rerenderOnModelChange ) { this.listenTo( this.model, 'change', this.render ); } else { this.listenTo( this.model, 'change:percent', this.progress ); } this.listenTo( this.model, 'change:title', this._syncTitle ); this.listenTo( this.model, 'change:caption', this._syncCaption ); this.listenTo( this.model, 'change:artist', this._syncArtist ); this.listenTo( this.model, 'change:album', this._syncAlbum ); // Update the selection. this.listenTo( this.model, 'add', this.select ); this.listenTo( this.model, 'remove', this.deselect ); if ( selection ) { selection.on( 'reset', this.updateSelect, this ); // Update the model's details view. this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); this.details( this.model, this.controller.state().get('selection') ); } this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); }, /** * @returns {wp.media.view.Attachment} Returns itself to allow chaining */ dispose: function() { var selection = this.options.selection; // Make sure all settings are saved before removing the view. this.updateAll(); if ( selection ) { selection.off( null, null, this ); } /** * call 'dispose' directly on the parent class */ View.prototype.dispose.apply( this, arguments ); return this; }, /** * @returns {wp.media.view.Attachment} Returns itself to allow chaining */ render: function() { var options = _.defaults( this.model.toJSON(), { orientation: 'landscape', uploading: false, type: '', subtype: '', icon: '', filename: '', caption: '', title: '', dateFormatted: '', width: '', height: '', compat: false, alt: '', description: '' }, this.options ); options.buttons = this.buttons; options.describe = this.controller.state().get('describe'); if ( 'image' === options.type ) { options.size = this.imageSize(); } options.can = {}; if ( options.nonces ) { options.can.remove = !! options.nonces['delete']; options.can.save = !! options.nonces.update; } if ( this.controller.state().get('allowLocalEdits') ) { options.allowLocalEdits = true; } if ( options.uploading && ! options.percent ) { options.percent = 0; } this.views.detach(); this.$el.html( this.template( options ) ); this.$el.toggleClass( 'uploading', options.uploading ); if ( options.uploading ) { this.$bar = this.$('.media-progress-bar div'); } else { delete this.$bar; } // Check if the model is selected. this.updateSelect(); // Update the save status. this.updateSave(); this.views.render(); return this; }, progress: function() { if ( this.$bar && this.$bar.length ) { this.$bar.width( this.model.get('percent') + '%' ); } }, /** * @param {Object} event */ toggleSelectionHandler: function( event ) { var method; // Don't do anything inside inputs. if ( 'INPUT' === event.target.nodeName ) { return; } // Catch arrow events if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { this.controller.trigger( 'attachment:keydown:arrow', event ); return; } // Catch enter and space events if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { return; } event.preventDefault(); // In the grid view, bubble up an edit:attachment event to the controller. if ( this.controller.isModeActive( 'grid' ) ) { if ( this.controller.isModeActive( 'edit' ) ) { // Pass the current target to restore focus when closing this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); return; } if ( this.controller.isModeActive( 'select' ) ) { method = 'toggle'; } } if ( event.shiftKey ) { method = 'between'; } else if ( event.ctrlKey || event.metaKey ) { method = 'toggle'; } this.toggleSelection({ method: method }); this.controller.trigger( 'selection:toggle' ); }, /** * @param {Object} options */ toggleSelection: function( options ) { var collection = this.collection, selection = this.options.selection, model = this.model, method = options && options.method, single, models, singleIndex, modelIndex; if ( ! selection ) { return; } single = selection.single(); method = _.isUndefined( method ) ? selection.multiple : method; // If the `method` is set to `between`, select all models that // exist between the current and the selected model. if ( 'between' === method && single && selection.multiple ) { // If the models are the same, short-circuit. if ( single === model ) { return; } singleIndex = collection.indexOf( single ); modelIndex = collection.indexOf( this.model ); if ( singleIndex < modelIndex ) { models = collection.models.slice( singleIndex, modelIndex + 1 ); } else { models = collection.models.slice( modelIndex, singleIndex + 1 ); } selection.add( models ); selection.single( model ); return; // If the `method` is set to `toggle`, just flip the selection // status, regardless of whether the model is the single model. } else if ( 'toggle' === method ) { selection[ this.selected() ? 'remove' : 'add' ]( model ); selection.single( model ); return; } else if ( 'add' === method ) { selection.add( model ); selection.single( model ); return; } // Fixes bug that loses focus when selecting a featured image if ( ! method ) { method = 'add'; } if ( method !== 'add' ) { method = 'reset'; } if ( this.selected() ) { // If the model is the single model, remove it. // If it is not the same as the single model, // it now becomes the single model. selection[ single === model ? 'remove' : 'single' ]( model ); } else { // If the model is not selected, run the `method` on the // selection. By default, we `reset` the selection, but the // `method` can be set to `add` the model to the selection. selection[ method ]( model ); selection.single( model ); } }, updateSelect: function() { this[ this.selected() ? 'select' : 'deselect' ](); }, /** * @returns {unresolved|Boolean} */ selected: function() { var selection = this.options.selection; if ( selection ) { return !! selection.get( this.model.cid ); } }, /** * @param {Backbone.Model} model * @param {Backbone.Collection} collection */ select: function( model, collection ) { var selection = this.options.selection, controller = this.controller; // Check if a selection exists and if it's the collection provided. // If they're not the same collection, bail; we're in another // selection's event loop. if ( ! selection || ( collection && collection !== selection ) ) { return; } // Bail if the model is already selected. if ( this.$el.hasClass( 'selected' ) ) { return; } // Add 'selected' class to model, set aria-checked to true. this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); // Make the checkbox tabable, except in media grid (bulk select mode). if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { this.$( '.check' ).attr( 'tabindex', '0' ); } }, /** * @param {Backbone.Model} model * @param {Backbone.Collection} collection */ deselect: function( model, collection ) { var selection = this.options.selection; // Check if a selection exists and if it's the collection provided. // If they're not the same collection, bail; we're in another // selection's event loop. if ( ! selection || ( collection && collection !== selection ) ) { return; } this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) .find( '.check' ).attr( 'tabindex', '-1' ); }, /** * @param {Backbone.Model} model * @param {Backbone.Collection} collection */ details: function( model, collection ) { var selection = this.options.selection, details; if ( selection !== collection ) { return; } details = selection.single(); this.$el.toggleClass( 'details', details === this.model ); }, /** * @param {Object} event */ preventDefault: function( event ) { event.preventDefault(); }, /** * @param {string} size * @returns {Object} */ imageSize: function( size ) { var sizes = this.model.get('sizes'), matched = false; size = size || 'medium'; // Use the provided image size if possible. if ( sizes ) { if ( sizes[ size ] ) { matched = sizes[ size ]; } else if ( sizes.large ) { matched = sizes.large; } else if ( sizes.thumbnail ) { matched = sizes.thumbnail; } else if ( sizes.full ) { matched = sizes.full; } if ( matched ) { return _.clone( matched ); } } return { url: this.model.get('url'), width: this.model.get('width'), height: this.model.get('height'), orientation: this.model.get('orientation') }; }, /** * @param {Object} event */ updateSetting: function( event ) { var $setting = $( event.target ).closest('[data-setting]'), setting, value; if ( ! $setting.length ) { return; } setting = $setting.data('setting'); value = event.target.value; if ( this.model.get( setting ) !== value ) { this.save( setting, value ); } }, /** * Pass all the arguments to the model's save method. * * Records the aggregate status of all save requests and updates the * view's classes accordingly. */ save: function() { var view = this, save = this._save = this._save || { status: 'ready' }, request = this.model.save.apply( this.model, arguments ), requests = save.requests ? $.when( request, save.requests ) : request; // If we're waiting to remove 'Saved.', stop. if ( save.savedTimer ) { clearTimeout( save.savedTimer ); } this.updateSave('waiting'); save.requests = requests; requests.always( function() { // If we've performed another request since this one, bail. if ( save.requests !== requests ) { return; } view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); save.savedTimer = setTimeout( function() { view.updateSave('ready'); delete save.savedTimer; }, 2000 ); }); }, /** * @param {string} status * @returns {wp.media.view.Attachment} Returns itself to allow chaining */ updateSave: function( status ) { var save = this._save = this._save || { status: 'ready' }; if ( status && status !== save.status ) { this.$el.removeClass( 'save-' + save.status ); save.status = status; } this.$el.addClass( 'save-' + save.status ); return this; }, updateAll: function() { var $settings = this.$('[data-setting]'), model = this.model, changed; changed = _.chain( $settings ).map( function( el ) { var $input = $('input, textarea, select, [value]', el ), setting, value; if ( ! $input.length ) { return; } setting = $(el).data('setting'); value = $input.val(); // Record the value if it changed. if ( model.get( setting ) !== value ) { return [ setting, value ]; } }).compact().object().value(); if ( ! _.isEmpty( changed ) ) { model.save( changed ); } }, /** * @param {Object} event */ removeFromLibrary: function( event ) { // Catch enter and space events if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { return; } // Stop propagation so the model isn't selected. event.stopPropagation(); this.collection.remove( this.model ); }, /** * Add the model if it isn't in the selection, if it is in the selection, * remove it. * * @param {[type]} event [description] * @return {[type]} [description] */ checkClickHandler: function ( event ) { var selection = this.options.selection; if ( ! selection ) { return; } event.stopPropagation(); if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { selection.remove( this.model ); // Move focus back to the attachment tile (from the check). this.$el.focus(); } else { selection.add( this.model ); } } }); // Ensure settings remain in sync between attachment views. _.each({ caption: '_syncCaption', title: '_syncTitle', artist: '_syncArtist', album: '_syncAlbum' }, function( method, setting ) { /** * @param {Backbone.Model} model * @param {string} value * @returns {wp.media.view.Attachment} Returns itself to allow chaining */ Attachment.prototype[ method ] = function( model, value ) { var $setting = this.$('[data-setting="' + setting + '"]'); if ( ! $setting.length ) { return this; } // If the updated value is in sync with the value in the DOM, there // is no need to re-render. If we're currently editing the value, // it will automatically be in sync, suppressing the re-render for // the view we're editing, while updating any others. if ( value === $setting.find('input, textarea, select, [value]').val() ) { return this; } return this.render(); }; }); module.exports = Attachment; },{"./view.js":51}],16:[function(require,module,exports){ /** * A similar view to media.view.Attachment.Details * for use in the Edit Attachment modal. * * @constructor * @augments wp.media.view.Attachment.Details * @augments wp.media.view.Attachment * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var Details = require( './details.js' ), MediaDetails = require( '../media-details.js' ), TwoColumn; TwoColumn = Details.extend({ template: wp.template( 'attachment-details-two-column' ), editAttachment: function( event ) { event.preventDefault(); this.controller.content.mode( 'edit-image' ); }, /** * Noop this from parent class, doesn't apply here. */ toggleSelectionHandler: function() {}, render: function() { Details.prototype.render.apply( this, arguments ); wp.media.mixin.removeAllPlayers(); this.$( 'audio, video' ).each( function (i, elem) { var el = MediaDetails.prepareSrc( elem ); new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); } ); } }); module.exports = TwoColumn; },{"../media-details.js":33,"./details.js":17}],17:[function(require,module,exports){ /** * wp.media.view.Attachment.Details * * @class * @augments wp.media.view.Attachment * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var Attachment = require( '../attachment.js' ), l10n = wp.media.view.l10n, Details; Details = Attachment.extend({ tagName: 'div', className: 'attachment-details', template: wp.template('attachment-details'), attributes: function() { return { 'tabIndex': 0, 'data-id': this.model.get( 'id' ) }; }, events: { 'change [data-setting]': 'updateSetting', 'change [data-setting] input': 'updateSetting', 'change [data-setting] select': 'updateSetting', 'change [data-setting] textarea': 'updateSetting', 'click .delete-attachment': 'deleteAttachment', 'click .trash-attachment': 'trashAttachment', 'click .untrash-attachment': 'untrashAttachment', 'click .edit-attachment': 'editAttachment', 'click .refresh-attachment': 'refreshAttachment', 'keydown': 'toggleSelectionHandler' }, initialize: function() { this.options = _.defaults( this.options, { rerenderOnModelChange: false }); this.on( 'ready', this.initialFocus ); // Call 'initialize' directly on the parent class. Attachment.prototype.initialize.apply( this, arguments ); }, initialFocus: function() { if ( ! wp.media.isTouchDevice ) { this.$( ':input' ).eq( 0 ).focus(); } }, /** * @param {Object} event */ deleteAttachment: function( event ) { event.preventDefault(); if ( window.confirm( l10n.warnDelete ) ) { this.model.destroy(); // Keep focus inside media modal // after image is deleted this.controller.modal.focusManager.focus(); } }, /** * @param {Object} event */ trashAttachment: function( event ) { var library = this.controller.library; event.preventDefault(); if ( wp.media.view.settings.mediaTrash && 'edit-metadata' === this.controller.content.mode() ) { this.model.set( 'status', 'trash' ); this.model.save().done( function() { library._requery( true ); } ); } else { this.model.destroy(); } }, /** * @param {Object} event */ untrashAttachment: function( event ) { var library = this.controller.library; event.preventDefault(); this.model.set( 'status', 'inherit' ); this.model.save().done( function() { library._requery( true ); } ); }, /** * @param {Object} event */ editAttachment: function( event ) { var editState = this.controller.states.get( 'edit-image' ); if ( window.imageEdit && editState ) { event.preventDefault(); editState.set( 'image', this.model ); this.controller.setState( 'edit-image' ); } else { this.$el.addClass('needs-refresh'); } }, /** * @param {Object} event */ refreshAttachment: function( event ) { this.$el.removeClass('needs-refresh'); event.preventDefault(); this.model.fetch(); }, /** * When reverse tabbing(shift+tab) out of the right details panel, deliver * the focus to the item in the list that was being edited. * * @param {Object} event */ toggleSelectionHandler: function( event ) { if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { this.controller.trigger( 'attachment:details:shift-tab', event ); return false; } if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { this.controller.trigger( 'attachment:keydown:arrow', event ); return; } } }); module.exports = Details; },{"../attachment.js":15}],18:[function(require,module,exports){ /** * wp.media.view.Attachment.Library * * @class * @augments wp.media.view.Attachment * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var Attachment = require( '../attachment.js' ), Library; Library = Attachment.extend({ buttons: { check: true } }); module.exports = Library; },{"../attachment.js":15}],19:[function(require,module,exports){ /** * wp.media.view.Attachments * * @class * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ var View = require( './view.js' ), Attachment = require( './attachment.js' ), $ = jQuery, Attachments; Attachments = View.extend({ tagName: 'ul', className: 'attachments', attributes: { tabIndex: -1 }, initialize: function() { this.el.id = _.uniqueId('__attachments-view-'); _.defaults( this.options, { refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, refreshThreshold: 3, AttachmentView: Attachment, sortable: false, resize: true, idealColumnWidth: $( window ).width() < 640 ? 135 : 150 }); this._viewsByCid = {}; this.$window = $( window ); this.resizeEvent = 'resize.media-modal-columns'; this.collection.on( 'add', function( attachment ) { this.views.add( this.createAttachmentView( attachment ), { at: this.collection.indexOf( attachment ) }); }, this ); this.collection.on( 'remove', function( attachment ) { var view = this._viewsByCid[ attachment.cid ]; delete this._viewsByCid[ attachment.cid ]; if ( view ) { view.remove(); } }, this ); this.collection.on( 'reset', this.render, this ); this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); // Throttle the scroll handler and bind this. this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); this.options.scrollElement = this.options.scrollElement || this.el; $( this.options.scrollElement ).on( 'scroll', this.scroll ); this.initSortable(); _.bindAll( this, 'setColumns' ); if ( this.options.resize ) { this.on( 'ready', this.bindEvents ); this.controller.on( 'open', this.setColumns ); // Call this.setColumns() after this view has been rendered in the DOM so // attachments get proper width applied. _.defer( this.setColumns, this ); } }, bindEvents: function() { this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); }, attachmentFocus: function() { this.$( 'li:first' ).focus(); }, restoreFocus: function() { this.$( 'li.selected:first' ).focus(); }, arrowEvent: function( event ) { var attachments = this.$el.children( 'li' ), perRow = this.columns, index = attachments.filter( ':focus' ).index(), row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); if ( index === -1 ) { return; } // Left arrow if ( 37 === event.keyCode ) { if ( 0 === index ) { return; } attachments.eq( index - 1 ).focus(); } // Up arrow if ( 38 === event.keyCode ) { if ( 1 === row ) { return; } attachments.eq( index - perRow ).focus(); } // Right arrow if ( 39 === event.keyCode ) { if ( attachments.length === index ) { return; } attachments.eq( index + 1 ).focus(); } // Down arrow if ( 40 === event.keyCode ) { if ( Math.ceil( attachments.length / perRow ) === row ) { return; } attachments.eq( index + perRow ).focus(); } }, dispose: function() { this.collection.props.off( null, null, this ); if ( this.options.resize ) { this.$window.off( this.resizeEvent ); } /** * call 'dispose' directly on the parent class */ View.prototype.dispose.apply( this, arguments ); }, setColumns: function() { var prev = this.columns, width = this.$el.width(); if ( width ) { this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; if ( ! prev || prev !== this.columns ) { this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); } } }, initSortable: function() { var collection = this.collection; if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { return; } this.$el.sortable( _.extend({ // If the `collection` has a `comparator`, disable sorting. disabled: !! collection.comparator, // Change the position of the attachment as soon as the // mouse pointer overlaps a thumbnail. tolerance: 'pointer', // Record the initial `index` of the dragged model. start: function( event, ui ) { ui.item.data('sortableIndexStart', ui.item.index()); }, // Update the model's index in the collection. // Do so silently, as the view is already accurate. update: function( event, ui ) { var model = collection.at( ui.item.data('sortableIndexStart') ), comparator = collection.comparator; // Temporarily disable the comparator to prevent `add` // from re-sorting. delete collection.comparator; // Silently shift the model to its new index. collection.remove( model, { silent: true }); collection.add( model, { silent: true, at: ui.item.index() }); // Restore the comparator. collection.comparator = comparator; // Fire the `reset` event to ensure other collections sync. collection.trigger( 'reset', collection ); // If the collection is sorted by menu order, // update the menu order. collection.saveMenuOrder(); } }, this.options.sortable ) ); // If the `orderby` property is changed on the `collection`, // check to see if we have a `comparator`. If so, disable sorting. collection.props.on( 'change:orderby', function() { this.$el.sortable( 'option', 'disabled', !! collection.comparator ); }, this ); this.collection.props.on( 'change:orderby', this.refreshSortable, this ); this.refreshSortable(); }, refreshSortable: function() { if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { return; } // If the `collection` has a `comparator`, disable sorting. var collection = this.collection, orderby = collection.props.get('orderby'), enabled = 'menuOrder' === orderby || ! collection.comparator; this.$el.sortable( 'option', 'disabled', ! enabled ); }, /** * @param {wp.media.model.Attachment} attachment * @returns {wp.media.View} */ createAttachmentView: function( attachment ) { var view = new this.options.AttachmentView({ controller: this.controller, model: attachment, collection: this.collection, selection: this.options.selection }); return this._viewsByCid[ attachment.cid ] = view; }, prepare: function() { // Create all of the Attachment views, and replace // the list in a single DOM operation. if ( this.collection.length ) { this.views.set( this.collection.map( this.createAttachmentView, this ) ); // If there are no elements, clear the views and load some. } else { this.views.unset(); this.collection.more().done( this.scroll ); } }, ready: function() { // Trigger the scroll event to check if we're within the // threshold to query for additional attachments. this.scroll(); }, scroll: function() { var view = this, el = this.options.scrollElement, scrollTop = el.scrollTop, toolbar; // The scroll event occurs on the document, but the element // that should be checked is the document body. if ( el === document ) { el = document.body; scrollTop = $(document).scrollTop(); } if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { return; } toolbar = this.views.parent.toolbar; // Show the spinner only if we are close to the bottom. if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { toolbar.get('spinner').show(); } if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { this.collection.more().done(function() { view.scroll(); toolbar.get('spinner').hide(); }); } } }); module.exports = Attachments; },{"./attachment.js":15,"./view.js":51}],20:[function(require,module,exports){ /** * wp.media.view.AttachmentsBrowser * * @class * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View * * @param {object} options * @param {object} [options.filters=false] Which filters to show in the browser's toolbar. * Accepts 'uploaded' and 'all'. * @param {object} [options.search=true] Whether to show the search interface in the * browser's toolbar. * @param {object} [options.date=true] Whether to show the date filter in the * browser's toolbar. * @param {object} [options.display=false] Whether to show the attachments display settings * view in the sidebar. * @param {bool|string} [options.sidebar=true] Whether to create a sidebar for the browser. * Accepts true, false, and 'errors'. */ var View = require( '../view.js' ), Library = require( '../attachment/library.js' ), Toolbar = require( '../toolbar.js' ), Spinner = require( '../spinner.js' ), Search = require( '../search.js' ), Label = require( '../label.js' ), Uploaded = require( '../attachment-filters/uploaded.js' ), All = require( '../attachment-filters/all.js' ), DateFilter = require( '../attachment-filters/date.js' ), UploaderInline = require( '../uploader/inline.js' ), Attachments = require( '../attachments.js' ), Sidebar = require( '../sidebar.js' ), UploaderStatus = require( '../uploader/status.js' ), Details = require( '../attachment/details.js' ), AttachmentCompat = require( '../attachment-compat.js' ), AttachmentDisplay = require( '../settings/attachment-display.js' ), mediaTrash = wp.media.view.settings.mediaTrash, l10n = wp.media.view.l10n, $ = jQuery, AttachmentsBrowser; AttachmentsBrowser = View.extend({ tagName: 'div', className: 'attachments-browser', initialize: function() { _.defaults( this.options, { filters: false, search: true, date: true, display: false, sidebar: true, AttachmentView: Library }); this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) ); this.controller.on( 'edit:selection', this.editSelection ); this.createToolbar(); if ( this.options.sidebar ) { this.createSidebar(); } this.createUploader(); this.createAttachments(); this.updateContent(); if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { this.$el.addClass( 'hide-sidebar' ); if ( 'errors' === this.options.sidebar ) { this.$el.addClass( 'sidebar-for-errors' ); } } this.collection.on( 'add remove reset', this.updateContent, this ); }, editSelection: function( modal ) { modal.$( '.media-button-backToLibrary' ).focus(); }, /** * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining */ dispose: function() { this.options.selection.off( null, null, this ); View.prototype.dispose.apply( this, arguments ); return this; }, createToolbar: function() { var LibraryViewSwitcher, Filters, toolbarOptions; toolbarOptions = { controller: this.controller }; if ( this.controller.isModeActive( 'grid' ) ) { toolbarOptions.className = 'media-toolbar wp-filter'; } /** * @member {wp.media.view.Toolbar} */ this.toolbar = new Toolbar( toolbarOptions ); this.views.add( this.toolbar ); this.toolbar.set( 'spinner', new Spinner({ priority: -60 }) ); if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) { // "Filters" will return a , screen reader text needs to be rendered before this.toolbar.set( 'dateFilterLabel', new Label({ value: l10n.filterByDate, attributes: { 'for': 'media-attachment-date-filters' }, priority: -75 }).render() ); this.toolbar.set( 'dateFilter', new DateFilter({ controller: this.controller, model: this.collection.props, priority: -75 }).render() ); // BulkSelection is a
with subviews, including screen reader text this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ text: l10n.bulkSelect, controller: this.controller, priority: -70 }).render() ); this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ filters: Filters, style: 'primary', disabled: true, text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected, controller: this.controller, priority: -60, click: function() { var changed = [], removed = [], selection = this.controller.state().get( 'selection' ), library = this.controller.state().get( 'library' ); if ( ! selection.length ) { return; } if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { return; } if ( mediaTrash && 'trash' !== selection.at( 0 ).get( 'status' ) && ! window.confirm( l10n.warnBulkTrash ) ) { return; } selection.each( function( model ) { if ( ! model.get( 'nonces' )['delete'] ) { removed.push( model ); return; } if ( mediaTrash && 'trash' === model.get( 'status' ) ) { model.set( 'status', 'inherit' ); changed.push( model.save() ); removed.push( model ); } else if ( mediaTrash ) { model.set( 'status', 'trash' ); changed.push( model.save() ); removed.push( model ); } else { model.destroy({wait: true}); } } ); if ( changed.length ) { selection.remove( removed ); $.when.apply( null, changed ).then( _.bind( function() { library._requery( true ); this.controller.trigger( 'selection:action:done' ); }, this ) ); } else { this.controller.trigger( 'selection:action:done' ); } } }).render() ); if ( mediaTrash ) { this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ filters: Filters, style: 'primary', disabled: true, text: l10n.deleteSelected, controller: this.controller, priority: -55, click: function() { var removed = [], selection = this.controller.state().get( 'selection' ); if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { return; } selection.each( function( model ) { if ( ! model.get( 'nonces' )['delete'] ) { removed.push( model ); return; } model.destroy(); } ); selection.remove( removed ); this.controller.trigger( 'selection:action:done' ); } }).render() ); } } else if ( this.options.date ) { // DateFilter is a