ready: function() { /** * call 'ready' directly on the parent class */ PriorityList.prototype.ready.apply( this, arguments ); this.visibility();
// Set up aria tabs initial attributes. this.focusManager.setupAriaTabs(); },
set: function() { /** * call 'set' directly on the parent class */ PriorityList.prototype.set.apply( this, arguments ); this.visibility(); },
unset: function() { /** * call 'unset' directly on the parent class */ PriorityList.prototype.unset.apply( this, arguments ); this.visibility(); },
visibility: function() { var region = this.region, view = this.controller[ region ].get(), views = this.views.get(), hide = ! views || views.length < 2;
if ( this === view ) { // Flag this menu as hidden or visible. this.isVisible = ! hide; // Set or remove a CSS class to hide the menu. this.controller.$el.toggleClass( 'hide-' + region, hide ); } }, /** * @param {string} id */ select: function( id ) { var view = this.get( id );
if ( ! view ) { return; }
this.deselect(); view.$el.addClass('active');
// Set up again the aria tabs initial attributes after the menu updates. this.focusManager.setupAriaTabs(); },
/** * Attach a selection collection to the frame. * * A selection is a collection of attachments used for a specific purpose * by a media frame. e.g. Selecting an attachment (or many) to insert into * post content. * * @see media.model.Selection */ createSelection: function() { var selection = this.options.selection;
if ( ! (selection instanceof wp.media.model.Selection) ) { this.options.selection = new wp.media.model.Selection( selection, { multiple: this.options.multiple }); }
this._selection = { attachments: new wp.media.model.Attachments(), difference: [] }; },
editImageContent: function() { var image = this.state().get('image'), view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
this.content.set( view );
// After creating the wrapper view, load the actual editor via an Ajax call. view.loadEditor(); },
/** * Create the default states on the frame. */ createStates: function() { var options = this.options;
if ( this.options.states ) { return; }
// Add the default states. this.states.add([ // Main states. new wp.media.controller.Library({ library: wp.media.query( options.library ), multiple: options.multiple, title: options.title, priority: 20 }), new wp.media.controller.EditImage( { model: options.editImage } ) ]); },
/** * Bind region mode event callbacks. * * @see media.controller.Region.render */ bindHandlers: function() { this.on( 'router:create:browse', this.createRouter, this ); this.on( 'router:render:browse', this.browseRouter, this ); this.on( 'content:create:browse', this.browseContent, this ); this.on( 'content:render:upload', this.uploadContent, this ); this.on( 'toolbar:create:select', this.createSelectToolbar, this ); this.on( 'content:render:edit-image', this.editImageContent, this ); },
/** * Render callback for the router region in the `browse` mode. * * @param {wp.media.view.Router} routerView */ browseRouter: function( routerView ) { routerView.set({ upload: { text: l10n.uploadFilesTitle, priority: 20 }, browse: { text: l10n.mediaLibraryTitle, priority: 40 } }); },
/** * Render callback for the content region in the `browse` mode. * * @param {wp.media.controller.Region} contentRegion */ browseContent: function( contentRegion ) { var state = this.state();
/** * Render callback for the content region in the `upload` mode. */ uploadContent: function() { this.$el.removeClass( 'hide-toolbar' ); this.content.set( new wp.media.view.UploaderInline({ controller: this }) ); },
/** * wp.media.controller.Library * * A state for choosing an attachment or group of attachments from the media library. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.State * @augments Backbone.Model * @mixes media.selectionSync * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=library] Unique identifier. * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. * If one is not supplied, a collection of all attachments will be created. * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. * If the 'selection' attribute is a plain JS object, * a Selection will be created using its values as the selection instance's `props` model. * Otherwise, it will copy the library's `props` model. * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. * @param {string} [attributes.content=upload] Initial mode for the content region. * Overridden by persistent user setting if 'contentUserSetting' is true. * @param {string} [attributes.menu=default] Initial mode for the menu region. * @param {string} [attributes.router=browse] Initial mode for the router region. * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. * @param {boolean} [attributes.searchable=true] Whether the library is searchable. * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. * Accepts 'all', 'uploaded', or 'unattached'. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. */ Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{ defaults: { id: 'library', title: l10n.mediaLibraryTitle, multiple: false, content: 'upload', menu: 'default', router: 'browse', toolbar: 'select', searchable: true, filterable: false, sortable: true, autoSelect: true, describe: false, contentUserSetting: true, syncSelection: true },
/** * If a library isn't provided, query all media items. * If a selection instance isn't provided, create one. * * @since 3.5.0 */ initialize: function() { var selection = this.get('selection'), props;
/** * Whether an attachment is image. * * @since 4.4.1 * * @param {wp.media.model.Attachment} attachment * @return {boolean} */ isImageAttachment: function( attachment ) { // If uploading, we know the filename but not the mime type. if ( attachment.get('uploading') ) { return /\.(jpe?g|png|gif|webp|avif|heic|heif)$/i.test( attachment.get('filename') ); }
return attachment.get('type') === 'image'; },
/** * Whether an attachment can be embedded (audio or video). * * @since 3.6.0 * * @param {wp.media.model.Attachment} attachment * @return {boolean} */ canEmbed: function( attachment ) { // If uploading, we know the filename but not the mime type. if ( ! attachment.get('uploading') ) { var type = attachment.get('type'); if ( type !== 'audio' && type !== 'video' ) { return false; } }
/** * If the state is active, no items are selected, and the current * content mode is not an option in the state's router (provided * the state has a router), reset the content mode to the default. * * @since 3.5.0 */ refreshContent: function() { var selection = this.get('selection'), frame = this.frame, router = frame.router.get(), mode = frame.content.mode();
/** * Callback handler when an attachment is uploaded. * * Switch to the Media Library if uploaded from the 'Upload Files' tab. * * Adds any uploading attachments to the selection. * * If the state only supports one attachment to be selected and multiple * attachments are uploaded, the last attachment in the upload queue will * be selected. * * @since 3.5.0 * * @param {wp.media.model.Attachment} attachment */ uploading: function( attachment ) { var content = this.frame.content;
if ( 'upload' === content.mode() ) { this.frame.content.mode('browse'); }
/** * Persist the mode of the content region as a user setting. * * @since 3.5.0 */ saveContentMode: function() { if ( 'browse' !== this.get('router') ) { return; }
var mode = this.frame.content.mode(), view = this.frame.router.get();
// Make selectionSync available on any Media Library state. _.extend( Library.prototype, wp.media.selectionSync );
module.exports = Library;
/***/ }),
/***/ 705: /***/ ((module) => {
var State = wp.media.controller.State, Library = wp.media.controller.Library, l10n = wp.media.view.l10n, ImageDetails;
/** * wp.media.controller.ImageDetails * * A state for editing the attachment display settings of an image that's been * inserted into the editor. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=image-details] Unique identifier. * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. * @param {wp.media.model.Attachment} attributes.image The image's model. * @param {string|false} [attributes.content=image-details] Initial mode for the content region. * @param {string|false} [attributes.menu=false] Initial mode for the menu region. * @param {string|false} [attributes.router=false] Initial mode for the router region. * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. * @param {boolean} [attributes.editing=false] Unused. * @param {int} [attributes.priority=60] Unused. * * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, * however this may not do anything. */ ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{ defaults: _.defaults({ id: 'image-details', title: l10n.imageDetailsTitle, content: 'image-details', menu: false, router: false, toolbar: 'image-details', editing: false, priority: 60 }, Library.prototype.defaults ),
/** * Constrains navigation with the Tab key within the media view element. * * @since 4.0.0 * * @param {Object} event A keydown jQuery event. * * @return {void} */ constrainTabbing: function( event ) { var tabbables;
// Look for the tab key. if ( 9 !== event.keyCode ) { return; }
tabbables = this.getTabbables();
// Keep tab focus within media modal while it's open. if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { tabbables.first().focus(); return false; } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { tabbables.last().focus(); return false; } },
/** * Hides from assistive technologies all the body children. * * Sets an `aria-hidden="true"` attribute on all the body children except * the provided element and other elements that should not be hidden. * * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"` * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside * of the modal dialog and get hidden from assistive technologies. * * @since 5.2.3 * * @param {Object} visibleElement The jQuery object representing the element that should not be hidden. * * @return {void} */ setAriaHiddenOnBodyChildren: function( visibleElement ) { var bodyChildren, self = this;
if ( this.isBodyAriaHidden ) { return; }
// Get all the body children. bodyChildren = document.body.children;
// Loop through the body children and hide the ones that should be hidden. _.each( bodyChildren, function( element ) { // Don't hide the modal element. if ( element === visibleElement[0] ) { return; }
// Determine the body children to hide. if ( self.elementShouldBeHidden( element ) ) { element.setAttribute( 'aria-hidden', 'true' ); // Store the hidden elements. self.ariaHiddenElements.push( element ); } } );
this.isBodyAriaHidden = true; },
/** * Unhides from assistive technologies all the body children. * * Makes visible again to assistive technologies all the body children * previously hidden and stored in this.ariaHiddenElements. * * @since 5.2.3 * * @return {void} */ removeAriaHiddenFromBodyChildren: function() { _.each( this.ariaHiddenElements, function( element ) { element.removeAttribute( 'aria-hidden' ); } );
/** * Determines if the passed element should not be hidden from assistive technologies. * * @since 5.2.3 * * @param {Object} element The DOM element that should be checked. * * @return {boolean} Whether the element should not be hidden from assistive technologies. */ elementShouldBeHidden: function( element ) { var role = element.getAttribute( 'role' ), liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ];
/* * Don't hide scripts, elements that already have `aria-hidden`, and * ARIA live regions. */ return ! ( element.tagName === 'SCRIPT' || element.hasAttribute( 'aria-hidden' ) || element.hasAttribute( 'aria-live' ) || liveRegionsRoles.indexOf( role ) !== -1 ); },
/** * Whether the body children are hidden from assistive technologies. * * @since 5.2.3 */ isBodyAriaHidden: false,
/** * Stores an array of DOM elements that should be hidden from assistive * technologies, for example when the media modal dialog opens. * * @since 5.2.3 */ ariaHiddenElements: [],
/** * Holds the jQuery collection of ARIA tabs. * * @since 5.3.0 */ tabs: $(),
// Make Up and Down arrow keys do nothing with horizontal tabs. if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { return; }
// Make Left and Right arrow keys do nothing with vertical tabs. if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { return; }
initialize: function() { /** * Create a model with the provided `defaults`. * * @member {Backbone.Model} */ this.model = new Backbone.Model( this.defaults );
// If any of the `options` have a key from `defaults`, apply its // value to the `model` and remove it from the `options object. _.each( this.defaults, function( def, key ) { var value = this.options[ key ]; if ( _.isUndefined( value ) ) { return; }
this.model.set( key, value ); delete this.options[ key ]; }, this );
this.listenTo( this.model, 'change', this.render ); }, /** * @return {wp.media.view.Button} Returns itself to allow chaining. */ render: function() { var classes = [ 'button', this.className ], model = this.model.toJSON();
/** * wp.media.view.Frame * * A frame is a composite view consisting of one or more regions and one or more * states. * * @memberOf wp.media.view * * @see wp.media.controller.State * @see wp.media.controller.Region * * @class * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View * @mixes wp.media.controller.StateMachine */ var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{ initialize: function() { _.defaults( this.options, { mode: [ 'select' ] }); this._createRegions(); this._createStates(); this._createModes(); },
_createRegions: function() { // Clone the regions array. this.regions = this.regions ? this.regions.slice() : [];
// Initialize regions. _.each( this.regions, function( region ) { this[ region ] = new wp.media.controller.Region({ view: this, id: region, selector: '.media-frame-' + region }); }, this ); }, /** * Create the frame's states. * * @see wp.media.controller.State * @see wp.media.controller.StateMachine * * @fires wp.media.controller.State#ready */ _createStates: function() { // Create the default `states` collection. this.states = new Backbone.Collection( null, { model: wp.media.controller.State });
// Ensure states have a reference to the frame. this.states.on( 'add', function( model ) { model.frame = this; model.trigger('ready'); }, this );
if ( this.options.states ) { this.states.add( this.options.states ); } },
/** * A frame can be in a mode or multiple modes at one time. * * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. */ _createModes: function() { // Store active "modes" that the frame is in. Unrelated to region modes. this.activeModes = new Backbone.Collection(); this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
_.each( this.options.mode, function( mode ) { this.activateMode( mode ); }, this ); }, /** * Reset all states on the frame to their defaults. * * @return {wp.media.view.Frame} Returns itself to allow chaining. */ reset: function() { this.states.invoke( 'trigger', 'reset' ); return this; }, /** * Map activeMode collection events to the frame. */ triggerModeEvents: function( model, collection, options ) { var collectionEvent, modeEventMap = { add: 'activate', remove: 'deactivate' }, eventToTrigger; // Probably a better way to do this. _.each( options, function( value, key ) { if ( value ) { collectionEvent = key; } } );
eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; this.trigger( eventToTrigger ); }, /** * Activate a mode on the frame. * * @param string mode Mode ID. * @return {this} Returns itself to allow chaining. */ activateMode: function( mode ) { // Bail if the mode is already active. if ( this.isModeActive( mode ) ) { return; } this.activeModes.add( [ { id: mode } ] ); // Add a CSS class to the frame so elements can be styled for the mode. this.$el.addClass( 'mode-' + mode );
/** * wp.media.controller.FeaturedImage * * A state for selecting a featured image for a post. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=featured-image] Unique identifier. * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. * If one is not supplied, a collection of all images will be created. * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. * @param {string} [attributes.content=upload] Initial mode for the content region. * Overridden by persistent user setting if 'contentUserSetting' is true. * @param {string} [attributes.menu=default] Initial mode for the menu region. * @param {string} [attributes.router=browse] Initial mode for the router region. * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. * @param {int} [attributes.priority=60] The priority for the state link in the media menu. * @param {boolean} [attributes.searchable=true] Whether the library is searchable. * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. * Accepts 'all', 'uploaded', or 'unattached'. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. */ FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{ defaults: _.defaults({ id: 'featured-image', title: l10n.setFeaturedImageTitle, multiple: false, filterable: 'uploaded', toolbar: 'featured-image', priority: 60, syncSelection: true }, Library.prototype.defaults ),
// If we haven't been provided a `library`, create a `Selection`. if ( ! this.get('library') ) { this.set( 'library', wp.media.query({ type: 'image' }) ); }
// 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 );
// 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') ); },
return data; }, /** * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. */ dispose: function() { if ( this.disposing ) { /** * call 'dispose' directly on the parent class */ return View.prototype.dispose.apply( this, arguments ); }
/* * Run remove on `dispose`, so we can be sure to refresh the * uploader with a view-less DOM. Track whether we're disposing * so we don't trigger an infinite loop. */ this.disposing = true; return this.remove(); }, /** * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. */ remove: function() { /** * call 'remove' directly on the parent class */ var result = View.prototype.remove.apply( this, arguments );
_.defer( _.bind( this.refresh, this ) ); return result; },
refresh: function() { var uploader = this.controller.uploader;
// Handle text inputs and textareas. } else if ( $setting.is('input[type="text"], textarea') ) { if ( ! $setting.is(':focus') ) { $setting.val( value ); } // Handle checkboxes. } else if ( $setting.is('input[type="checkbox"]') ) { $setting.prop( 'checked', !! value && 'false' !== value ); } }, /** * @param {Object} event */ updateHandler: function( event ) { var $setting = $( event.target ).closest('[data-setting]'), value = event.target.value, userSetting;
event.preventDefault();
if ( ! $setting.length ) { return; }
// Use the correct value for checkboxes. if ( $setting.is('input[type="checkbox"]') ) { value = $setting[0].checked; }
// Update the corresponding setting. this.model.set( $setting.data('setting'), value );
// If the setting has a corresponding user setting, // update that as well. userSetting = $setting.data('userSetting'); if ( userSetting ) { window.setUserSetting( userSetting, value ); } },
updateChanges: function( model ) { if ( model.hasChanged() ) { _( model.changed ).chain().keys().each( this.update, this ); } } });
var Library = wp.media.controller.Library, l10n = wp.media.view.l10n, GalleryEdit;
/** * wp.media.controller.GalleryEdit * * A state for editing a gallery's images and settings. * * @since 3.5.0 * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @memberOf wp.media.controller * * @param {Object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=gallery-edit] Unique identifier. * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. * If one is not supplied, an empty media.model.Selection collection is created. * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. * @param {boolean} [attributes.searchable=false] Whether the library is searchable. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. * @param {string|false} [attributes.content=browse] Initial mode for the content region. * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. * @param {number} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. * @param {number} [attributes.priority=60] The priority for the state link in the media menu. * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. * Defaults to false for this state, because the library passed in *is* the selection. * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. */ GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{ defaults: { id: 'gallery-edit', title: l10n.editGalleryTitle, multiple: false, searchable: false, sortable: true, date: false, display: false, content: 'browse', toolbar: 'gallery-edit', describe: true, displaySettings: true, dragInfo: true, idealColumnWidth: 170, editing: false, priority: 60, syncSelection: false },
/** * Initializes the library. * * Creates a selection if a library isn't supplied and creates an attachment * view if no attachment view is supplied. * * @since 3.5.0 * * @return {void} */ initialize: function() { // If we haven't been provided a `library`, create a `Selection`. if ( ! this.get('library') ) { this.set( 'library', new wp.media.model.Selection() ); }
// The single `Attachment` view to be used in the `Attachments` view. if ( ! this.get('AttachmentView') ) { this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); }
/** * Activates the library. * * Limits the library to images, watches for uploaded attachments. Watches for * the browse event on the frame and binds it to gallerySettings. * * @since 3.5.0 * * @return {void} */ activate: function() { var library = this.get('library');
// Limit the library to images only. library.props.set( 'type', 'image' );
// Watch for uploaded attachments. this.get('library').observe( wp.Uploader.queue );
this.frame.on( 'content:render:browse', this.gallerySettings, this );
search: _.debounce( function( event ) { var searchTerm = event.target.value.trim();
// Trigger the search only after 2 ASCII characters. if ( searchTerm && searchTerm.length > 1 ) { this.model.set( 'search', searchTerm ); } else { this.model.unset( 'search' ); } }, 500 ) });
module.exports = Search;
/***/ }),
/***/ 2275: /***/ ((module) => {
var Library = wp.media.controller.Library, l10n = wp.media.view.l10n, ReplaceImage;
/** * wp.media.controller.ReplaceImage * * A state for replacing an image. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=replace-image] Unique identifier. * @param {string} [attributes.title=Replace Image] Title for the state. Displays in the media menu and the frame's title region. * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. * If one is not supplied, a collection of all images will be created. * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. * @param {string} [attributes.content=upload] Initial mode for the content region. * Overridden by persistent user setting if 'contentUserSetting' is true. * @param {string} [attributes.menu=default] Initial mode for the menu region. * @param {string} [attributes.router=browse] Initial mode for the router region. * @param {string} [attributes.toolbar=replace] Initial mode for the toolbar region. * @param {int} [attributes.priority=60] The priority for the state link in the media menu. * @param {boolean} [attributes.searchable=true] Whether the library is searchable. * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. * Accepts 'all', 'uploaded', or 'unattached'. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. */ ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{ defaults: _.defaults({ id: 'replace-image', title: l10n.replaceImageTitle, multiple: false, filterable: 'uploaded', toolbar: 'replace', menu: false, priority: 60, syncSelection: true }, Library.prototype.defaults ),
this.image = options.image; // If we haven't been provided a `library`, create a `Selection`. if ( ! this.get('library') ) { this.set( 'library', wp.media.query({ type: 'image' }) ); }
// 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 );
// 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') ); },
// Hide the modal element by adding display:none. this.$el.hide();
/* * Make visible again to assistive technologies all body children that * have been made hidden when the modal opened. */ this.focusManager.removeAriaHiddenFromBodyChildren();
// Move focus back in useful location once modal is closed. if ( null !== this.clickedOpenerEl ) { // Move focus back to the element that opened the modal. this.clickedOpenerEl.focus(); } else { // Fallback to the admin page main element. $( '#wpbody-content' ) .attr( 'tabindex', '-1' ) .trigger( 'focus' ); }
this.propagate('close');
if ( options && options.escape ) { this.propagate('escape'); }
/** * Handles the selection of attachments when the command or control key is pressed with the enter key. * * @since 6.7 * * @param {Object} event The keydown event object. */ selectHandler: function( event ) { var selection = this.controller.state().get( 'selection' );
/** * Triggers a modal event and if the `propagate` option is set, * forwards events to the modal's controller. * * @param {string} id * @return {wp.media.view.Modal} Returns itself to allow chaining. */ propagate: function( id ) { this.trigger( id );
if ( this.options.propagate ) { this.controller.trigger( id ); }
return this; }, /** * @param {Object} event */ keydown: function( event ) { // Close the modal when escape is pressed. if ( 27 === event.which && this.$el.is(':visible') ) { this.escape(); event.stopImmediatePropagation(); }
// Select the attachment when command or control and enter are pressed. if ( ( 13 === event.which || 10 === event.which ) && ( event.metaKey || event.ctrlKey ) ) { this.selectHandler( event ); event.stopImmediatePropagation(); }
} });
module.exports = Modal;
/***/ }),
/***/ 2650: /***/ ((module) => {
var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, $ = jQuery, ImageDetails;
// Ensure core UI is enabled. this.$el.addClass('wp-core-ui');
// Initialize modal container view. if ( this.options.modal ) { this.modal = new wp.media.view.Modal({ controller: this, title: this.options.title });
this.modal.content( this ); }
// Force the uploader off if the upload limit has been exceeded or // if the browser isn't supported. if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { this.options.uploader = false; }
this.on( 'attach', _.bind( this.views.ready, this.views ), this );
// Bind default title creation. this.on( 'title:create:default', this.createTitle, this ); this.title.mode('default');
// Bind default menu. this.on( 'menu:create:default', this.createMenu, this );
// Set the menu ARIA tab panel attributes when the modal opens. this.on( 'open', this.setMenuTabPanelAriaAttributes, this ); // Set the router ARIA tab panel attributes when the modal opens. this.on( 'open', this.setRouterTabPanelAriaAttributes, this );
// Update the menu ARIA tab panel attributes when the content updates. this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this ); // Update the router ARIA tab panel attributes when the content updates. this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this ); },
/** * Sets the attributes to be used on the menu ARIA tab panel. * * @since 5.3.0 * * @return {void} */ setMenuTabPanelAriaAttributes: function() { var stateId = this.state().get( 'id' ), tabPanelEl = this.$el.find( '.media-frame-tab-panel' ), ariaLabelledby;
// Set the tab panel attributes only if the tabs are visible. tabPanelEl .attr( { role: 'tabpanel', 'aria-labelledby': ariaLabelledby, tabIndex: '0' } ); } },
/** * Sets the attributes to be used on the router ARIA tab panel. * * @since 5.3.0 * * @return {void} */ setRouterTabPanelAriaAttributes: function() { var tabPanelEl = this.$el.find( '.media-frame-content' ), ariaLabelledby;
// Set the tab panel attributes only if the tabs are visible. if ( this.state().get( 'router' ) && this.routerView && this.routerView.isVisible && this.content._mode ) { ariaLabelledby = 'menu-item-' + this.content._mode;
// The single `Attachment` view to be used in the `Attachments` view. AttachmentView: wp.media.view.Attachment.Selection }); // Call 'initialize' directly on the parent class. return Attachments.prototype.initialize.apply( this, arguments ); } });
// Bail if not enabled or UA does not support drag'n'drop or File API. if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { return this; }
refresh: function( e ) { var dropzone_id; for ( dropzone_id in this.dropzones ) { // Hide the dropzones only if dragging has left the screen. this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); }
if ( ! _.isUndefined( e ) ) { $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); }
attach: function( index, editor ) { // Attach a dropzone to an editor. var dropzone = this.$el.clone(); this.dropzones.push( dropzone ); $( editor ).append( dropzone ); return this; },
/** * When a file is dropped on the editor uploader, open up an editor media workflow * and upload the file immediately. * * @param {jQuery.Event} event The 'drop' event. */ drop: function( event ) { var $wrap, uploadView;
// Set the active editor to the drop target. $wrap = $( event.target ).parents( '.wp-editor-wrap' ); if ( $wrap.length > 0 && $wrap[0].id ) { window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); }
dropzoneDragleave: function( e ) { this.overDropzone = false; _.delay( _.bind( this.refresh, this, e ), 50 ); },
click: function( e ) { // In the rare case where the dropzone gets stuck, hide it on click. this.containerDragleave( e ); this.dropzoneDragleave( e ); this.localDrag = false; } });
// On click, just select the model, instead of removing the model from // the selection. toggleSelection: function() { this.options.selection.single( this.model ); } });
// Don't do anything inside inputs and on the attachment check and remove buttons. if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 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; }
// Avoid toggles when the command or control key is pressed with the enter key to prevent deselecting the last selected attachment. if ( ( event.metaKey || event.ctrlKey ) && ( 13 === event.keyCode || 10 === event.keyCode ) ) { return; }
// 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 );
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 ); } },
/* * 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;
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; }
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 ) { /** * @function _syncCaption * @memberOf wp.media.view.Attachment * @instance * * @param {Backbone.Model} model * @param {string} value * @return {wp.media.view.Attachment} Returns itself to allow chaining. */ /** * @function _syncTitle * @memberOf wp.media.view.Attachment * @instance * * @param {Backbone.Model} model * @param {string} value * @return {wp.media.view.Attachment} Returns itself to allow chaining. */ /** * @function _syncArtist * @memberOf wp.media.view.Attachment * @instance * * @param {Backbone.Model} model * @param {string} value * @return {wp.media.view.Attachment} Returns itself to allow chaining. */ /** * @function _syncAlbum * @memberOf wp.media.view.Attachment * @instance * * @param {Backbone.Model} model * @param {string} value * @return {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;
/***/ }),
/***/ 4181: /***/ ((module) => {
/** * wp.media.selectionSync * * Sync an attachments selection in a state with another state. * * Allows for selecting multiple images in the Add Media workflow, and then * switching to the Insert Gallery workflow while preserving the attachments selection. * * @memberOf wp.media * * @mixin */ var selectionSync = { /** * @since 3.5.0 */ syncSelection: function() { var selection = this.get('selection'), manager = this.frame._selection;
/* * If the selection supports multiple items, validate the stored * attachments based on the new selection's conditions. Record * the attachments that are not included; we'll maintain a * reference to those. Other attachments are considered in flux. */ if ( selection.multiple ) { selection.reset( [], { silent: true }); selection.validateAll( manager.attachments ); manager.difference = _.difference( manager.attachments.models, selection.models ); }
// Sync the selection's single item with the master. selection.single( manager.single ); },
/** * Record the currently active attachments, which is a combination * of the selection's attachments and the set of selected * attachments that this specific selection considered invalid. * Reset the difference and record the single attachment. * * @since 3.5.0 */ recordSelection: function() { var selection = this.get('selection'), manager = this.frame._selection;
// Call 'initialize' directly on the parent class. Select.prototype.initialize.apply( this, arguments ); this.createIframeStates();
},
/** * Create the default states. */ createStates: function() { var options = this.options;
this.states.add([ // Main states. new Library({ id: 'insert', title: l10n.insertMediaTitle, priority: 20, toolbar: 'main-insert', filterable: 'all', library: wp.media.query( options.library ), multiple: options.multiple ? 'reset' : false, editable: true,
// If the user isn't allowed to edit fields, // can they still edit it locally? allowLocalEdits: true,
// Show the attachment display settings. displaySettings: true, // Update user settings when users adjust the // attachment display settings. displayUserSettings: true }),
// Only bother checking media type counts if one of the counts is zero. checkCounts = _.find( this.counts, function( type ) { return type.count === 0; } );
this.on( 'menu:create:gallery', this.createMenu, this ); this.on( 'menu:create:playlist', this.createMenu, this ); this.on( 'menu:create:video-playlist', this.createMenu, this ); this.on( 'toolbar:create:main-insert', this.createToolbar, this ); this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
_.each( handlers, function( regionHandlers, region ) { _.each( regionHandlers, function( callback, handler ) { this.on( region + ':render:' + handler, this[ callback ], this ); }, this ); }, this ); },
activate: function() { // Hide menu items for states tied to particular media types if there are no items. _.each( this.counts, function( type ) { if ( type.count < 1 ) { this.menuItemVisibility( type.state, 'hide' ); } }, this ); },
// Move focus to the modal after canceling a Gallery. this.controller.modal.focusManager.focus(); } }, separateCancel: new wp.media.View({ className: 'separator', priority: 40 }) }); },
// Move focus to the modal after canceling an Audio Playlist. this.controller.modal.focusManager.focus(); } }, separateCancel: new wp.media.View({ className: 'separator', priority: 40 }) }); },
// Move focus to the modal after canceling a Video Playlist. this.controller.modal.focusManager.focus(); } }, separateCancel: new wp.media.View({ className: 'separator', priority: 40 }) }); },
// Content. embedContent: function() { var view = new wp.media.view.Embed({ controller: this, model: this.state() }).render();
this.content.set( view ); },
editSelectionContent: function() { var state = this.state(), selection = state.get('selection'), view;
click: function() { this.controller.content.mode('browse'); // Move focus to the modal when jumping back from Edit Selection to Add Media view. this.controller.modal.focusManager.focus(); } });
// Browse our library of attachments. this.content.set( view );
// Trigger the controller to set focus. this.trigger( 'edit:selection', this ); },
editImageContent: function() { var image = this.state().get('image'), view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
this.content.set( view );
// After creating the wrapper view, load the actual editor via an Ajax call. view.loadEditor();
view.set( 'selection', new wp.media.view.Selection({ controller: this, collection: this.state().get('selection'), priority: -40,
// If the selection is editable, pass the callback to // switch the content mode. editable: editable && function() { this.controller.content.mode('edit-selection'); } }).render() ); },
/** * @fires wp.media.controller.State#reset */ click: function() { var controller = this.controller, state = controller.state(), edit = controller.state('gallery-edit');
edit.get('library').add( state.get('selection').models ); state.trigger('reset'); controller.setState('gallery-edit'); // Move focus to the modal when jumping back from Add to Gallery to Edit Gallery view. this.controller.modal.focusManager.focus(); } } } }) ); },
/** * @fires wp.media.controller.State#reset */ click: function() { var controller = this.controller, state = controller.state(), edit = controller.state('playlist-edit');
edit.get('library').add( state.get('selection').models ); state.trigger('reset'); controller.setState('playlist-edit'); // Move focus to the modal when jumping back from Add to Audio Playlist to Edit Audio Playlist view. this.controller.modal.focusManager.focus(); } } } }) ); },
click: function() { var controller = this.controller, state = controller.state(), edit = controller.state('video-playlist-edit');
edit.get('library').add( state.get('selection').models ); state.trigger('reset'); controller.setState('video-playlist-edit'); // Move focus to the modal when jumping back from Add to Video Playlist to Edit Video Playlist view. this.controller.modal.focusManager.focus(); } } } }) ); } });
/** * wp.media.View * * The base view class for media. * * Undelegating events, removing events from the model, and * removing events from the controller mirror the code for * `Backbone.View.dispose` in Backbone 0.9.8 development. * * This behavior has since been removed, and should not be used * outside of the media manager. * * @memberOf wp.media * * @class * @augments wp.Backbone.View * @augments Backbone.View */ var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{ constructor: function( options ) { if ( options && options.controller ) { this.controller = options.controller; } wp.Backbone.View.apply( this, arguments ); }, /** * @todo The internal comment mentions this might have been a stop-gap * before Backbone 0.9.8 came out. Figure out if Backbone core takes * care of this in Backbone.View now. * * @return {wp.media.View} Returns itself to allow chaining. */ dispose: function() { /* * Undelegating events, removing events from the model, and * removing events from the controller mirror the code for * `Backbone.View.dispose` in Backbone 0.9.8 development. */ this.undelegateEvents();
if ( this.model && this.model.off ) { this.model.off( null, null, this ); }
if ( this.collection && this.collection.off ) { this.collection.off( null, null, this ); }
// Unbind controller events. if ( this.controller && this.controller.off ) { this.controller.off( null, null, this ); }
return this; }, /** * @return {wp.media.View} Returns itself to allow chaining. */ remove: function() { this.dispose(); /** * call 'remove' directly on the parent class */ return wp.Backbone.View.prototype.remove.apply( this, arguments ); } });
initialize: function() { this.controller.on( 'content:render', this.update, this ); // Call 'initialize' directly on the parent class. Menu.prototype.initialize.apply( this, arguments ); },
update: function() { var mode = this.controller.content.mode(); if ( mode ) { this.select( mode ); } } });
module.exports = Router;
/***/ }),
/***/ 4910: /***/ ((module) => {
var l10n = wp.media.view.l10n, $ = Backbone.$, Embed;
/** * wp.media.controller.Embed * * A state for embedding media from a URL. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} attributes The attributes hash passed to the state. * @param {string} [attributes.id=embed] Unique identifier. * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. * @param {string} [attributes.content=embed] Initial mode for the content region. * @param {string} [attributes.menu=default] Initial mode for the menu region. * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. * @param {string} [attributes.menu=false] Initial mode for the menu region. * @param {int} [attributes.priority=120] The priority for the state link in the media menu. * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. * @param {string} [attributes.url] The embed URL. * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. */ Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{ defaults: { id: 'embed', title: l10n.insertFromUrlTitle, content: 'embed', menu: 'default', toolbar: 'main-embed', priority: 120, type: 'link', url: '', metadata: {} },
// The amount of time used when debouncing the scan. sensitivity: 400,
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 ); },
/** * Trigger a scan of the embedded URL's content for metadata required to embed. * * @fires wp.media.controller.Embed#scan */ scan: function() { var scanners, embed = this, attributes = { type: 'link', scanners: [] };
/* * Scan is triggered with the list of `attributes` to set on the * state, useful for the 'type' attribute and 'scanners' attribute, * an array of promise objects for asynchronous scan operations. */ if ( this.props.get('url') ) { this.trigger( 'scan', attributes ); }
/** * wp.media.view.Toolbar * * A toolbar which consists of a primary and a secondary section. Each sections * can be filled with views. * * @memberOf wp.media.view * * @class * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ tagName: 'div', className: 'media-toolbar',
initialize: function() { var state = this.controller.state(), selection = this.selection = state.get('selection'), library = this.library = state.get('library');
this._views = {};
// The toolbar is composed of two `PriorityList` views. this.primary = new wp.media.view.PriorityList(); this.secondary = new wp.media.view.PriorityList(); this.tertiary = new wp.media.view.PriorityList(); this.primary.$el.addClass('media-toolbar-primary search-form'); this.secondary.$el.addClass('media-toolbar-secondary'); this.tertiary.$el.addClass('media-bg-overlay');
// Accept an object with an `id` : `view` mapping. if ( _.isObject( id ) ) { _.each( id, function( view, id ) { this.set( id, view, { silent: true }); }, this );
// Prevent insertion of attachments if any of them are still uploading. if ( selection && selection.models ) { disabled = _.some( selection.models, function( attachment ) { return attachment.get('uploading') === true; }); } if ( requires.uploadingComplete && modelsUploading ) { disabled = true; }
/** * wp.media.controller.Cropper * * A class for cropping an image when called from the header media customization panel. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.State * @augments Backbone.Model */ Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{ defaults: { id: 'cropper', title: l10n.cropImage, // Region mode defaults. toolbar: 'crop', content: 'crop', router: false, canSkipCrop: false,
// Default doCrop Ajax arguments to allow the Customizer (for example) to inject state. doCropArgs: {} },
/** * Shows the crop image window when called from the Add new image button. * * @since 4.2.0 * * @return {void} */ activate: function() { this.frame.on( 'content:create:crop', this.createCropContent, this ); this.frame.on( 'close', this.removeCropper, this ); this.set('selection', new Backbone.Collection(this.frame._selection.single)); },
/** * Changes the state of the toolbar window to browse mode. * * @since 4.2.0 * * @return {void} */ deactivate: function() { this.frame.toolbar.mode('browse'); },
/** * Creates the crop image window. * * Initialized when clicking on the Select and Crop button. * * @since 4.2.0 * * @fires crop window * * @return {void} */ createCropContent: function() { this.cropperView = new wp.media.view.Cropper({ controller: this, attachment: this.get('selection').first() }); this.cropperView.on('image-loaded', this.createCropToolbar, this); this.frame.content.set(this.cropperView);
},
/** * Removes the image selection and closes the cropping window. * * @since 4.2.0 * * @return {void} */ removeCropper: function() { this.imgSelect.cancelSelection(); this.imgSelect.setOptions({remove: true}); this.imgSelect.update(); this.cropperView.remove(); },
/** * Checks if cropping can be skipped and creates crop toolbar accordingly. * * @since 4.2.0 * * @return {void} */ createCropToolbar: function() { var canSkipCrop, hasRequiredAspectRatio, suggestedCropSize, toolbarOptions;
click: function() { var controller = this.controller, state = controller.state();
controller.close();
// Not sure if we want to use wp.media.string.image which will create a shortcode or // perhaps wp.html.string to at least to build the <img />. state.trigger( 'update', controller.image.toJSON() );
// Restore and reset the default state. controller.setState( controller.options.state ); controller.reset(); } } } }) ); },
// Not sure if we want to use wp.media.string.image which will create a shortcode or // perhaps wp.html.string to at least to build the <img />. state.trigger( 'replace', controller.image.toJSON() );
// Restore and reset the default state. controller.setState( controller.options.state ); controller.reset(); } } } }) ); }
});
module.exports = ImageDetails;
/***/ }),
/***/ 5663: /***/ ((module) => {
var l10n = wp.media.view.l10n, EditImage;
/** * wp.media.controller.EditImage * * A state for editing (cropping, etc.) an image. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} attributes The attributes hash passed to the state. * @param {wp.media.model.Attachment} attributes.model The attachment. * @param {string} [attributes.id=edit-image] Unique identifier. * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. * @param {string} [attributes.content=edit-image] Initial mode for the content region. * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. * @param {string} [attributes.menu=false] Initial mode for the menu region. * @param {string} [attributes.url] Unused. @todo Consider removal. */ EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{ defaults: { id: 'edit-image', title: l10n.editImage, menu: false, toolbar: 'edit-image', content: 'edit-image', url: '' },
/** * Activates a frame for editing a featured image. * * @since 3.9.0 * * @return {void} */ activate: function() { this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) ); },
/** * Deactivates a frame for editing a featured image. * * @since 3.9.0 * * @return {void} */ deactivate: function() { this.frame.off( 'toolbar:render:edit-image' ); },
/** * Adds a toolbar with a back button. * * When the back button is pressed it checks whether there is a previous state. * In case there is a previous state it sets that previous state otherwise it * closes the frame. * * @since 3.9.0 * * @return {void} */ toolbar: function() { var frame = this.frame, lastState = frame.lastState(), previous = lastState && lastState.id;
/** * wp.media.controller.State * * A state is a step in a workflow that when set will trigger the controllers * for the regions to be updated as specified in the frame. * * A state has an event-driven lifecycle: * * 'ready' triggers when a state is added to a state machine's collection. * 'activate' triggers when a state is activated by a state machine. * 'deactivate' triggers when a state is deactivated by a state machine. * 'reset' is not triggered automatically. It should be invoked by the * proper controller to reset the state to its default. * * @memberOf wp.media.controller * * @class * @augments Backbone.Model */ var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{ /** * Constructor. * * @since 3.5.0 */ constructor: function() { this.on( 'activate', this._preActivate, this ); this.on( 'activate', this.activate, this ); this.on( 'activate', this._postActivate, this ); this.on( 'deactivate', this._deactivate, this ); this.on( 'deactivate', this.deactivate, this ); this.on( 'reset', this.reset, this ); this.on( 'ready', this._ready, this ); this.on( 'ready', this.ready, this ); /** * Call parent constructor with passed arguments */ Backbone.Model.apply( this, arguments ); this.on( 'change:menu', this._updateMenu, this ); }, /** * Ready event callback. * * @abstract * @since 3.5.0 */ ready: function() {},
if ( this.frame.menu ) { actionMenuItems = this.frame.menu.get('views'), actionMenuLength = actionMenuItems ? actionMenuItems.views.get().length : 0, // Show action menu only if it is active and has more than one default element. this.frame.$el.toggleClass( 'hide-menu', ! mode || actionMenuLength < 2 ); } if ( ! mode ) { return; }
/** * @since 3.5.0 * @access private */ _updateMenu: function() { var previous = this.previous('menu'), menu = this.get('menu');
if ( previous ) { this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); }
if ( menu ) { this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); } },
/** * Create a view in the media menu for the state. * * @since 3.5.0 * @access private * * @param {media.view.Menu} view The menu view. */ _renderMenu: function( view ) { var menuItem = this.get('menuItem'), title = this.get('title'), priority = this.get('priority');
if ( ! menuItem && title ) { menuItem = { text: title };
if ( priority ) { menuItem.priority = priority; } }
if ( ! menuItem ) { return; }
view.set( this.id, menuItem ); } });
_.each(['toolbar','content'], function( region ) { /** * @access private */ State.prototype[ '_' + region ] = function() { var mode = this.get( region ); if ( mode ) { this.frame[ region ].render( mode ); } }; });
/* * Reset all the attributes inherited from Attachment including role=checkbox, * tabindex, etc., as they are inappropriate for this view. See #47458 and [30483] / #30390. */ attributes: {},
/** * Moves focus to the previous or next attachment in the grid. * Fallbacks to the upload button or media frame when there are no attachments. * * @since 5.3.0 */ moveFocus: function() { if ( this.previousAttachment.length ) { this.previousAttachment.trigger( 'focus' ); return; }
if ( this.nextAttachment.length ) { this.nextAttachment.trigger( 'focus' ); return; }
// Fallback: move focus to the "Select Files" button in the media modal. if ( this.controller.uploader && this.controller.uploader.$browser ) { this.controller.uploader.$browser.trigger( 'focus' ); return; }
// Last fallback. this.moveFocusToLastFallback(); },
/** * Moves focus to the media frame as last fallback. * * @since 5.3.0 */ moveFocusToLastFallback: function() { // Last fallback: make the frame focusable and move focus to it. $( '.media-frame' ) .attr( 'tabindex', '-1' ) .trigger( 'focus' ); },
/** * Deletes an attachment. * * Deletes an attachment after asking for confirmation. After deletion, * keeps focus in the modal. * * @since 3.5.0 * * @param {MouseEvent} event A click event. * * @return {void} */ deleteAttachment: function( event ) { event.preventDefault();
/** * Sets the Trash state on an attachment, or destroys the model itself. * * If the mediaTrash setting is set to true, trashes the attachment. * Otherwise, the model itself is destroyed. * * @since 3.9.0 * * @param {MouseEvent} event A click event. * * @return {void} */ trashAttachment: function( event ) { var library = this.controller.library, self = this; event.preventDefault();
this.getFocusableElements();
// When in the Media Library and the Media Trash is enabled. 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 ); /* * @todo We need to move focus back to the previous, next, or first * attachment but the library gets re-queried and refreshed. * Thus, the references to the previous attachments are lost. * We need an alternate method. */ self.moveFocusToLastFallback(); } ); } else { this.model.destroy(); this.moveFocus(); } },
wp.media.mixin.removeAllPlayers(); this.$( 'audio, video' ).each( function (i, elem) { var el = wp.media.view.MediaDetails.prepareSrc( elem ); new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); } ); } });
/** * wp.media.controller.StateMachine * * A state machine keeps track of state. It is in one state at a time, * and can change from one state to another. * * States are stored as models in a Backbone collection. * * @memberOf wp.media.controller * * @since 3.5.0 * * @class * @augments Backbone.Model * @mixin * @mixes Backbone.Events */ var StateMachine = function() { return { // Use Backbone's self-propagating `extend` inheritance method. extend: Backbone.Model.extend }; };
_.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{ /** * Fetch a state. * * If no `id` is provided, returns the active state. * * Implicitly creates states. * * Ensure that the `states` collection exists so the `StateMachine` * can be used as a mixin. * * @since 3.5.0 * * @param {string} id * @return {wp.media.controller.State} Returns a State model from * the StateMachine collection. */ state: function( id ) { this.states = this.states || new Backbone.Collection();
// Default to the active state. id = id || this._state;
if ( id && ! this.states.get( id ) ) { this.states.add({ id: id }); } return this.states.get( id ); },
/** * Sets the active state. * * Bail if we're trying to select the current state, if we haven't * created the `states` collection, or are trying to select a state * that does not exist. * * @since 3.5.0 * * @param {string} id * * @fires wp.media.controller.State#deactivate * @fires wp.media.controller.State#activate * * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. */ setState: function( id ) { var previous = this.state();
if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { return this; }
if ( previous ) { previous.trigger('deactivate'); this._lastState = previous.id; }
/** * Returns the previous active state. * * Call the `state()` method with no parameters to retrieve the current * active state. * * @since 3.5.0 * * @return {wp.media.controller.State} Returns a State model from * the StateMachine collection. */ lastState: function() { if ( this._lastState ) { return this.state( this._lastState ); } } });
// Map all event binding and triggering on a StateMachine to its `states` collection. _.each([ 'on', 'off', 'trigger' ], function( method ) { /** * @function on * @memberOf wp.media.controller.StateMachine * @instance * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. */ /** * @function off * @memberOf wp.media.controller.StateMachine * @instance * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. */ /** * @function trigger * @memberOf wp.media.controller.StateMachine * @instance * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. */ StateMachine.prototype[ method ] = function() { // Ensure that the `states` collection exists so the `StateMachine` // can be used as a mixin. this.states = this.states || new Backbone.Collection(); // Forward the method to the `states` collection. this.states[ method ].apply( this.states, arguments ); return this; }; });
module.exports = StateMachine;
/***/ }),
/***/ 6172: /***/ ((module) => {
var Controller = wp.media.controller, SiteIconCropper;
/** * wp.media.controller.SiteIconCropper * * A state for cropping a Site Icon. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.Cropper * @augments wp.media.controller.State * @augments Backbone.Model */ SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{ activate: function() { this.frame.on( 'content:create:crop', this.createCropContent, this ); this.frame.on( 'close', this.removeCropper, this ); this.set('selection', new Backbone.Collection(this.frame._selection.single)); },
/** * wp.media.view.AttachmentsBrowser * * @memberOf wp.media.view * * @class * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View * * @param {object} [options] The options hash passed to the view. * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar. * Accepts 'uploaded' and 'all'. * @param {boolean} [options.search=true] Whether to show the search interface in the * browser's toolbar. * @param {boolean} [options.date=true] Whether to show the date filter in the * browser's toolbar. * @param {boolean} [options.display=false] Whether to show the attachments display settings * view in the sidebar. * @param {boolean|string} [options.sidebar=true] Whether to create a sidebar for the browser. * Accepts true, false, and 'errors'. */ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{ tagName: 'div', className: 'attachments-browser',
this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this ); this.controller.on( 'edit:selection', this.editSelection );
// In the Media Library, the sidebar is used to display errors before the attachments grid. if ( this.options.sidebar && 'errors' === this.options.sidebar ) { this.createSidebar(); }
/* * In the grid mode (the Media Library), place the Inline Uploader before * other sections so that the visual order and the DOM order match. This way, * the Inline Uploader in the Media Library is right after the "Add New" * button, see ticket #37188. */ if ( this.controller.isModeActive( 'grid' ) ) { this.createUploader();
/* * Create a multi-purpose toolbar. Used as main toolbar in the Media Library * and also for other things, for example the "Drag and drop to reorder" and * "Suggested dimensions" info in the media modal. */ this.createToolbar(); } else { this.createToolbar(); this.createUploader(); }
// Add a heading before the attachments list. this.createAttachmentsHeading();
// Create the attachments wrapper view. this.createAttachmentsWrapperView();
// For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { this.createSidebar(); }
this.updateContent();
if ( ! infiniteScrolling ) { this.updateLoadMoreView(); }
this.collection.on( 'add remove reset', this.updateContent, this );
if ( ! infiniteScrolling ) { this.collection.on( 'add remove reset', this.updateLoadMoreView, this ); }
// The non-cached or cached attachments query has completed. this.collection.on( 'attachments:received', this.announceSearchResults, this ); },
/** * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate * the number of search results to screen reader users. This function is * debounced because the collection updates multiple times. * * @since 5.3.0 * * @return {void} */ announceSearchResults: _.debounce( function() { var count, /* translators: Accessibility text. %d: Number of attachments found in a search. */ mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Click load more for more results.' );
if ( infiniteScrolling ) { /* translators: Accessibility text. %d: Number of attachments found in a search. */ mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Scroll the page for more results.' ); }
if ( this.collection.mirroring && this.collection.mirroring.args.s ) { count = this.collection.length;
editSelection: function( modal ) { // When editing a selection, move focus to the "Go to library" button. modal.$( '.media-button-backToLibrary' ).focus(); },
this.toolbar.set( 'spinner', new wp.media.view.Spinner({ priority: -20 }) );
if ( showFilterByType || this.options.date ) { /* * Create a h2 heading before the select elements that filter attachments. * This heading is visible in the modal and visually hidden in the grid. */ this.toolbar.set( 'filters-heading', new wp.media.view.Heading( { priority: -100, text: l10n.filterAttachments, level: 'h2', className: 'media-attachments-filter-heading' }).render() ); }
if ( showFilterByType ) { // "Filters" is a <select>, a visually hidden label element needs to be rendered before. this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ value: l10n.filterByType, attributes: { 'for': 'media-attachment-filters' }, priority: -80 }).render() );
/* * Feels odd to bring the global media library switcher into the Attachment browser view. * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); * which the controller can tap into and add this view? */ if ( this.controller.isModeActive( 'grid' ) ) { LibraryViewSwitcher = View.extend({ className: 'view-switch media-grid-view-switch', template: wp.template( 'media-library-view-switcher') });
this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ controller: this.controller, priority: -90 }).render() );
// DateFilter is a <select>, a visually hidden label element needs to be rendered before. this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ value: l10n.filterByDate, attributes: { 'for': 'media-attachment-date-filters' }, priority: -75 }).render() ); this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ controller: this.controller, model: this.collection.props, priority: -75 }).render() );
// BulkSelection is a <div> with subviews, including screen reader text. this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ text: l10n.bulkSelect, controller: this.controller, priority: -70 }).render() );
updateContent: function() { var view = this, noItemsView;
if ( this.controller.isModeActive( 'grid' ) ) { // Usually the media library. noItemsView = view.attachmentsNoResults; } else { // Usually the media modal. noItemsView = view.uploader; }
/** * Updates the Load More view. This function is debounced because the * collection updates multiple times at the add, remove, and reset events. * We need it to run only once, after all attachments are added or removed. * * @since 5.8.0 * * @return {void} */ updateLoadMoreView: _.debounce( function() { // Ensure the load more view elements are initially hidden at each update. this.loadMoreButton.$el.addClass( 'hidden' ); this.loadMoreCount.$el.addClass( 'hidden' ); this.loadMoreJumpToFirst.$el.addClass( 'hidden' ).prop( 'disabled', true );
if ( ! this.collection.getTotalAttachments() ) { return; }
if ( this.collection.length ) { this.loadMoreCount.$el.text( /* translators: 1: Number of displayed attachments, 2: Number of total attachments. */ sprintf( __( 'Showing %1$s of %2$s media items' ), this.collection.length, this.collection.getTotalAttachments() ) );
this.loadMoreCount.$el.removeClass( 'hidden' ); }
/* * Notice that while the collection updates multiple times hasMore() may * return true when it's actually not true. */ if ( this.collection.hasMore() ) { this.loadMoreButton.$el.removeClass( 'hidden' ); }
// Find the media item to move focus to. The jQuery `eq()` index is zero-based. this.firstAddedMediaItem = this.$el.find( '.attachment' ).eq( this.firstAddedMediaItemIndex );
// If there's a media item to move focus to, make the "Jump to" button available. if ( this.firstAddedMediaItem.length ) { this.firstAddedMediaItem.addClass( 'new-media' ); this.loadMoreJumpToFirst.$el.removeClass( 'hidden' ).prop( 'disabled', false ); }
// If there are new items added, but no more to be added, move focus to Jump button. if ( this.firstAddedMediaItem.length && ! this.collection.hasMore() ) { this.loadMoreJumpToFirst.$el.trigger( 'focus' ); } }, 10 ),
/* * The collection index is zero-based while the length counts the actual * amount of items. Thus the length is equivalent to the position of the * first added item. */ this.firstAddedMediaItemIndex = this.collection.length;
/** * Moves focus to the first new added item. . * * @since 5.8.0 * * @return {void} */ jumpToFirstAddedItem: function() { // Set focus on first added item. this.firstAddedMediaItem.focus(); },
/** * wp.media.controller.GalleryAdd * * A state for selecting more images to add to a gallery. * * @since 3.5.0 * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @memberof wp.media.controller * * @param {Object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=gallery-library] Unique identifier. * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. * If one is not supplied, a collection of all images will be created. * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. * Accepts 'all', 'uploaded', or 'unattached'. * @param {string} [attributes.menu=gallery] Initial mode for the menu region. * @param {string} [attributes.content=upload] Initial mode for the content region. * Overridden by persistent user setting if 'contentUserSetting' is true. * @param {string} [attributes.router=browse] Initial mode for the router region. * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. * @param {boolean} [attributes.searchable=true] Whether the library is searchable. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. * @param {number} [attributes.priority=100] The priority for the state link in the media menu. * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. */ GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{ defaults: _.defaults({ id: 'gallery-library', title: l10n.addToGalleryTitle, multiple: 'add', filterable: 'uploaded', menu: 'gallery', toolbar: 'gallery-add', priority: 100, syncSelection: false }, Library.prototype.defaults ),
/** * Initializes the library. Creates a library of images if a library isn't supplied. * * @since 3.5.0 * * @return {void} */ initialize: function() { if ( ! this.get('library') ) { this.set( 'library', wp.media.query({ type: 'image' }) ); }
/* * Accept attachments that exist in the original library but * that do not exist in gallery's library yet. */ library.validator = function( attachment ) { return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && 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.editLibrary = edit;
var Selection = wp.media.model.Selection, Library = wp.media.controller.Library, CollectionAdd;
/** * wp.media.controller.CollectionAdd * * A state for adding attachments to a collection (e.g. video playlist). * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=library] Unique identifier. * @param {string} attributes.title Title for the state. Displays in the frame's title region. * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. * If one is not supplied, a collection of attachments of the specified type will be created. * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. * Accepts 'all', 'uploaded', or 'unattached'. * @param {string} [attributes.menu=gallery] Initial mode for the menu region. * @param {string} [attributes.content=upload] Initial mode for the content region. * Overridden by persistent user setting if 'contentUserSetting' is true. * @param {string} [attributes.router=browse] Initial mode for the router region. * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. * @param {boolean} [attributes.searchable=true] Whether the library is searchable. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. * @param {int} [attributes.priority=100] The priority for the state link in the media menu. * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. * @param {string} attributes.type The collection's media type. (e.g. 'video'). * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). */ CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{ defaults: _.defaults( { // Selection defaults. @see media.model.Selection multiple: 'add', // Attachments browser defaults. @see media.view.AttachmentsBrowser filterable: 'uploaded',
// If we haven't been provided a `library`, create a `Selection`. if ( ! this.get('library') ) { this.set( 'library', wp.media.query({ type: this.get('type') }) ); } Library.prototype.initialize.apply( this, arguments ); },
// 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 ) && 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);
// Store the set ratio. var setRatio = imgSelect.getOptions().aspectRatio;
// On mousedown, if no ratio is set and the Shift key is down, use a 1:1 ratio. this.parent.children().on( 'mousedown touchstart', function( e ) {
// If no ratio is set and the shift key is down, use a 1:1 ratio. if ( ! setRatio && e.shiftKey ) { imgSelect.setOptions( { aspectRatio: '1:1' } ); } } );
/** * Represents the overview of attachments in the Media Library. * * The constructor binds events to the collection this view represents when * adding or removing attachments or resetting the entire collection. * * @since 3.5.0 * * @constructs * @memberof wp.media.view * * @augments wp.media.View * * @listens collection:add * @listens collection:remove * @listens collection:reset * @listens controller:library:selection:add * @listens scrollElement:scroll * @listens this:ready * @listens controller:open */ initialize: function() { this.el.id = _.uniqueId('__attachments-view-');
/** * @since 5.8.0 Added the `infiniteScrolling` parameter. * * @param infiniteScrolling Whether to enable infinite scrolling or use * the default "load more" button. * @param refreshSensitivity The time in milliseconds to throttle the scroll * handler. * @param refreshThreshold The amount of pixels that should be scrolled before * loading more attachments from the server. * @param AttachmentView The view class to be used for models in the * collection. * @param sortable A jQuery sortable options object * ( http://api.jqueryui.com/sortable/ ). * @param resize A boolean indicating whether or not to listen to * resize events. * @param idealColumnWidth The width in pixels which a column should have when * calculating the total number of columns. */ _.defaults( this.options, { infiniteScrolling: infiniteScrolling || false, refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, refreshThreshold: 3, AttachmentView: wp.media.view.Attachment, sortable: false, resize: true, idealColumnWidth: $( window ).width() < 640 ? 135 : 150 });
/* * Find the view to be removed, delete it and call the remove function to clear * any set event handlers. */ 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.controller.on( 'library:selection:add', this.attachmentFocus, this );
if ( this.options.infiniteScrolling ) { // Throttle the scroll handler and bind this. this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
/* * Call this.setColumns() after this view has been rendered in the * DOM so attachments get proper width applied. */ _.defer( this.setColumns, this ); } },
/** * Listens to the resizeEvent on the window. * * Adjusts the amount of columns accordingly. First removes any existing event * handlers to prevent duplicate listeners. * * @since 4.0.0 * * @listens window:resize * * @return {void} */ bindEvents: function() { this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); },
/** * Focuses the first item in the collection. * * @since 4.0.0 * * @return {void} */ attachmentFocus: function() { /* * @todo When uploading new attachments, this tries to move focus to * the attachments grid. Actually, a progress bar gets initially displayed * and then updated when uploading completes, so focus is lost. * Additionally: this view is used for both the attachments list and * the list of selected attachments in the bottom media toolbar. Thus, when * uploading attachments, it is called twice and returns two different `this`. * `this.columns` is truthy within the modal. */ if ( this.columns ) { // Move focus to the grid list within the modal. this.$el.focus(); } },
/** * Restores focus to the selected item in the collection. * * Moves focus back to the first selected attachment in the grid. Used when * tabbing backwards from the attachment details sidebar. * See media.view.AttachmentsBrowser. * * @since 4.0.0 * * @return {void} */ restoreFocus: function() { this.$( 'li.selected:first' ).focus(); },
/** * Handles events for arrow key presses. * * Focuses the attachment in the direction of the used arrow key if it exists. * * @since 4.0.0 * * @param {KeyboardEvent} event The keyboard event that triggered this function. * * @return {void} */ 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 = 37. if ( 37 === event.keyCode ) { if ( 0 === index ) { return; } attachments.eq( index - 1 ).focus(); }
// Up arrow = 38. if ( 38 === event.keyCode ) { if ( 1 === row ) { return; } attachments.eq( index - perRow ).focus(); }
// Right arrow = 39. if ( 39 === event.keyCode ) { if ( attachments.length === index ) { return; } attachments.eq( index + 1 ).focus(); }
// Down arrow = 40. if ( 40 === event.keyCode ) { if ( Math.ceil( attachments.length / perRow ) === row ) { return; } attachments.eq( index + perRow ).focus(); } },
/** * Clears any set event handlers. * * @since 3.5.0 * * @return {void} */ 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 ); },
/** * Calculates the amount of columns. * * Calculates the amount of columns and sets it on the data-columns attribute * of .media-frame-content. * * @since 4.0.0 * * @return {void} */ setColumns: function() { var prev = this.columns, width = this.$el.width();
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(); },
/** * Disables jQuery sortable if collection has a comparator or collection.orderby * equals menuOrder. * * @since 3.5.0 * * @return {void} */ refreshSortable: function() { if ( ! this.options.sortable || ! $.fn.sortable ) { return; }
/** * Creates a new view for an attachment and adds it to _viewsByCid. * * @since 3.5.0 * * @param {wp.media.model.Attachment} attachment * * @return {wp.media.View} The created view. */ createAttachmentView: function( attachment ) { var view = new this.options.AttachmentView({ controller: this.controller, model: attachment, collection: this.collection, selection: this.options.selection });
/** * Prepares view for display. * * Creates views for every attachment in collection if the collection is not * empty, otherwise clears all views and loads more attachments. * * @since 3.5.0 * * @return {void} */ prepare: function() { if ( this.collection.length ) { this.views.set( this.collection.map( this.createAttachmentView, this ) ); } else { this.views.unset(); if ( this.options.infiniteScrolling ) { this.collection.more().done( this.scroll ); } } },
/** * Triggers the scroll function to check if we should query for additional * attachments right away. * * @since 3.5.0 * * @return {void} */ ready: function() { if ( this.options.infiniteScrolling ) { this.scroll(); } },
/** * Handles scroll events. * * Shows the spinner if we're close to the bottom. Loads more attachments from * server if we're {refreshThreshold} times away from the bottom. * * @since 3.5.0 * * @return {void} */ 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(); }
// Show the spinner only if we are close to the bottom. if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { toolbar.get('spinner').show(); }
// Can show additional info here while retrying to create image sub-sizes. this.views.add( '.upload-errors', statusError, { at: 0 } ); _.delay( function() { buttonClose.trigger( 'focus' ); wp.a11y.speak( error.get( 'message' ), 'assertive' ); }, 1000 ); },
dismiss: function() { var errors = this.views.get('.upload-errors');
if ( errors ) { _.invoke( errors, 'remove' ); } wp.Uploader.errors.reset(); // Move focus to the modal after the dismiss button gets removed from the DOM. if ( this.controller.modal ) { this.controller.modal.focusManager.focus(); } } });
updateoEmbed: _.debounce( function() { var url = this.model.get( 'url' );
// Clear out previous results. this.$('.embed-container').hide().find('.embed-preview').empty(); this.$( '.setting' ).hide();
// Only proceed with embed if the field contains more than 11 characters. // Example: http://a.io is 11 chars if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) { return; }
// Support YouTube embed urls, since they work once in the editor. re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; youTubeEmbedMatch = re.exec( url ); if ( youTubeEmbedMatch ) { url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; }
// Ensure that the animation is triggered by waiting until // the transparent element is painted into the DOM. _.defer( function() { $el.css({ opacity: 1 }); }); },
hide: function() { var $el = this.$el.css({ opacity: 0 });
wp.media.transition( $el ).done( function() { // Transition end events are subject to race conditions. // Make sure that the value is set as intended. if ( '0' === $el.css('opacity') ) { $el.hide(); } });
/** * wp.media.controller.CollectionEdit * * A state for editing a collection, which is used by audio and video playlists, * and can be used for other collections. * * @memberOf wp.media.controller * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. * If one is not supplied, an empty media.model.Selection collection is created. * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. * @param {string} [attributes.content=browse] Initial mode for the content region. * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. * @param {boolean} [attributes.searchable=false] Whether the library is searchable. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. * @param {int} [attributes.priority=60] The priority for the state link in the media menu. * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. * Defaults to false for this state, because the library passed in *is* the selection. * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. * @param {string} attributes.type The collection's media type. (e.g. 'video'). * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). */ CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{ defaults: { multiple: false, sortable: true, date: false, searchable: false, content: 'browse', describe: true, dragInfo: true, idealColumnWidth: 170, editing: false, priority: 60, SettingsView: false, syncSelection: false },
// If we haven't been provided a `library`, create a `Selection`. if ( ! this.get('library') ) { this.set( 'library', new wp.media.model.Selection() ); } // The single `Attachment` view to be used in the `Attachments` view. if ( ! this.get('AttachmentView') ) { this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); } Library.prototype.initialize.apply( this, arguments ); },
/** * Render the collection embed settings view in the browser sidebar. * * @todo This is against the pattern elsewhere in media. Typically the frame * is responsible for adding region mode callbacks. Explain. * * @since 3.9.0 * * @param {wp.media.view.attachmentsBrowser} The attachments browser view. */ renderSettings: function( attachmentsBrowserView ) { var library = this.get('library'), collectionType = this.get('collectionType'), dragInfoText = this.get('dragInfoText'), SettingsView = this.get('SettingsView'), obj = {};
// Accept an object with an `id` : `view` mapping. if ( _.isObject( id ) ) { _.each( id, function( view, id ) { this.set( id, view ); }, this ); return this; }
/** * Allows to override the click event. */ _click: function() { var clickOverride = this.options.click;
if ( clickOverride ) { clickOverride.call( this ); } else { this.click(); } },
click: function() { var state = this.options.state;
if ( state ) { this.controller.setState( state ); // Toggle the menu visibility in the responsive view. this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below. } },
clickSelect: function() { var options = this.options, controller = this.controller;
if ( options.close ) { controller.close(); }
if ( options.event ) { controller.state().trigger( options.event ); }
if ( options.state ) { controller.setState( options.state ); }
if ( options.reset ) { controller.reset(); } } });
module.exports = Select;
/***/ }),
/***/ 9660: /***/ ((module) => {
var Controller = wp.media.controller, CustomizeImageCropper;
/** * A state for cropping an image in the customizer. * * @since 4.3.0 * * @constructs wp.media.controller.CustomizeImageCropper * @memberOf wp.media.controller * @augments wp.media.controller.CustomizeImageCropper.Cropper * @inheritDoc */ CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{ /** * Posts the crop details to the admin. * * Uses crop measurements when flexible in both directions. * Constrains flexible side based on image ratio and size of the fixed side. * * @since 4.3.0 * * @param {Object} attachment The attachment to crop. * * @return {$.promise} A jQuery promise that represents the crop image request. */ doCrop: function( attachment ) { var cropDetails = attachment.get( 'cropDetails' ), control = this.get( 'control' ), ratio = cropDetails.width / cropDetails.height;
// Use crop measurements when flexible in both directions. if ( control.params.flex_width && control.params.flex_height ) { cropDetails.dst_width = cropDetails.width; cropDetails.dst_height = cropDetails.height;
// Constrain flexible side based on image ratio and size of the fixed side. } else { cropDetails.dst_width = control.params.flex_width ? control.params.height * ratio : control.params.width; cropDetails.dst_height = control.params.flex_height ? control.params.width / ratio : control.params.height; }
/** * wp.media.controller.Region * * A region is a persistent application layout area. * * A region assumes one mode at any time, and can be switched to another. * * When mode changes, events are triggered on the region's parent view. * The parent view will listen to specific events and fill the region with an * appropriate view depending on mode. For example, a frame listens for the * 'browse' mode t be activated on the 'content' view and then fills the region * with an AttachmentsBrowser view. * * @memberOf wp.media.controller * * @class * * @param {Object} options Options hash for the region. * @param {string} options.id Unique identifier for the region. * @param {Backbone.View} options.view A parent view the region exists within. * @param {string} options.selector jQuery selector for the region within the parent view. */ var Region = function( options ) { _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); };
// Use Backbone's self-propagating `extend` inheritance method. Region.extend = Backbone.Model.extend;
_.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{ /** * Activate a mode. * * @since 3.5.0 * * @param {string} mode * * @fires Region#activate * @fires Region#deactivate * * @return {wp.media.controller.Region} Returns itself to allow chaining. */ mode: function( mode ) { if ( ! mode ) { return this._mode; } // Bail if we're trying to change to the current mode. if ( mode === this._mode ) { return this; }
/** * Create region view event. * * Region view creation takes place in an event callback on the frame. * * @event wp.media.controller.Region#create * @type {object} * @property {object} view */ this.trigger( 'create', set ); view = set.view;
/** * Render region view event. * * Region view creation takes place in an event callback on the frame. * * @event wp.media.controller.Region#render * @type {object} */ this.trigger( 'render', view ); if ( view ) { this.set( view ); } return this; },
// Trigger `{this.id}:{event}:{this._mode}` event on the frame. args[0] = base + ':' + this._mode; this.view.trigger.apply( this.view, args );
// Trigger `{this.id}:{event}` event on the frame. args[0] = base; this.view.trigger.apply( this.view, args ); return this; } });
module.exports = Region;
/***/ })
/******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /** * @output wp-includes/js/media-views.js */
var media = wp.media, $ = jQuery, l10n;
media.isTouchDevice = ( 'ontouchend' in document );
// Link any localized strings. l10n = media.view.l10n = window._wpMediaViewsL10n || {};
// Link any settings. media.view.settings = l10n.settings || {}; delete l10n.settings;
// Copy the `post` setting over to the model settings. media.model.settings.post = media.view.settings.post;
// Check if the browser supports CSS 3.0 transitions. $.support.transition = (function(){ var style = document.documentElement.style, transitions = { WebkitTransition: 'webkitTransitionEnd', MozTransition: 'transitionend', OTransition: 'oTransitionEnd otransitionend', transition: 'transitionend' }, transition;
/** * A shared event bus used to provide events into * the media workflows that 3rd-party devs can use to hook * in. */ media.events = _.extend( {}, Backbone.Events );
/** * Makes it easier to bind events using transitions. * * @param {string} selector * @param {number} sensitivity * @return {Promise} */ media.transition = function( selector, sensitivity ) { var deferred = $.Deferred();
sensitivity = sensitivity || 2000;
if ( $.support.transition ) { if ( ! (selector instanceof $) ) { selector = $( selector ); }
// Resolve the deferred when the first element finishes animating. selector.first().one( $.support.transition.end, deferred.resolve );
// Just in case the event doesn't trigger, fire a callback. _.delay( deferred.resolve, sensitivity );
// Otherwise, execute on the spot. } else { deferred.resolve(); }