You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1334 lines
42 KiB
1334 lines
42 KiB
/** |
|
* @output wp-admin/js/widgets/media-widgets.js |
|
*/ |
|
|
|
/* eslint consistent-this: [ "error", "control" ] */ |
|
|
|
/** |
|
* @namespace wp.mediaWidgets |
|
* @memberOf wp |
|
*/ |
|
wp.mediaWidgets = ( function( $ ) { |
|
'use strict'; |
|
|
|
var component = {}; |
|
|
|
/** |
|
* Widget control (view) constructors, mapping widget id_base to subclass of MediaWidgetControl. |
|
* |
|
* Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base. |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @type {Object.<string, wp.mediaWidgets.MediaWidgetModel>} |
|
*/ |
|
component.controlConstructors = {}; |
|
|
|
/** |
|
* Widget model constructors, mapping widget id_base to subclass of MediaWidgetModel. |
|
* |
|
* Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base. |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @type {Object.<string, wp.mediaWidgets.MediaWidgetModel>} |
|
*/ |
|
component.modelConstructors = {}; |
|
|
|
component.PersistentDisplaySettingsLibrary = wp.media.controller.Library.extend(/** @lends wp.mediaWidgets.PersistentDisplaySettingsLibrary.prototype */{ |
|
|
|
/** |
|
* Library which persists the customized display settings across selections. |
|
* |
|
* @constructs wp.mediaWidgets.PersistentDisplaySettingsLibrary |
|
* @augments wp.media.controller.Library |
|
* |
|
* @param {Object} options - Options. |
|
* |
|
* @returns {void} |
|
*/ |
|
initialize: function initialize( options ) { |
|
_.bindAll( this, 'handleDisplaySettingChange' ); |
|
wp.media.controller.Library.prototype.initialize.call( this, options ); |
|
}, |
|
|
|
/** |
|
* Sync changes to the current display settings back into the current customized. |
|
* |
|
* @param {Backbone.Model} displaySettings - Modified display settings. |
|
* @returns {void} |
|
*/ |
|
handleDisplaySettingChange: function handleDisplaySettingChange( displaySettings ) { |
|
this.get( 'selectedDisplaySettings' ).set( displaySettings.attributes ); |
|
}, |
|
|
|
/** |
|
* Get the display settings model. |
|
* |
|
* Model returned is updated with the current customized display settings, |
|
* and an event listener is added so that changes made to the settings |
|
* will sync back into the model storing the session's customized display |
|
* settings. |
|
* |
|
* @param {Backbone.Model} model - Display settings model. |
|
* @returns {Backbone.Model} Display settings model. |
|
*/ |
|
display: function getDisplaySettingsModel( model ) { |
|
var display, selectedDisplaySettings = this.get( 'selectedDisplaySettings' ); |
|
display = wp.media.controller.Library.prototype.display.call( this, model ); |
|
|
|
display.off( 'change', this.handleDisplaySettingChange ); // Prevent duplicated event handlers. |
|
display.set( selectedDisplaySettings.attributes ); |
|
if ( 'custom' === selectedDisplaySettings.get( 'link_type' ) ) { |
|
display.linkUrl = selectedDisplaySettings.get( 'link_url' ); |
|
} |
|
display.on( 'change', this.handleDisplaySettingChange ); |
|
return display; |
|
} |
|
}); |
|
|
|
/** |
|
* Extended view for managing the embed UI. |
|
* |
|
* @class wp.mediaWidgets.MediaEmbedView |
|
* @augments wp.media.view.Embed |
|
*/ |
|
component.MediaEmbedView = wp.media.view.Embed.extend(/** @lends wp.mediaWidgets.MediaEmbedView.prototype */{ |
|
|
|
/** |
|
* Initialize. |
|
* |
|
* @since 4.9.0 |
|
* |
|
* @param {object} options - Options. |
|
* @returns {void} |
|
*/ |
|
initialize: function( options ) { |
|
var view = this, embedController; // eslint-disable-line consistent-this |
|
wp.media.view.Embed.prototype.initialize.call( view, options ); |
|
if ( 'image' !== view.controller.options.mimeType ) { |
|
embedController = view.controller.states.get( 'embed' ); |
|
embedController.off( 'scan', embedController.scanImage, embedController ); |
|
} |
|
}, |
|
|
|
/** |
|
* Refresh embed view. |
|
* |
|
* Forked override of {wp.media.view.Embed#refresh()} to suppress irrelevant "link text" field. |
|
* |
|
* @returns {void} |
|
*/ |
|
refresh: function refresh() { |
|
/** |
|
* @class wp.mediaWidgets~Constructor |
|
*/ |
|
var Constructor; |
|
|
|
if ( 'image' === this.controller.options.mimeType ) { |
|
Constructor = wp.media.view.EmbedImage; |
|
} else { |
|
|
|
// This should be eliminated once #40450 lands of when this is merged into core. |
|
Constructor = wp.media.view.EmbedLink.extend(/** @lends wp.mediaWidgets~Constructor.prototype */{ |
|
|
|
/** |
|
* Set the disabled state on the Add to Widget button. |
|
* |
|
* @param {boolean} disabled - Disabled. |
|
* @returns {void} |
|
*/ |
|
setAddToWidgetButtonDisabled: function setAddToWidgetButtonDisabled( disabled ) { |
|
this.views.parent.views.parent.views.get( '.media-frame-toolbar' )[0].$el.find( '.media-button-select' ).prop( 'disabled', disabled ); |
|
}, |
|
|
|
/** |
|
* Set or clear an error notice. |
|
* |
|
* @param {string} notice - Notice. |
|
* @returns {void} |
|
*/ |
|
setErrorNotice: function setErrorNotice( notice ) { |
|
var embedLinkView = this, noticeContainer; // eslint-disable-line consistent-this |
|
|
|
noticeContainer = embedLinkView.views.parent.$el.find( '> .notice:first-child' ); |
|
if ( ! notice ) { |
|
if ( noticeContainer.length ) { |
|
noticeContainer.slideUp( 'fast' ); |
|
} |
|
} else { |
|
if ( ! noticeContainer.length ) { |
|
noticeContainer = $( '<div class="media-widget-embed-notice notice notice-error notice-alt"></div>' ); |
|
noticeContainer.hide(); |
|
embedLinkView.views.parent.$el.prepend( noticeContainer ); |
|
} |
|
noticeContainer.empty(); |
|
noticeContainer.append( $( '<p>', { |
|
html: notice |
|
})); |
|
noticeContainer.slideDown( 'fast' ); |
|
} |
|
}, |
|
|
|
/** |
|
* Update oEmbed. |
|
* |
|
* @since 4.9.0 |
|
* |
|
* @returns {void} |
|
*/ |
|
updateoEmbed: function() { |
|
var embedLinkView = this, url; // eslint-disable-line consistent-this |
|
|
|
url = embedLinkView.model.get( 'url' ); |
|
|
|
// Abort if the URL field was emptied out. |
|
if ( ! url ) { |
|
embedLinkView.setErrorNotice( '' ); |
|
embedLinkView.setAddToWidgetButtonDisabled( true ); |
|
return; |
|
} |
|
|
|
if ( ! url.match( /^(http|https):\/\/.+\// ) ) { |
|
embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' ); |
|
embedLinkView.setAddToWidgetButtonDisabled( true ); |
|
} |
|
|
|
wp.media.view.EmbedLink.prototype.updateoEmbed.call( embedLinkView ); |
|
}, |
|
|
|
/** |
|
* Fetch media. |
|
* |
|
* @returns {void} |
|
*/ |
|
fetch: function() { |
|
var embedLinkView = this, fetchSuccess, matches, fileExt, urlParser, url, re, youTubeEmbedMatch; // eslint-disable-line consistent-this |
|
url = embedLinkView.model.get( 'url' ); |
|
|
|
if ( embedLinkView.dfd && 'pending' === embedLinkView.dfd.state() ) { |
|
embedLinkView.dfd.abort(); |
|
} |
|
|
|
fetchSuccess = function( response ) { |
|
embedLinkView.renderoEmbed({ |
|
data: { |
|
body: response |
|
} |
|
}); |
|
|
|
embedLinkView.controller.$el.find( '#embed-url-field' ).removeClass( 'invalid' ); |
|
embedLinkView.setErrorNotice( '' ); |
|
embedLinkView.setAddToWidgetButtonDisabled( false ); |
|
}; |
|
|
|
urlParser = document.createElement( 'a' ); |
|
urlParser.href = url; |
|
matches = urlParser.pathname.toLowerCase().match( /\.(\w+)$/ ); |
|
if ( matches ) { |
|
fileExt = matches[1]; |
|
if ( ! wp.media.view.settings.embedMimes[ fileExt ] ) { |
|
embedLinkView.renderFail(); |
|
} else if ( 0 !== wp.media.view.settings.embedMimes[ fileExt ].indexOf( embedLinkView.controller.options.mimeType ) ) { |
|
embedLinkView.renderFail(); |
|
} else { |
|
fetchSuccess( '<!--success-->' ); |
|
} |
|
return; |
|
} |
|
|
|
// Support YouTube embed links. |
|
re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; |
|
youTubeEmbedMatch = re.exec( url ); |
|
if ( youTubeEmbedMatch ) { |
|
url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; |
|
// silently change url to proper oembed-able version. |
|
embedLinkView.model.attributes.url = url; |
|
} |
|
|
|
embedLinkView.dfd = wp.apiRequest({ |
|
url: wp.media.view.settings.oEmbedProxyUrl, |
|
data: { |
|
url: url, |
|
maxwidth: embedLinkView.model.get( 'width' ), |
|
maxheight: embedLinkView.model.get( 'height' ), |
|
discover: false |
|
}, |
|
type: 'GET', |
|
dataType: 'json', |
|
context: embedLinkView |
|
}); |
|
|
|
embedLinkView.dfd.done( function( response ) { |
|
if ( embedLinkView.controller.options.mimeType !== response.type ) { |
|
embedLinkView.renderFail(); |
|
return; |
|
} |
|
fetchSuccess( response.html ); |
|
}); |
|
embedLinkView.dfd.fail( _.bind( embedLinkView.renderFail, embedLinkView ) ); |
|
}, |
|
|
|
/** |
|
* Handle render failure. |
|
* |
|
* Overrides the {EmbedLink#renderFail()} method to prevent showing the "Link Text" field. |
|
* The element is getting display:none in the stylesheet, but the underlying method uses |
|
* uses {jQuery.fn.show()} which adds an inline style. This avoids the need for !important. |
|
* |
|
* @returns {void} |
|
*/ |
|
renderFail: function renderFail() { |
|
var embedLinkView = this; // eslint-disable-line consistent-this |
|
embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' ); |
|
embedLinkView.setErrorNotice( embedLinkView.controller.options.invalidEmbedTypeError || 'ERROR' ); |
|
embedLinkView.setAddToWidgetButtonDisabled( true ); |
|
} |
|
}); |
|
} |
|
|
|
this.settings( new Constructor({ |
|
controller: this.controller, |
|
model: this.model.props, |
|
priority: 40 |
|
})); |
|
} |
|
}); |
|
|
|
/** |
|
* Custom media frame for selecting uploaded media or providing media by URL. |
|
* |
|
* @class wp.mediaWidgets.MediaFrameSelect |
|
* @augments wp.media.view.MediaFrame.Post |
|
*/ |
|
component.MediaFrameSelect = wp.media.view.MediaFrame.Post.extend(/** @lends wp.mediaWidgets.MediaFrameSelect.prototype */{ |
|
|
|
/** |
|
* Create the default states. |
|
* |
|
* @returns {void} |
|
*/ |
|
createStates: function createStates() { |
|
var mime = this.options.mimeType, specificMimes = []; |
|
_.each( wp.media.view.settings.embedMimes, function( embedMime ) { |
|
if ( 0 === embedMime.indexOf( mime ) ) { |
|
specificMimes.push( embedMime ); |
|
} |
|
}); |
|
if ( specificMimes.length > 0 ) { |
|
mime = specificMimes; |
|
} |
|
|
|
this.states.add([ |
|
|
|
// Main states. |
|
new component.PersistentDisplaySettingsLibrary({ |
|
id: 'insert', |
|
title: this.options.title, |
|
selection: this.options.selection, |
|
priority: 20, |
|
toolbar: 'main-insert', |
|
filterable: 'dates', |
|
library: wp.media.query({ |
|
type: mime |
|
}), |
|
multiple: false, |
|
editable: true, |
|
|
|
selectedDisplaySettings: this.options.selectedDisplaySettings, |
|
displaySettings: _.isUndefined( this.options.showDisplaySettings ) ? true : this.options.showDisplaySettings, |
|
displayUserSettings: false // We use the display settings from the current/default widget instance props. |
|
}), |
|
|
|
new wp.media.controller.EditImage({ model: this.options.editImage }), |
|
|
|
// Embed states. |
|
new wp.media.controller.Embed({ |
|
metadata: this.options.metadata, |
|
type: 'image' === this.options.mimeType ? 'image' : 'link', |
|
invalidEmbedTypeError: this.options.invalidEmbedTypeError |
|
}) |
|
]); |
|
}, |
|
|
|
/** |
|
* Main insert toolbar. |
|
* |
|
* Forked override of {wp.media.view.MediaFrame.Post#mainInsertToolbar()} to override text. |
|
* |
|
* @param {wp.Backbone.View} view - Toolbar view. |
|
* @this {wp.media.controller.Library} |
|
* @returns {void} |
|
*/ |
|
mainInsertToolbar: function mainInsertToolbar( view ) { |
|
var controller = this; // eslint-disable-line consistent-this |
|
view.set( 'insert', { |
|
style: 'primary', |
|
priority: 80, |
|
text: controller.options.text, // The whole reason for the fork. |
|
requires: { selection: true }, |
|
|
|
/** |
|
* Handle click. |
|
* |
|
* @ignore |
|
* |
|
* @fires wp.media.controller.State#insert() |
|
* @returns {void} |
|
*/ |
|
click: function onClick() { |
|
var state = controller.state(), |
|
selection = state.get( 'selection' ); |
|
|
|
controller.close(); |
|
state.trigger( 'insert', selection ).reset(); |
|
} |
|
}); |
|
}, |
|
|
|
/** |
|
* Main embed toolbar. |
|
* |
|
* Forked override of {wp.media.view.MediaFrame.Post#mainEmbedToolbar()} to override text. |
|
* |
|
* @param {wp.Backbone.View} toolbar - Toolbar view. |
|
* @this {wp.media.controller.Library} |
|
* @returns {void} |
|
*/ |
|
mainEmbedToolbar: function mainEmbedToolbar( toolbar ) { |
|
toolbar.view = new wp.media.view.Toolbar.Embed({ |
|
controller: this, |
|
text: this.options.text, |
|
event: 'insert' |
|
}); |
|
}, |
|
|
|
/** |
|
* Embed content. |
|
* |
|
* Forked override of {wp.media.view.MediaFrame.Post#embedContent()} to suppress irrelevant "link text" field. |
|
* |
|
* @returns {void} |
|
*/ |
|
embedContent: function embedContent() { |
|
var view = new component.MediaEmbedView({ |
|
controller: this, |
|
model: this.state() |
|
}).render(); |
|
|
|
this.content.set( view ); |
|
|
|
if ( ! wp.media.isTouchDevice ) { |
|
view.url.focus(); |
|
} |
|
} |
|
}); |
|
|
|
component.MediaWidgetControl = Backbone.View.extend(/** @lends wp.mediaWidgets.MediaWidgetControl.prototype */{ |
|
|
|
/** |
|
* Translation strings. |
|
* |
|
* The mapping of translation strings is handled by media widget subclasses, |
|
* exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). |
|
* |
|
* @type {Object} |
|
*/ |
|
l10n: { |
|
add_to_widget: '{{add_to_widget}}', |
|
add_media: '{{add_media}}' |
|
}, |
|
|
|
/** |
|
* Widget ID base. |
|
* |
|
* This may be defined by the subclass. It may be exported from PHP to JS |
|
* such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). If not, |
|
* it will attempt to be discovered by looking to see if this control |
|
* instance extends each member of component.controlConstructors, and if |
|
* it does extend one, will use the key as the id_base. |
|
* |
|
* @type {string} |
|
*/ |
|
id_base: '', |
|
|
|
/** |
|
* Mime type. |
|
* |
|
* This must be defined by the subclass. It may be exported from PHP to JS |
|
* such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). |
|
* |
|
* @type {string} |
|
*/ |
|
mime_type: '', |
|
|
|
/** |
|
* View events. |
|
* |
|
* @type {Object} |
|
*/ |
|
events: { |
|
'click .notice-missing-attachment a': 'handleMediaLibraryLinkClick', |
|
'click .select-media': 'selectMedia', |
|
'click .placeholder': 'selectMedia', |
|
'click .edit-media': 'editMedia' |
|
}, |
|
|
|
/** |
|
* Show display settings. |
|
* |
|
* @type {boolean} |
|
*/ |
|
showDisplaySettings: true, |
|
|
|
/** |
|
* Media Widget Control. |
|
* |
|
* @constructs wp.mediaWidgets.MediaWidgetControl |
|
* @augments Backbone.View |
|
* @abstract |
|
* |
|
* @param {Object} options - Options. |
|
* @param {Backbone.Model} options.model - Model. |
|
* @param {jQuery} options.el - Control field container element. |
|
* @param {jQuery} options.syncContainer - Container element where fields are synced for the server. |
|
* |
|
* @returns {void} |
|
*/ |
|
initialize: function initialize( options ) { |
|
var control = this; |
|
|
|
Backbone.View.prototype.initialize.call( control, options ); |
|
|
|
if ( ! ( control.model instanceof component.MediaWidgetModel ) ) { |
|
throw new Error( 'Missing options.model' ); |
|
} |
|
if ( ! options.el ) { |
|
throw new Error( 'Missing options.el' ); |
|
} |
|
if ( ! options.syncContainer ) { |
|
throw new Error( 'Missing options.syncContainer' ); |
|
} |
|
|
|
control.syncContainer = options.syncContainer; |
|
|
|
control.$el.addClass( 'media-widget-control' ); |
|
|
|
// Allow methods to be passed in with control context preserved. |
|
_.bindAll( control, 'syncModelToInputs', 'render', 'updateSelectedAttachment', 'renderPreview' ); |
|
|
|
if ( ! control.id_base ) { |
|
_.find( component.controlConstructors, function( Constructor, idBase ) { |
|
if ( control instanceof Constructor ) { |
|
control.id_base = idBase; |
|
return true; |
|
} |
|
return false; |
|
}); |
|
if ( ! control.id_base ) { |
|
throw new Error( 'Missing id_base.' ); |
|
} |
|
} |
|
|
|
// Track attributes needed to renderPreview in it's own model. |
|
control.previewTemplateProps = new Backbone.Model( control.mapModelToPreviewTemplateProps() ); |
|
|
|
// Re-render the preview when the attachment changes. |
|
control.selectedAttachment = new wp.media.model.Attachment(); |
|
control.renderPreview = _.debounce( control.renderPreview ); |
|
control.listenTo( control.previewTemplateProps, 'change', control.renderPreview ); |
|
|
|
// Make sure a copy of the selected attachment is always fetched. |
|
control.model.on( 'change:attachment_id', control.updateSelectedAttachment ); |
|
control.model.on( 'change:url', control.updateSelectedAttachment ); |
|
control.updateSelectedAttachment(); |
|
|
|
/* |
|
* Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state. |
|
* In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model |
|
* from the start, without having to sync with hidden fields. See <https://core.trac.wordpress.org/ticket/33507>. |
|
*/ |
|
control.listenTo( control.model, 'change', control.syncModelToInputs ); |
|
control.listenTo( control.model, 'change', control.syncModelToPreviewProps ); |
|
control.listenTo( control.model, 'change', control.render ); |
|
|
|
// Update the title. |
|
control.$el.on( 'input change', '.title', function updateTitle() { |
|
control.model.set({ |
|
title: $.trim( $( this ).val() ) |
|
}); |
|
}); |
|
|
|
// Update link_url attribute. |
|
control.$el.on( 'input change', '.link', function updateLinkUrl() { |
|
var linkUrl = $.trim( $( this ).val() ), linkType = 'custom'; |
|
if ( control.selectedAttachment.get( 'linkUrl' ) === linkUrl || control.selectedAttachment.get( 'link' ) === linkUrl ) { |
|
linkType = 'post'; |
|
} else if ( control.selectedAttachment.get( 'url' ) === linkUrl ) { |
|
linkType = 'file'; |
|
} |
|
control.model.set( { |
|
link_url: linkUrl, |
|
link_type: linkType |
|
}); |
|
|
|
// Update display settings for the next time the user opens to select from the media library. |
|
control.displaySettings.set( { |
|
link: linkType, |
|
linkUrl: linkUrl |
|
}); |
|
}); |
|
|
|
/* |
|
* Copy current display settings from the widget model to serve as basis |
|
* of customized display settings for the current media frame session. |
|
* Changes to display settings will be synced into this model, and |
|
* when a new selection is made, the settings from this will be synced |
|
* into that AttachmentDisplay's model to persist the setting changes. |
|
*/ |
|
control.displaySettings = new Backbone.Model( _.pick( |
|
control.mapModelToMediaFrameProps( |
|
_.extend( control.model.defaults(), control.model.toJSON() ) |
|
), |
|
_.keys( wp.media.view.settings.defaultProps ) |
|
) ); |
|
}, |
|
|
|
/** |
|
* Update the selected attachment if necessary. |
|
* |
|
* @returns {void} |
|
*/ |
|
updateSelectedAttachment: function updateSelectedAttachment() { |
|
var control = this, attachment; |
|
|
|
if ( 0 === control.model.get( 'attachment_id' ) ) { |
|
control.selectedAttachment.clear(); |
|
control.model.set( 'error', false ); |
|
} else if ( control.model.get( 'attachment_id' ) !== control.selectedAttachment.get( 'id' ) ) { |
|
attachment = new wp.media.model.Attachment({ |
|
id: control.model.get( 'attachment_id' ) |
|
}); |
|
attachment.fetch() |
|
.done( function done() { |
|
control.model.set( 'error', false ); |
|
control.selectedAttachment.set( attachment.toJSON() ); |
|
}) |
|
.fail( function fail() { |
|
control.model.set( 'error', 'missing_attachment' ); |
|
}); |
|
} |
|
}, |
|
|
|
/** |
|
* Sync the model attributes to the hidden inputs, and update previewTemplateProps. |
|
* |
|
* @returns {void} |
|
*/ |
|
syncModelToPreviewProps: function syncModelToPreviewProps() { |
|
var control = this; |
|
control.previewTemplateProps.set( control.mapModelToPreviewTemplateProps() ); |
|
}, |
|
|
|
/** |
|
* Sync the model attributes to the hidden inputs, and update previewTemplateProps. |
|
* |
|
* @returns {void} |
|
*/ |
|
syncModelToInputs: function syncModelToInputs() { |
|
var control = this; |
|
control.syncContainer.find( '.media-widget-instance-property' ).each( function() { |
|
var input = $( this ), value, propertyName; |
|
propertyName = input.data( 'property' ); |
|
value = control.model.get( propertyName ); |
|
if ( _.isUndefined( value ) ) { |
|
return; |
|
} |
|
|
|
if ( 'array' === control.model.schema[ propertyName ].type && _.isArray( value ) ) { |
|
value = value.join( ',' ); |
|
} else if ( 'boolean' === control.model.schema[ propertyName ].type ) { |
|
value = value ? '1' : ''; // Because in PHP, strval( true ) === '1' && strval( false ) === ''. |
|
} else { |
|
value = String( value ); |
|
} |
|
|
|
if ( input.val() !== value ) { |
|
input.val( value ); |
|
input.trigger( 'change' ); |
|
} |
|
}); |
|
}, |
|
|
|
/** |
|
* Get template. |
|
* |
|
* @returns {Function} Template. |
|
*/ |
|
template: function template() { |
|
var control = this; |
|
if ( ! $( '#tmpl-widget-media-' + control.id_base + '-control' ).length ) { |
|
throw new Error( 'Missing widget control template for ' + control.id_base ); |
|
} |
|
return wp.template( 'widget-media-' + control.id_base + '-control' ); |
|
}, |
|
|
|
/** |
|
* Render template. |
|
* |
|
* @returns {void} |
|
*/ |
|
render: function render() { |
|
var control = this, titleInput; |
|
|
|
if ( ! control.templateRendered ) { |
|
control.$el.html( control.template()( control.model.toJSON() ) ); |
|
control.renderPreview(); // Hereafter it will re-render when control.selectedAttachment changes. |
|
control.templateRendered = true; |
|
} |
|
|
|
titleInput = control.$el.find( '.title' ); |
|
if ( ! titleInput.is( document.activeElement ) ) { |
|
titleInput.val( control.model.get( 'title' ) ); |
|
} |
|
|
|
control.$el.toggleClass( 'selected', control.isSelected() ); |
|
}, |
|
|
|
/** |
|
* Render media preview. |
|
* |
|
* @abstract |
|
* @returns {void} |
|
*/ |
|
renderPreview: function renderPreview() { |
|
throw new Error( 'renderPreview must be implemented' ); |
|
}, |
|
|
|
/** |
|
* Whether a media item is selected. |
|
* |
|
* @returns {boolean} Whether selected and no error. |
|
*/ |
|
isSelected: function isSelected() { |
|
var control = this; |
|
|
|
if ( control.model.get( 'error' ) ) { |
|
return false; |
|
} |
|
|
|
return Boolean( control.model.get( 'attachment_id' ) || control.model.get( 'url' ) ); |
|
}, |
|
|
|
/** |
|
* Handle click on link to Media Library to open modal, such as the link that appears when in the missing attachment error notice. |
|
* |
|
* @param {jQuery.Event} event - Event. |
|
* @returns {void} |
|
*/ |
|
handleMediaLibraryLinkClick: function handleMediaLibraryLinkClick( event ) { |
|
var control = this; |
|
event.preventDefault(); |
|
control.selectMedia(); |
|
}, |
|
|
|
/** |
|
* Open the media select frame to chose an item. |
|
* |
|
* @returns {void} |
|
*/ |
|
selectMedia: function selectMedia() { |
|
var control = this, selection, mediaFrame, defaultSync, mediaFrameProps, selectionModels = []; |
|
|
|
if ( control.isSelected() && 0 !== control.model.get( 'attachment_id' ) ) { |
|
selectionModels.push( control.selectedAttachment ); |
|
} |
|
|
|
selection = new wp.media.model.Selection( selectionModels, { multiple: false } ); |
|
|
|
mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); |
|
if ( mediaFrameProps.size ) { |
|
control.displaySettings.set( 'size', mediaFrameProps.size ); |
|
} |
|
|
|
mediaFrame = new component.MediaFrameSelect({ |
|
title: control.l10n.add_media, |
|
frame: 'post', |
|
text: control.l10n.add_to_widget, |
|
selection: selection, |
|
mimeType: control.mime_type, |
|
selectedDisplaySettings: control.displaySettings, |
|
showDisplaySettings: control.showDisplaySettings, |
|
metadata: mediaFrameProps, |
|
state: control.isSelected() && 0 === control.model.get( 'attachment_id' ) ? 'embed' : 'insert', |
|
invalidEmbedTypeError: control.l10n.unsupported_file_type |
|
}); |
|
wp.media.frame = mediaFrame; // See wp.media(). |
|
|
|
// Handle selection of a media item. |
|
mediaFrame.on( 'insert', function onInsert() { |
|
var attachment = {}, state = mediaFrame.state(); |
|
|
|
// Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. |
|
if ( 'embed' === state.get( 'id' ) ) { |
|
_.extend( attachment, { id: 0 }, state.props.toJSON() ); |
|
} else { |
|
_.extend( attachment, state.get( 'selection' ).first().toJSON() ); |
|
} |
|
|
|
control.selectedAttachment.set( attachment ); |
|
control.model.set( 'error', false ); |
|
|
|
// Update widget instance. |
|
control.model.set( control.getModelPropsFromMediaFrame( mediaFrame ) ); |
|
}); |
|
|
|
// Disable syncing of attachment changes back to server (except for deletions). See <https://core.trac.wordpress.org/ticket/40403>. |
|
defaultSync = wp.media.model.Attachment.prototype.sync; |
|
wp.media.model.Attachment.prototype.sync = function( method ) { |
|
if ( 'delete' === method ) { |
|
return defaultSync.apply( this, arguments ); |
|
} else { |
|
return $.Deferred().rejectWith( this ).promise(); |
|
} |
|
}; |
|
mediaFrame.on( 'close', function onClose() { |
|
wp.media.model.Attachment.prototype.sync = defaultSync; |
|
}); |
|
|
|
mediaFrame.$el.addClass( 'media-widget' ); |
|
mediaFrame.open(); |
|
|
|
// Clear the selected attachment when it is deleted in the media select frame. |
|
if ( selection ) { |
|
selection.on( 'destroy', function onDestroy( attachment ) { |
|
if ( control.model.get( 'attachment_id' ) === attachment.get( 'id' ) ) { |
|
control.model.set({ |
|
attachment_id: 0, |
|
url: '' |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
/* |
|
* Make sure focus is set inside of modal so that hitting Esc will close |
|
* the modal and not inadvertently cause the widget to collapse in the customizer. |
|
*/ |
|
mediaFrame.$el.find( '.media-frame-menu .media-menu-item.active' ).focus(); |
|
}, |
|
|
|
/** |
|
* Get the instance props from the media selection frame. |
|
* |
|
* @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame. |
|
* @returns {Object} Props. |
|
*/ |
|
getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) { |
|
var control = this, state, mediaFrameProps, modelProps; |
|
|
|
state = mediaFrame.state(); |
|
if ( 'insert' === state.get( 'id' ) ) { |
|
mediaFrameProps = state.get( 'selection' ).first().toJSON(); |
|
mediaFrameProps.postUrl = mediaFrameProps.link; |
|
|
|
if ( control.showDisplaySettings ) { |
|
_.extend( |
|
mediaFrameProps, |
|
mediaFrame.content.get( '.attachments-browser' ).sidebar.get( 'display' ).model.toJSON() |
|
); |
|
} |
|
if ( mediaFrameProps.sizes && mediaFrameProps.size && mediaFrameProps.sizes[ mediaFrameProps.size ] ) { |
|
mediaFrameProps.url = mediaFrameProps.sizes[ mediaFrameProps.size ].url; |
|
} |
|
} else if ( 'embed' === state.get( 'id' ) ) { |
|
mediaFrameProps = _.extend( |
|
state.props.toJSON(), |
|
{ attachment_id: 0 }, // Because some media frames use `attachment_id` not `id`. |
|
control.model.getEmbedResetProps() |
|
); |
|
} else { |
|
throw new Error( 'Unexpected state: ' + state.get( 'id' ) ); |
|
} |
|
|
|
if ( mediaFrameProps.id ) { |
|
mediaFrameProps.attachment_id = mediaFrameProps.id; |
|
} |
|
|
|
modelProps = control.mapMediaToModelProps( mediaFrameProps ); |
|
|
|
// Clear the extension prop so sources will be reset for video and audio media. |
|
_.each( wp.media.view.settings.embedExts, function( ext ) { |
|
if ( ext in control.model.schema && modelProps.url !== modelProps[ ext ] ) { |
|
modelProps[ ext ] = ''; |
|
} |
|
}); |
|
|
|
return modelProps; |
|
}, |
|
|
|
/** |
|
* Map media frame props to model props. |
|
* |
|
* @param {Object} mediaFrameProps - Media frame props. |
|
* @returns {Object} Model props. |
|
*/ |
|
mapMediaToModelProps: function mapMediaToModelProps( mediaFrameProps ) { |
|
var control = this, mediaFramePropToModelPropMap = {}, modelProps = {}, extension; |
|
_.each( control.model.schema, function( fieldSchema, modelProp ) { |
|
|
|
// Ignore widget title attribute. |
|
if ( 'title' === modelProp ) { |
|
return; |
|
} |
|
mediaFramePropToModelPropMap[ fieldSchema.media_prop || modelProp ] = modelProp; |
|
}); |
|
|
|
_.each( mediaFrameProps, function( value, mediaProp ) { |
|
var propName = mediaFramePropToModelPropMap[ mediaProp ] || mediaProp; |
|
if ( control.model.schema[ propName ] ) { |
|
modelProps[ propName ] = value; |
|
} |
|
}); |
|
|
|
if ( 'custom' === mediaFrameProps.size ) { |
|
modelProps.width = mediaFrameProps.customWidth; |
|
modelProps.height = mediaFrameProps.customHeight; |
|
} |
|
|
|
if ( 'post' === mediaFrameProps.link ) { |
|
modelProps.link_url = mediaFrameProps.postUrl || mediaFrameProps.linkUrl; |
|
} else if ( 'file' === mediaFrameProps.link ) { |
|
modelProps.link_url = mediaFrameProps.url; |
|
} |
|
|
|
// Because some media frames use `id` instead of `attachment_id`. |
|
if ( ! mediaFrameProps.attachment_id && mediaFrameProps.id ) { |
|
modelProps.attachment_id = mediaFrameProps.id; |
|
} |
|
|
|
if ( mediaFrameProps.url ) { |
|
extension = mediaFrameProps.url.replace( /#.*$/, '' ).replace( /\?.*$/, '' ).split( '.' ).pop().toLowerCase(); |
|
if ( extension in control.model.schema ) { |
|
modelProps[ extension ] = mediaFrameProps.url; |
|
} |
|
} |
|
|
|
// Always omit the titles derived from mediaFrameProps. |
|
return _.omit( modelProps, 'title' ); |
|
}, |
|
|
|
/** |
|
* Map model props to media frame props. |
|
* |
|
* @param {Object} modelProps - Model props. |
|
* @returns {Object} Media frame props. |
|
*/ |
|
mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) { |
|
var control = this, mediaFrameProps = {}; |
|
|
|
_.each( modelProps, function( value, modelProp ) { |
|
var fieldSchema = control.model.schema[ modelProp ] || {}; |
|
mediaFrameProps[ fieldSchema.media_prop || modelProp ] = value; |
|
}); |
|
|
|
// Some media frames use attachment_id. |
|
mediaFrameProps.attachment_id = mediaFrameProps.id; |
|
|
|
if ( 'custom' === mediaFrameProps.size ) { |
|
mediaFrameProps.customWidth = control.model.get( 'width' ); |
|
mediaFrameProps.customHeight = control.model.get( 'height' ); |
|
} |
|
|
|
return mediaFrameProps; |
|
}, |
|
|
|
/** |
|
* Map model props to previewTemplateProps. |
|
* |
|
* @returns {Object} Preview Template Props. |
|
*/ |
|
mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() { |
|
var control = this, previewTemplateProps = {}; |
|
_.each( control.model.schema, function( value, prop ) { |
|
if ( ! value.hasOwnProperty( 'should_preview_update' ) || value.should_preview_update ) { |
|
previewTemplateProps[ prop ] = control.model.get( prop ); |
|
} |
|
}); |
|
|
|
// Templates need to be aware of the error. |
|
previewTemplateProps.error = control.model.get( 'error' ); |
|
return previewTemplateProps; |
|
}, |
|
|
|
/** |
|
* Open the media frame to modify the selected item. |
|
* |
|
* @abstract |
|
* @returns {void} |
|
*/ |
|
editMedia: function editMedia() { |
|
throw new Error( 'editMedia not implemented' ); |
|
} |
|
}); |
|
|
|
/** |
|
* Media widget model. |
|
* |
|
* @class wp.mediaWidgets.MediaWidgetModel |
|
* @augments Backbone.Model |
|
*/ |
|
component.MediaWidgetModel = Backbone.Model.extend(/** @lends wp.mediaWidgets.MediaWidgetModel.prototype */{ |
|
|
|
/** |
|
* Id attribute. |
|
* |
|
* @type {string} |
|
*/ |
|
idAttribute: 'widget_id', |
|
|
|
/** |
|
* Instance schema. |
|
* |
|
* This adheres to JSON Schema and subclasses should have their schema |
|
* exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). |
|
* |
|
* @type {Object.<string, Object>} |
|
*/ |
|
schema: { |
|
title: { |
|
type: 'string', |
|
'default': '' |
|
}, |
|
attachment_id: { |
|
type: 'integer', |
|
'default': 0 |
|
}, |
|
url: { |
|
type: 'string', |
|
'default': '' |
|
} |
|
}, |
|
|
|
/** |
|
* Get default attribute values. |
|
* |
|
* @returns {Object} Mapping of property names to their default values. |
|
*/ |
|
defaults: function() { |
|
var defaults = {}; |
|
_.each( this.schema, function( fieldSchema, field ) { |
|
defaults[ field ] = fieldSchema['default']; |
|
}); |
|
return defaults; |
|
}, |
|
|
|
/** |
|
* Set attribute value(s). |
|
* |
|
* This is a wrapped version of Backbone.Model#set() which allows us to |
|
* cast the attribute values from the hidden inputs' string values into |
|
* the appropriate data types (integers or booleans). |
|
* |
|
* @param {string|Object} key - Attribute name or attribute pairs. |
|
* @param {mixed|Object} [val] - Attribute value or options object. |
|
* @param {Object} [options] - Options when attribute name and value are passed separately. |
|
* @returns {wp.mediaWidgets.MediaWidgetModel} This model. |
|
*/ |
|
set: function set( key, val, options ) { |
|
var model = this, attrs, opts, castedAttrs; // eslint-disable-line consistent-this |
|
if ( null === key ) { |
|
return model; |
|
} |
|
if ( 'object' === typeof key ) { |
|
attrs = key; |
|
opts = val; |
|
} else { |
|
attrs = {}; |
|
attrs[ key ] = val; |
|
opts = options; |
|
} |
|
|
|
castedAttrs = {}; |
|
_.each( attrs, function( value, name ) { |
|
var type; |
|
if ( ! model.schema[ name ] ) { |
|
castedAttrs[ name ] = value; |
|
return; |
|
} |
|
type = model.schema[ name ].type; |
|
if ( 'array' === type ) { |
|
castedAttrs[ name ] = value; |
|
if ( ! _.isArray( castedAttrs[ name ] ) ) { |
|
castedAttrs[ name ] = castedAttrs[ name ].split( /,/ ); // Good enough for parsing an ID list. |
|
} |
|
if ( model.schema[ name ].items && 'integer' === model.schema[ name ].items.type ) { |
|
castedAttrs[ name ] = _.filter( |
|
_.map( castedAttrs[ name ], function( id ) { |
|
return parseInt( id, 10 ); |
|
}, |
|
function( id ) { |
|
return 'number' === typeof id; |
|
} |
|
) ); |
|
} |
|
} else if ( 'integer' === type ) { |
|
castedAttrs[ name ] = parseInt( value, 10 ); |
|
} else if ( 'boolean' === type ) { |
|
castedAttrs[ name ] = ! ( ! value || '0' === value || 'false' === value ); |
|
} else { |
|
castedAttrs[ name ] = value; |
|
} |
|
}); |
|
|
|
return Backbone.Model.prototype.set.call( this, castedAttrs, opts ); |
|
}, |
|
|
|
/** |
|
* Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment). |
|
* |
|
* @returns {Object} Reset/override props. |
|
*/ |
|
getEmbedResetProps: function getEmbedResetProps() { |
|
return { |
|
id: 0 |
|
}; |
|
} |
|
}); |
|
|
|
/** |
|
* Collection of all widget model instances. |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @type {Backbone.Collection} |
|
*/ |
|
component.modelCollection = new ( Backbone.Collection.extend( { |
|
model: component.MediaWidgetModel |
|
}) )(); |
|
|
|
/** |
|
* Mapping of widget ID to instances of MediaWidgetControl subclasses. |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @type {Object.<string, wp.mediaWidgets.MediaWidgetControl>} |
|
*/ |
|
component.widgetControls = {}; |
|
|
|
/** |
|
* Handle widget being added or initialized for the first time at the widget-added event. |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @param {jQuery.Event} event - Event. |
|
* @param {jQuery} widgetContainer - Widget container element. |
|
* |
|
* @returns {void} |
|
*/ |
|
component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { |
|
var fieldContainer, syncContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone; |
|
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. |
|
idBase = widgetForm.find( '> .id_base' ).val(); |
|
widgetId = widgetForm.find( '> .widget-id' ).val(); |
|
|
|
// Prevent initializing already-added widgets. |
|
if ( component.widgetControls[ widgetId ] ) { |
|
return; |
|
} |
|
|
|
ControlConstructor = component.controlConstructors[ idBase ]; |
|
if ( ! ControlConstructor ) { |
|
return; |
|
} |
|
|
|
ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; |
|
|
|
/* |
|
* Create a container element for the widget control (Backbone.View). |
|
* This is inserted into the DOM immediately before the .widget-content |
|
* element because the contents of this element are essentially "managed" |
|
* by PHP, where each widget update cause the entire element to be emptied |
|
* and replaced with the rendered output of WP_Widget::form() which is |
|
* sent back in Ajax request made to save/update the widget instance. |
|
* To prevent a "flash of replaced DOM elements and re-initialized JS |
|
* components", the JS template is rendered outside of the normal form |
|
* container. |
|
*/ |
|
fieldContainer = $( '<div></div>' ); |
|
syncContainer = widgetContainer.find( '.widget-content:first' ); |
|
syncContainer.before( fieldContainer ); |
|
|
|
/* |
|
* Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state. |
|
* In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model |
|
* from the start, without having to sync with hidden fields. See <https://core.trac.wordpress.org/ticket/33507>. |
|
*/ |
|
modelAttributes = {}; |
|
syncContainer.find( '.media-widget-instance-property' ).each( function() { |
|
var input = $( this ); |
|
modelAttributes[ input.data( 'property' ) ] = input.val(); |
|
}); |
|
modelAttributes.widget_id = widgetId; |
|
|
|
widgetModel = new ModelConstructor( modelAttributes ); |
|
|
|
widgetControl = new ControlConstructor({ |
|
el: fieldContainer, |
|
syncContainer: syncContainer, |
|
model: widgetModel |
|
}); |
|
|
|
/* |
|
* Render the widget once the widget parent's container finishes animating, |
|
* as the widget-added event fires with a slideDown of the container. |
|
* This ensures that the container's dimensions are fixed so that ME.js |
|
* can initialize with the proper dimensions. |
|
*/ |
|
renderWhenAnimationDone = function() { |
|
if ( ! widgetContainer.hasClass( 'open' ) ) { |
|
setTimeout( renderWhenAnimationDone, animatedCheckDelay ); |
|
} else { |
|
widgetControl.render(); |
|
} |
|
}; |
|
renderWhenAnimationDone(); |
|
|
|
/* |
|
* Note that the model and control currently won't ever get garbage-collected |
|
* when a widget gets removed/deleted because there is no widget-removed event. |
|
*/ |
|
component.modelCollection.add( [ widgetModel ] ); |
|
component.widgetControls[ widgetModel.get( 'widget_id' ) ] = widgetControl; |
|
}; |
|
|
|
/** |
|
* Setup widget in accessibility mode. |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @returns {void} |
|
*/ |
|
component.setupAccessibleMode = function setupAccessibleMode() { |
|
var widgetForm, widgetId, idBase, widgetControl, ControlConstructor, ModelConstructor, modelAttributes, fieldContainer, syncContainer; |
|
widgetForm = $( '.editwidget > form' ); |
|
if ( 0 === widgetForm.length ) { |
|
return; |
|
} |
|
|
|
idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val(); |
|
|
|
ControlConstructor = component.controlConstructors[ idBase ]; |
|
if ( ! ControlConstructor ) { |
|
return; |
|
} |
|
|
|
widgetId = widgetForm.find( '> .widget-control-actions > .widget-id' ).val(); |
|
|
|
ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; |
|
fieldContainer = $( '<div></div>' ); |
|
syncContainer = widgetForm.find( '> .widget-inside' ); |
|
syncContainer.before( fieldContainer ); |
|
|
|
modelAttributes = {}; |
|
syncContainer.find( '.media-widget-instance-property' ).each( function() { |
|
var input = $( this ); |
|
modelAttributes[ input.data( 'property' ) ] = input.val(); |
|
}); |
|
modelAttributes.widget_id = widgetId; |
|
|
|
widgetControl = new ControlConstructor({ |
|
el: fieldContainer, |
|
syncContainer: syncContainer, |
|
model: new ModelConstructor( modelAttributes ) |
|
}); |
|
|
|
component.modelCollection.add( [ widgetControl.model ] ); |
|
component.widgetControls[ widgetControl.model.get( 'widget_id' ) ] = widgetControl; |
|
|
|
widgetControl.render(); |
|
}; |
|
|
|
/** |
|
* Sync widget instance data sanitized from server back onto widget model. |
|
* |
|
* This gets called via the 'widget-updated' event when saving a widget from |
|
* the widgets admin screen and also via the 'widget-synced' event when making |
|
* a change to a widget in the customizer. |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @param {jQuery.Event} event - Event. |
|
* @param {jQuery} widgetContainer - Widget container element. |
|
* |
|
* @returns {void} |
|
*/ |
|
component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { |
|
var widgetForm, widgetContent, widgetId, widgetControl, attributes = {}; |
|
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); |
|
widgetId = widgetForm.find( '> .widget-id' ).val(); |
|
|
|
widgetControl = component.widgetControls[ widgetId ]; |
|
if ( ! widgetControl ) { |
|
return; |
|
} |
|
|
|
// Make sure the server-sanitized values get synced back into the model. |
|
widgetContent = widgetForm.find( '> .widget-content' ); |
|
widgetContent.find( '.media-widget-instance-property' ).each( function() { |
|
var property = $( this ).data( 'property' ); |
|
attributes[ property ] = $( this ).val(); |
|
}); |
|
|
|
// Suspend syncing model back to inputs when syncing from inputs to model, preventing infinite loop. |
|
widgetControl.stopListening( widgetControl.model, 'change', widgetControl.syncModelToInputs ); |
|
widgetControl.model.set( attributes ); |
|
widgetControl.listenTo( widgetControl.model, 'change', widgetControl.syncModelToInputs ); |
|
}; |
|
|
|
/** |
|
* Initialize functionality. |
|
* |
|
* This function exists to prevent the JS file from having to boot itself. |
|
* When WordPress enqueues this script, it should have an inline script |
|
* attached which calls wp.mediaWidgets.init(). |
|
* |
|
* @memberOf wp.mediaWidgets |
|
* |
|
* @returns {void} |
|
*/ |
|
component.init = function init() { |
|
var $document = $( document ); |
|
$document.on( 'widget-added', component.handleWidgetAdded ); |
|
$document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); |
|
|
|
/* |
|
* Manually trigger widget-added events for media widgets on the admin |
|
* screen once they are expanded. The widget-added event is not triggered |
|
* for each pre-existing widget on the widgets admin screen like it is |
|
* on the customizer. Likewise, the customizer only triggers widget-added |
|
* when the widget is expanded to just-in-time construct the widget form |
|
* when it is actually going to be displayed. So the following implements |
|
* the same for the widgets admin screen, to invoke the widget-added |
|
* handler when a pre-existing media widget is expanded. |
|
*/ |
|
$( function initializeExistingWidgetContainers() { |
|
var widgetContainers; |
|
if ( 'widgets' !== window.pagenow ) { |
|
return; |
|
} |
|
widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); |
|
widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { |
|
var widgetContainer = $( this ); |
|
component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); |
|
}); |
|
|
|
// Accessibility mode. |
|
$( window ).on( 'load', function() { |
|
component.setupAccessibleMode(); |
|
}); |
|
}); |
|
}; |
|
|
|
return component; |
|
})( jQuery );
|
|
|