first commit
This commit is contained in:
462
wp-admin/js/widgets/custom-html-widgets.js
Normal file
462
wp-admin/js/widgets/custom-html-widgets.js
Normal file
@ -0,0 +1,462 @@
|
||||
/**
|
||||
* @output wp-admin/js/widgets/custom-html-widgets.js
|
||||
*/
|
||||
|
||||
/* global wp */
|
||||
/* eslint consistent-this: [ "error", "control" ] */
|
||||
/* eslint no-magic-numbers: ["error", { "ignore": [0,1,-1] }] */
|
||||
|
||||
/**
|
||||
* @namespace wp.customHtmlWidget
|
||||
* @memberOf wp
|
||||
*/
|
||||
wp.customHtmlWidgets = ( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
var component = {
|
||||
idBases: [ 'custom_html' ],
|
||||
codeEditorSettings: {},
|
||||
l10n: {
|
||||
errorNotice: {
|
||||
singular: '',
|
||||
plural: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
component.CustomHtmlWidgetControl = Backbone.View.extend(/** @lends wp.customHtmlWidgets.CustomHtmlWidgetControl.prototype */{
|
||||
|
||||
/**
|
||||
* View events.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
events: {},
|
||||
|
||||
/**
|
||||
* Text widget control.
|
||||
*
|
||||
* @constructs wp.customHtmlWidgets.CustomHtmlWidgetControl
|
||||
* @augments Backbone.View
|
||||
* @abstract
|
||||
*
|
||||
* @param {Object} options - Options.
|
||||
* @param {jQuery} options.el - Control field container element.
|
||||
* @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
initialize: function initialize( options ) {
|
||||
var control = this;
|
||||
|
||||
if ( ! options.el ) {
|
||||
throw new Error( 'Missing options.el' );
|
||||
}
|
||||
if ( ! options.syncContainer ) {
|
||||
throw new Error( 'Missing options.syncContainer' );
|
||||
}
|
||||
|
||||
Backbone.View.prototype.initialize.call( control, options );
|
||||
control.syncContainer = options.syncContainer;
|
||||
control.widgetIdBase = control.syncContainer.parent().find( '.id_base' ).val();
|
||||
control.widgetNumber = control.syncContainer.parent().find( '.widget_number' ).val();
|
||||
control.customizeSettingId = 'widget_' + control.widgetIdBase + '[' + String( control.widgetNumber ) + ']';
|
||||
|
||||
control.$el.addClass( 'custom-html-widget-fields' );
|
||||
control.$el.html( wp.template( 'widget-custom-html-control-fields' )( { codeEditorDisabled: component.codeEditorSettings.disabled } ) );
|
||||
|
||||
control.errorNoticeContainer = control.$el.find( '.code-editor-error-container' );
|
||||
control.currentErrorAnnotations = [];
|
||||
control.saveButton = control.syncContainer.add( control.syncContainer.parent().find( '.widget-control-actions' ) ).find( '.widget-control-save, #savewidget' );
|
||||
control.saveButton.addClass( 'custom-html-widget-save-button' ); // To facilitate style targeting.
|
||||
|
||||
control.fields = {
|
||||
title: control.$el.find( '.title' ),
|
||||
content: control.$el.find( '.content' )
|
||||
};
|
||||
|
||||
// Sync input fields to hidden sync fields which actually get sent to the server.
|
||||
_.each( control.fields, function( fieldInput, fieldName ) {
|
||||
fieldInput.on( 'input change', function updateSyncField() {
|
||||
var syncInput = control.syncContainer.find( '.sync-input.' + fieldName );
|
||||
if ( syncInput.val() !== fieldInput.val() ) {
|
||||
syncInput.val( fieldInput.val() );
|
||||
syncInput.trigger( 'change' );
|
||||
}
|
||||
});
|
||||
|
||||
// Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event.
|
||||
fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update input fields from the sync fields.
|
||||
*
|
||||
* This function is called at the widget-updated and widget-synced events.
|
||||
* A field will only be updated if it is not currently focused, to avoid
|
||||
* overwriting content that the user is entering.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
updateFields: function updateFields() {
|
||||
var control = this, syncInput;
|
||||
|
||||
if ( ! control.fields.title.is( document.activeElement ) ) {
|
||||
syncInput = control.syncContainer.find( '.sync-input.title' );
|
||||
control.fields.title.val( syncInput.val() );
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevent updating content when the editor is focused or if there are current error annotations,
|
||||
* to prevent the editor's contents from getting sanitized as soon as a user removes focus from
|
||||
* the editor. This is particularly important for users who cannot unfiltered_html.
|
||||
*/
|
||||
control.contentUpdateBypassed = control.fields.content.is( document.activeElement ) || control.editor && control.editor.codemirror.state.focused || 0 !== control.currentErrorAnnotations.length;
|
||||
if ( ! control.contentUpdateBypassed ) {
|
||||
syncInput = control.syncContainer.find( '.sync-input.content' );
|
||||
control.fields.content.val( syncInput.val() );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show linting error notice.
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error annotations.
|
||||
* @return {void}
|
||||
*/
|
||||
updateErrorNotice: function( errorAnnotations ) {
|
||||
var control = this, errorNotice, message = '', customizeSetting;
|
||||
|
||||
if ( 1 === errorAnnotations.length ) {
|
||||
message = component.l10n.errorNotice.singular.replace( '%d', '1' );
|
||||
} else if ( errorAnnotations.length > 1 ) {
|
||||
message = component.l10n.errorNotice.plural.replace( '%d', String( errorAnnotations.length ) );
|
||||
}
|
||||
|
||||
if ( control.fields.content[0].setCustomValidity ) {
|
||||
control.fields.content[0].setCustomValidity( message );
|
||||
}
|
||||
|
||||
if ( wp.customize && wp.customize.has( control.customizeSettingId ) ) {
|
||||
customizeSetting = wp.customize( control.customizeSettingId );
|
||||
customizeSetting.notifications.remove( 'htmlhint_error' );
|
||||
if ( 0 !== errorAnnotations.length ) {
|
||||
customizeSetting.notifications.add( 'htmlhint_error', new wp.customize.Notification( 'htmlhint_error', {
|
||||
message: message,
|
||||
type: 'error'
|
||||
} ) );
|
||||
}
|
||||
} else if ( 0 !== errorAnnotations.length ) {
|
||||
errorNotice = $( '<div class="inline notice notice-error notice-alt"></div>' );
|
||||
errorNotice.append( $( '<p></p>', {
|
||||
text: message
|
||||
} ) );
|
||||
control.errorNoticeContainer.empty();
|
||||
control.errorNoticeContainer.append( errorNotice );
|
||||
control.errorNoticeContainer.slideDown( 'fast' );
|
||||
wp.a11y.speak( message );
|
||||
} else {
|
||||
control.errorNoticeContainer.slideUp( 'fast' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize editor.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
initializeEditor: function initializeEditor() {
|
||||
var control = this, settings;
|
||||
|
||||
if ( component.codeEditorSettings.disabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings = _.extend( {}, component.codeEditorSettings, {
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field before the editor.
|
||||
*
|
||||
* @ignore
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
onTabPrevious: function onTabPrevious() {
|
||||
control.fields.title.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field after the editor.
|
||||
*
|
||||
* @ignore
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
onTabNext: function onTabNext() {
|
||||
var tabbables = control.syncContainer.add( control.syncContainer.parent().find( '.widget-position, .widget-control-actions' ) ).find( ':tabbable' );
|
||||
tabbables.first().focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable save button and store linting errors for use in updateFields.
|
||||
*
|
||||
* @ignore
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error notifications.
|
||||
* @return {void}
|
||||
*/
|
||||
onChangeLintingErrors: function onChangeLintingErrors( errorAnnotations ) {
|
||||
control.currentErrorAnnotations = errorAnnotations;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update error notice.
|
||||
*
|
||||
* @ignore
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error annotations.
|
||||
* @return {void}
|
||||
*/
|
||||
onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
|
||||
control.saveButton.toggleClass( 'validation-blocked disabled', errorAnnotations.length > 0 );
|
||||
control.updateErrorNotice( errorAnnotations );
|
||||
}
|
||||
});
|
||||
|
||||
control.editor = wp.codeEditor.initialize( control.fields.content, settings );
|
||||
|
||||
// Improve the editor accessibility.
|
||||
$( control.editor.codemirror.display.lineDiv )
|
||||
.attr({
|
||||
role: 'textbox',
|
||||
'aria-multiline': 'true',
|
||||
'aria-labelledby': control.fields.content[0].id + '-label',
|
||||
'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
|
||||
});
|
||||
|
||||
// Focus the editor when clicking on its label.
|
||||
$( '#' + control.fields.content[0].id + '-label' ).on( 'click', function() {
|
||||
control.editor.codemirror.focus();
|
||||
});
|
||||
|
||||
control.fields.content.on( 'change', function() {
|
||||
if ( this.value !== control.editor.codemirror.getValue() ) {
|
||||
control.editor.codemirror.setValue( this.value );
|
||||
}
|
||||
});
|
||||
control.editor.codemirror.on( 'change', function() {
|
||||
var value = control.editor.codemirror.getValue();
|
||||
if ( value !== control.fields.content.val() ) {
|
||||
control.fields.content.val( value ).trigger( 'change' );
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure the editor gets updated if the content was updated on the server (sanitization) but not updated in the editor since it was focused.
|
||||
control.editor.codemirror.on( 'blur', function() {
|
||||
if ( control.contentUpdateBypassed ) {
|
||||
control.syncContainer.find( '.sync-input.content' ).trigger( 'change' );
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent hitting Esc from collapsing the widget control.
|
||||
if ( wp.customize ) {
|
||||
control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
|
||||
var escKeyCode = 27;
|
||||
if ( escKeyCode === event.keyCode ) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Mapping of widget ID to instances of CustomHtmlWidgetControl subclasses.
|
||||
*
|
||||
* @alias wp.customHtmlWidgets.widgetControls
|
||||
*
|
||||
* @type {Object.<string, wp.textWidgets.CustomHtmlWidgetControl>}
|
||||
*/
|
||||
component.widgetControls = {};
|
||||
|
||||
/**
|
||||
* Handle widget being added or initialized for the first time at the widget-added event.
|
||||
*
|
||||
* @alias wp.customHtmlWidgets.handleWidgetAdded
|
||||
*
|
||||
* @param {jQuery.Event} event - Event.
|
||||
* @param {jQuery} widgetContainer - Widget container element.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
|
||||
var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer;
|
||||
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();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent initializing already-added widgets.
|
||||
widgetId = widgetForm.find( '.widget-id' ).val();
|
||||
if ( component.widgetControls[ widgetId ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a container element for the widget control fields.
|
||||
* This is inserted into the DOM immediately before the 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 );
|
||||
|
||||
widgetControl = new component.CustomHtmlWidgetControl({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
component.widgetControls[ widgetId ] = widgetControl;
|
||||
|
||||
/*
|
||||
* 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 textarea is visible and the editor can be initialized.
|
||||
*/
|
||||
renderWhenAnimationDone = function() {
|
||||
if ( ! ( wp.customize ? widgetContainer.parent().hasClass( 'expanded' ) : widgetContainer.hasClass( 'open' ) ) ) { // Core merge: The wp.customize condition can be eliminated with this change being in core: https://github.com/xwp/wordpress-develop/pull/247/commits/5322387d
|
||||
setTimeout( renderWhenAnimationDone, animatedCheckDelay );
|
||||
} else {
|
||||
widgetControl.initializeEditor();
|
||||
}
|
||||
};
|
||||
renderWhenAnimationDone();
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup widget in accessibility mode.
|
||||
*
|
||||
* @alias wp.customHtmlWidgets.setupAccessibleMode
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
component.setupAccessibleMode = function setupAccessibleMode() {
|
||||
var widgetForm, idBase, widgetControl, fieldContainer, syncContainer;
|
||||
widgetForm = $( '.editwidget > form' );
|
||||
if ( 0 === widgetForm.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
idBase = widgetForm.find( '.id_base' ).val();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetForm.find( '> .widget-inside' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
widgetControl = new component.CustomHtmlWidgetControl({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
widgetControl.initializeEditor();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @alias wp.customHtmlWidgets.handleWidgetUpdated
|
||||
*
|
||||
* @param {jQuery.Event} event - Event.
|
||||
* @param {jQuery} widgetContainer - Widget container element.
|
||||
* @return {void}
|
||||
*/
|
||||
component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) {
|
||||
var widgetForm, widgetId, widgetControl, idBase;
|
||||
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
|
||||
|
||||
idBase = widgetForm.find( '> .id_base' ).val();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetId = widgetForm.find( '> .widget-id' ).val();
|
||||
widgetControl = component.widgetControls[ widgetId ];
|
||||
if ( ! widgetControl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetControl.updateFields();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.textWidgets.init().
|
||||
*
|
||||
* @alias wp.customHtmlWidgets.init
|
||||
*
|
||||
* @param {Object} settings - Options for code editor, exported from PHP.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
component.init = function init( settings ) {
|
||||
var $document = $( document );
|
||||
_.extend( component.codeEditorSettings, settings );
|
||||
|
||||
$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.
|
||||
if ( document.readyState === 'complete' ) {
|
||||
// Page is fully loaded.
|
||||
component.setupAccessibleMode();
|
||||
} else {
|
||||
// Page is still loading.
|
||||
$( window ).on( 'load', function() {
|
||||
component.setupAccessibleMode();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return component;
|
||||
})( jQuery );
|
2
wp-admin/js/widgets/custom-html-widgets.min.js
vendored
Normal file
2
wp-admin/js/widgets/custom-html-widgets.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
154
wp-admin/js/widgets/media-audio-widget.js
Normal file
154
wp-admin/js/widgets/media-audio-widget.js
Normal file
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @output wp-admin/js/widgets/media-audio-widget.js
|
||||
*/
|
||||
|
||||
/* eslint consistent-this: [ "error", "control" ] */
|
||||
(function( component ) {
|
||||
'use strict';
|
||||
|
||||
var AudioWidgetModel, AudioWidgetControl, AudioDetailsMediaFrame;
|
||||
|
||||
/**
|
||||
* Custom audio details frame that removes the replace-audio state.
|
||||
*
|
||||
* @class wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame
|
||||
* @augments wp.media.view.MediaFrame.AudioDetails
|
||||
*/
|
||||
AudioDetailsMediaFrame = wp.media.view.MediaFrame.AudioDetails.extend(/** @lends wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame.prototype */{
|
||||
|
||||
/**
|
||||
* Create the default states.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
createStates: function createStates() {
|
||||
this.states.add([
|
||||
new wp.media.controller.AudioDetails({
|
||||
media: this.media
|
||||
}),
|
||||
|
||||
new wp.media.controller.MediaLibrary({
|
||||
type: 'audio',
|
||||
id: 'add-audio-source',
|
||||
title: wp.media.view.l10n.audioAddSourceTitle,
|
||||
toolbar: 'add-audio-source',
|
||||
media: this.media,
|
||||
menu: false
|
||||
})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Audio widget model.
|
||||
*
|
||||
* See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @class wp.mediaWidgets.modelConstructors.media_audio
|
||||
* @augments wp.mediaWidgets.MediaWidgetModel
|
||||
*/
|
||||
AudioWidgetModel = component.MediaWidgetModel.extend({});
|
||||
|
||||
/**
|
||||
* Audio widget control.
|
||||
*
|
||||
* See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @class wp.mediaWidgets.controlConstructors.media_audio
|
||||
* @augments wp.mediaWidgets.MediaWidgetControl
|
||||
*/
|
||||
AudioWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_audio.prototype */{
|
||||
|
||||
/**
|
||||
* Show display settings.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
showDisplaySettings: false,
|
||||
|
||||
/**
|
||||
* Map model props to media frame props.
|
||||
*
|
||||
* @param {Object} modelProps - Model props.
|
||||
* @return {Object} Media frame props.
|
||||
*/
|
||||
mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) {
|
||||
var control = this, mediaFrameProps;
|
||||
mediaFrameProps = component.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call( control, modelProps );
|
||||
mediaFrameProps.link = 'embed';
|
||||
return mediaFrameProps;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render preview.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
renderPreview: function renderPreview() {
|
||||
var control = this, previewContainer, previewTemplate, attachmentId, attachmentUrl;
|
||||
attachmentId = control.model.get( 'attachment_id' );
|
||||
attachmentUrl = control.model.get( 'url' );
|
||||
|
||||
if ( ! attachmentId && ! attachmentUrl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
previewContainer = control.$el.find( '.media-widget-preview' );
|
||||
previewTemplate = wp.template( 'wp-media-widget-audio-preview' );
|
||||
|
||||
previewContainer.html( previewTemplate({
|
||||
model: {
|
||||
attachment_id: control.model.get( 'attachment_id' ),
|
||||
src: attachmentUrl
|
||||
},
|
||||
error: control.model.get( 'error' )
|
||||
}));
|
||||
wp.mediaelement.initialize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the media audio-edit frame to modify the selected item.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
editMedia: function editMedia() {
|
||||
var control = this, mediaFrame, metadata, updateCallback;
|
||||
|
||||
metadata = control.mapModelToMediaFrameProps( control.model.toJSON() );
|
||||
|
||||
// Set up the media frame.
|
||||
mediaFrame = new AudioDetailsMediaFrame({
|
||||
frame: 'audio',
|
||||
state: 'audio-details',
|
||||
metadata: metadata
|
||||
});
|
||||
wp.media.frame = mediaFrame;
|
||||
mediaFrame.$el.addClass( 'media-widget' );
|
||||
|
||||
updateCallback = function( mediaFrameProps ) {
|
||||
|
||||
// Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview.
|
||||
control.selectedAttachment.set( mediaFrameProps );
|
||||
|
||||
control.model.set( _.extend(
|
||||
control.model.defaults(),
|
||||
control.mapMediaToModelProps( mediaFrameProps ),
|
||||
{ error: false }
|
||||
) );
|
||||
};
|
||||
|
||||
mediaFrame.state( 'audio-details' ).on( 'update', updateCallback );
|
||||
mediaFrame.state( 'replace-audio' ).on( 'replace', updateCallback );
|
||||
mediaFrame.on( 'close', function() {
|
||||
mediaFrame.detach();
|
||||
});
|
||||
|
||||
mediaFrame.open();
|
||||
}
|
||||
});
|
||||
|
||||
// Exports.
|
||||
component.controlConstructors.media_audio = AudioWidgetControl;
|
||||
component.modelConstructors.media_audio = AudioWidgetModel;
|
||||
|
||||
})( wp.mediaWidgets );
|
2
wp-admin/js/widgets/media-audio-widget.min.js
vendored
Normal file
2
wp-admin/js/widgets/media-audio-widget.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
!function(t){"use strict";var a=wp.media.view.MediaFrame.AudioDetails.extend({createStates:function(){this.states.add([new wp.media.controller.AudioDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"audio",id:"add-audio-source",title:wp.media.view.l10n.audioAddSourceTitle,toolbar:"add-audio-source",media:this.media,menu:!1})])}}),e=t.MediaWidgetModel.extend({}),d=t.MediaWidgetControl.extend({showDisplaySettings:!1,mapModelToMediaFrameProps:function(e){e=t.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(this,e);return e.link="embed",e},renderPreview:function(){var e,t=this,d=t.model.get("attachment_id"),a=t.model.get("url");(d||a)&&(d=t.$el.find(".media-widget-preview"),e=wp.template("wp-media-widget-audio-preview"),d.html(e({model:{attachment_id:t.model.get("attachment_id"),src:a},error:t.model.get("error")})),wp.mediaelement.initialize())},editMedia:function(){var t=this,e=t.mapModelToMediaFrameProps(t.model.toJSON()),d=new a({frame:"audio",state:"audio-details",metadata:e});(wp.media.frame=d).$el.addClass("media-widget"),e=function(e){t.selectedAttachment.set(e),t.model.set(_.extend(t.model.defaults(),t.mapMediaToModelProps(e),{error:!1}))},d.state("audio-details").on("update",e),d.state("replace-audio").on("replace",e),d.on("close",function(){d.detach()}),d.open()}});t.controlConstructors.media_audio=d,t.modelConstructors.media_audio=e}(wp.mediaWidgets);
|
341
wp-admin/js/widgets/media-gallery-widget.js
Normal file
341
wp-admin/js/widgets/media-gallery-widget.js
Normal file
@ -0,0 +1,341 @@
|
||||
/**
|
||||
* @output wp-admin/js/widgets/media-gallery-widget.js
|
||||
*/
|
||||
|
||||
/* eslint consistent-this: [ "error", "control" ] */
|
||||
(function( component ) {
|
||||
'use strict';
|
||||
|
||||
var GalleryWidgetModel, GalleryWidgetControl, GalleryDetailsMediaFrame;
|
||||
|
||||
/**
|
||||
* Custom gallery details frame.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @class wp.mediaWidgets~GalleryDetailsMediaFrame
|
||||
* @augments wp.media.view.MediaFrame.Post
|
||||
*/
|
||||
GalleryDetailsMediaFrame = wp.media.view.MediaFrame.Post.extend(/** @lends wp.mediaWidgets~GalleryDetailsMediaFrame.prototype */{
|
||||
|
||||
/**
|
||||
* Create the default states.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @return {void}
|
||||
*/
|
||||
createStates: function createStates() {
|
||||
this.states.add([
|
||||
new wp.media.controller.Library({
|
||||
id: 'gallery',
|
||||
title: wp.media.view.l10n.createGalleryTitle,
|
||||
priority: 40,
|
||||
toolbar: 'main-gallery',
|
||||
filterable: 'uploaded',
|
||||
multiple: 'add',
|
||||
editable: true,
|
||||
|
||||
library: wp.media.query( _.defaults({
|
||||
type: 'image'
|
||||
}, this.options.library ) )
|
||||
}),
|
||||
|
||||
// Gallery states.
|
||||
new wp.media.controller.GalleryEdit({
|
||||
library: this.options.selection,
|
||||
editing: this.options.editing,
|
||||
menu: 'gallery'
|
||||
}),
|
||||
|
||||
new wp.media.controller.GalleryAdd()
|
||||
]);
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* Gallery widget model.
|
||||
*
|
||||
* See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @since 4.9.0
|
||||
*
|
||||
* @class wp.mediaWidgets.modelConstructors.media_gallery
|
||||
* @augments wp.mediaWidgets.MediaWidgetModel
|
||||
*/
|
||||
GalleryWidgetModel = component.MediaWidgetModel.extend(/** @lends wp.mediaWidgets.modelConstructors.media_gallery.prototype */{} );
|
||||
|
||||
GalleryWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_gallery.prototype */{
|
||||
|
||||
/**
|
||||
* View events.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @type {object}
|
||||
*/
|
||||
events: _.extend( {}, component.MediaWidgetControl.prototype.events, {
|
||||
'click .media-widget-gallery-preview': 'editMedia'
|
||||
} ),
|
||||
|
||||
/**
|
||||
* Gallery widget control.
|
||||
*
|
||||
* See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @constructs wp.mediaWidgets.controlConstructors.media_gallery
|
||||
* @augments wp.mediaWidgets.MediaWidgetControl
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @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.
|
||||
* @return {void}
|
||||
*/
|
||||
initialize: function initialize( options ) {
|
||||
var control = this;
|
||||
|
||||
component.MediaWidgetControl.prototype.initialize.call( control, options );
|
||||
|
||||
_.bindAll( control, 'updateSelectedAttachments', 'handleAttachmentDestroy' );
|
||||
control.selectedAttachments = new wp.media.model.Attachments();
|
||||
control.model.on( 'change:ids', control.updateSelectedAttachments );
|
||||
control.selectedAttachments.on( 'change', control.renderPreview );
|
||||
control.selectedAttachments.on( 'reset', control.renderPreview );
|
||||
control.updateSelectedAttachments();
|
||||
|
||||
/*
|
||||
* Refresh a Gallery widget partial when the user modifies one of the selected attachments.
|
||||
* This ensures that when an attachment's caption is updated in the media modal the Gallery
|
||||
* widget in the preview will then be refreshed to show the change. Normally doing this
|
||||
* would not be necessary because all of the state should be contained inside the changeset,
|
||||
* as everything done in the Customizer should not make a change to the site unless the
|
||||
* changeset itself is published. Attachments are a current exception to this rule.
|
||||
* For a proposal to include attachments in the customized state, see #37887.
|
||||
*/
|
||||
if ( wp.customize && wp.customize.previewer ) {
|
||||
control.selectedAttachments.on( 'change', function() {
|
||||
wp.customize.previewer.send( 'refresh-widget-partial', control.model.get( 'widget_id' ) );
|
||||
} );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the selected attachments if necessary.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @return {void}
|
||||
*/
|
||||
updateSelectedAttachments: function updateSelectedAttachments() {
|
||||
var control = this, newIds, oldIds, removedIds, addedIds, addedQuery;
|
||||
|
||||
newIds = control.model.get( 'ids' );
|
||||
oldIds = _.pluck( control.selectedAttachments.models, 'id' );
|
||||
|
||||
removedIds = _.difference( oldIds, newIds );
|
||||
_.each( removedIds, function( removedId ) {
|
||||
control.selectedAttachments.remove( control.selectedAttachments.get( removedId ) );
|
||||
});
|
||||
|
||||
addedIds = _.difference( newIds, oldIds );
|
||||
if ( addedIds.length ) {
|
||||
addedQuery = wp.media.query({
|
||||
order: 'ASC',
|
||||
orderby: 'post__in',
|
||||
perPage: -1,
|
||||
post__in: newIds,
|
||||
query: true,
|
||||
type: 'image'
|
||||
});
|
||||
addedQuery.more().done( function() {
|
||||
control.selectedAttachments.reset( addedQuery.models );
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render preview.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @return {void}
|
||||
*/
|
||||
renderPreview: function renderPreview() {
|
||||
var control = this, previewContainer, previewTemplate, data;
|
||||
|
||||
previewContainer = control.$el.find( '.media-widget-preview' );
|
||||
previewTemplate = wp.template( 'wp-media-widget-gallery-preview' );
|
||||
|
||||
data = control.previewTemplateProps.toJSON();
|
||||
data.attachments = {};
|
||||
control.selectedAttachments.each( function( attachment ) {
|
||||
data.attachments[ attachment.id ] = attachment.toJSON();
|
||||
} );
|
||||
|
||||
previewContainer.html( previewTemplate( data ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether there are selected attachments.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @return {boolean} Selected.
|
||||
*/
|
||||
isSelected: function isSelected() {
|
||||
var control = this;
|
||||
|
||||
if ( control.model.get( 'error' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return control.model.get( 'ids' ).length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the media select frame to edit images.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @return {void}
|
||||
*/
|
||||
editMedia: function editMedia() {
|
||||
var control = this, selection, mediaFrame, mediaFrameProps;
|
||||
|
||||
selection = new wp.media.model.Selection( control.selectedAttachments.models, {
|
||||
multiple: true
|
||||
});
|
||||
|
||||
mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() );
|
||||
selection.gallery = new Backbone.Model( mediaFrameProps );
|
||||
if ( mediaFrameProps.size ) {
|
||||
control.displaySettings.set( 'size', mediaFrameProps.size );
|
||||
}
|
||||
mediaFrame = new GalleryDetailsMediaFrame({
|
||||
frame: 'manage',
|
||||
text: control.l10n.add_to_widget,
|
||||
selection: selection,
|
||||
mimeType: control.mime_type,
|
||||
selectedDisplaySettings: control.displaySettings,
|
||||
showDisplaySettings: control.showDisplaySettings,
|
||||
metadata: mediaFrameProps,
|
||||
editing: true,
|
||||
multiple: true,
|
||||
state: 'gallery-edit'
|
||||
});
|
||||
wp.media.frame = mediaFrame; // See wp.media().
|
||||
|
||||
// Handle selection of a media item.
|
||||
mediaFrame.on( 'update', function onUpdate( newSelection ) {
|
||||
var state = mediaFrame.state(), resultSelection;
|
||||
|
||||
resultSelection = newSelection || state.get( 'selection' );
|
||||
if ( ! resultSelection ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy orderby_random from gallery state.
|
||||
if ( resultSelection.gallery ) {
|
||||
control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) );
|
||||
}
|
||||
|
||||
// Directly update selectedAttachments to prevent needing to do additional request.
|
||||
control.selectedAttachments.reset( resultSelection.models );
|
||||
|
||||
// Update models in the widget instance.
|
||||
control.model.set( {
|
||||
ids: _.pluck( resultSelection.models, 'id' )
|
||||
} );
|
||||
} );
|
||||
|
||||
mediaFrame.$el.addClass( 'media-widget' );
|
||||
mediaFrame.open();
|
||||
|
||||
if ( selection ) {
|
||||
selection.on( 'destroy', control.handleAttachmentDestroy );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the media select frame to chose an item.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @return {void}
|
||||
*/
|
||||
selectMedia: function selectMedia() {
|
||||
var control = this, selection, mediaFrame, mediaFrameProps;
|
||||
selection = new wp.media.model.Selection( control.selectedAttachments.models, {
|
||||
multiple: true
|
||||
});
|
||||
|
||||
mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() );
|
||||
if ( mediaFrameProps.size ) {
|
||||
control.displaySettings.set( 'size', mediaFrameProps.size );
|
||||
}
|
||||
mediaFrame = new GalleryDetailsMediaFrame({
|
||||
frame: 'select',
|
||||
text: control.l10n.add_to_widget,
|
||||
selection: selection,
|
||||
mimeType: control.mime_type,
|
||||
selectedDisplaySettings: control.displaySettings,
|
||||
showDisplaySettings: control.showDisplaySettings,
|
||||
metadata: mediaFrameProps,
|
||||
state: 'gallery'
|
||||
});
|
||||
wp.media.frame = mediaFrame; // See wp.media().
|
||||
|
||||
// Handle selection of a media item.
|
||||
mediaFrame.on( 'update', function onUpdate( newSelection ) {
|
||||
var state = mediaFrame.state(), resultSelection;
|
||||
|
||||
resultSelection = newSelection || state.get( 'selection' );
|
||||
if ( ! resultSelection ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy orderby_random from gallery state.
|
||||
if ( resultSelection.gallery ) {
|
||||
control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) );
|
||||
}
|
||||
|
||||
// Directly update selectedAttachments to prevent needing to do additional request.
|
||||
control.selectedAttachments.reset( resultSelection.models );
|
||||
|
||||
// Update widget instance.
|
||||
control.model.set( {
|
||||
ids: _.pluck( resultSelection.models, 'id' )
|
||||
} );
|
||||
} );
|
||||
|
||||
mediaFrame.$el.addClass( 'media-widget' );
|
||||
mediaFrame.open();
|
||||
|
||||
if ( selection ) {
|
||||
selection.on( 'destroy', control.handleAttachmentDestroy );
|
||||
}
|
||||
|
||||
/*
|
||||
* 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( ':focusable:first' ).focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the selected attachment when it is deleted in the media select frame.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @param {wp.media.models.Attachment} attachment - Attachment.
|
||||
* @return {void}
|
||||
*/
|
||||
handleAttachmentDestroy: function handleAttachmentDestroy( attachment ) {
|
||||
var control = this;
|
||||
control.model.set( {
|
||||
ids: _.difference(
|
||||
control.model.get( 'ids' ),
|
||||
[ attachment.id ]
|
||||
)
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
// Exports.
|
||||
component.controlConstructors.media_gallery = GalleryWidgetControl;
|
||||
component.modelConstructors.media_gallery = GalleryWidgetModel;
|
||||
|
||||
})( wp.mediaWidgets );
|
2
wp-admin/js/widgets/media-gallery-widget.min.js
vendored
Normal file
2
wp-admin/js/widgets/media-gallery-widget.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
!function(i){"use strict";var a=wp.media.view.MediaFrame.Post.extend({createStates:function(){this.states.add([new wp.media.controller.Library({id:"gallery",title:wp.media.view.l10n.createGalleryTitle,priority:40,toolbar:"main-gallery",filterable:"uploaded",multiple:"add",editable:!0,library:wp.media.query(_.defaults({type:"image"},this.options.library))}),new wp.media.controller.GalleryEdit({library:this.options.selection,editing:this.options.editing,menu:"gallery"}),new wp.media.controller.GalleryAdd])}}),e=i.MediaWidgetModel.extend({}),t=i.MediaWidgetControl.extend({events:_.extend({},i.MediaWidgetControl.prototype.events,{"click .media-widget-gallery-preview":"editMedia"}),initialize:function(e){var t=this;i.MediaWidgetControl.prototype.initialize.call(t,e),_.bindAll(t,"updateSelectedAttachments","handleAttachmentDestroy"),t.selectedAttachments=new wp.media.model.Attachments,t.model.on("change:ids",t.updateSelectedAttachments),t.selectedAttachments.on("change",t.renderPreview),t.selectedAttachments.on("reset",t.renderPreview),t.updateSelectedAttachments(),wp.customize&&wp.customize.previewer&&t.selectedAttachments.on("change",function(){wp.customize.previewer.send("refresh-widget-partial",t.model.get("widget_id"))})},updateSelectedAttachments:function(){var e,t=this,i=t.model.get("ids"),d=_.pluck(t.selectedAttachments.models,"id"),a=_.difference(d,i);_.each(a,function(e){t.selectedAttachments.remove(t.selectedAttachments.get(e))}),_.difference(i,d).length&&(e=wp.media.query({order:"ASC",orderby:"post__in",perPage:-1,post__in:i,query:!0,type:"image"})).more().done(function(){t.selectedAttachments.reset(e.models)})},renderPreview:function(){var e=this,t=e.$el.find(".media-widget-preview"),i=wp.template("wp-media-widget-gallery-preview"),d=e.previewTemplateProps.toJSON();d.attachments={},e.selectedAttachments.each(function(e){d.attachments[e.id]=e.toJSON()}),t.html(i(d))},isSelected:function(){return!this.model.get("error")&&0<this.model.get("ids").length},editMedia:function(){var i,d=this,e=new wp.media.model.Selection(d.selectedAttachments.models,{multiple:!0}),t=d.mapModelToMediaFrameProps(d.model.toJSON());e.gallery=new Backbone.Model(t),t.size&&d.displaySettings.set("size",t.size),i=new a({frame:"manage",text:d.l10n.add_to_widget,selection:e,mimeType:d.mime_type,selectedDisplaySettings:d.displaySettings,showDisplaySettings:d.showDisplaySettings,metadata:t,editing:!0,multiple:!0,state:"gallery-edit"}),(wp.media.frame=i).on("update",function(e){var t=i.state(),e=e||t.get("selection");e&&(e.gallery&&d.model.set(d.mapMediaToModelProps(e.gallery.toJSON())),d.selectedAttachments.reset(e.models),d.model.set({ids:_.pluck(e.models,"id")}))}),i.$el.addClass("media-widget"),i.open(),e&&e.on("destroy",d.handleAttachmentDestroy)},selectMedia:function(){var i,d=this,e=new wp.media.model.Selection(d.selectedAttachments.models,{multiple:!0}),t=d.mapModelToMediaFrameProps(d.model.toJSON());t.size&&d.displaySettings.set("size",t.size),i=new a({frame:"select",text:d.l10n.add_to_widget,selection:e,mimeType:d.mime_type,selectedDisplaySettings:d.displaySettings,showDisplaySettings:d.showDisplaySettings,metadata:t,state:"gallery"}),(wp.media.frame=i).on("update",function(e){var t=i.state(),e=e||t.get("selection");e&&(e.gallery&&d.model.set(d.mapMediaToModelProps(e.gallery.toJSON())),d.selectedAttachments.reset(e.models),d.model.set({ids:_.pluck(e.models,"id")}))}),i.$el.addClass("media-widget"),i.open(),e&&e.on("destroy",d.handleAttachmentDestroy),i.$el.find(":focusable:first").focus()},handleAttachmentDestroy:function(e){this.model.set({ids:_.difference(this.model.get("ids"),[e.id])})}});i.controlConstructors.media_gallery=t,i.modelConstructors.media_gallery=e}(wp.mediaWidgets);
|
170
wp-admin/js/widgets/media-image-widget.js
Normal file
170
wp-admin/js/widgets/media-image-widget.js
Normal file
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @output wp-admin/js/widgets/media-image-widget.js
|
||||
*/
|
||||
|
||||
/* eslint consistent-this: [ "error", "control" ] */
|
||||
(function( component, $ ) {
|
||||
'use strict';
|
||||
|
||||
var ImageWidgetModel, ImageWidgetControl;
|
||||
|
||||
/**
|
||||
* Image widget model.
|
||||
*
|
||||
* See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @class wp.mediaWidgets.modelConstructors.media_image
|
||||
* @augments wp.mediaWidgets.MediaWidgetModel
|
||||
*/
|
||||
ImageWidgetModel = component.MediaWidgetModel.extend({});
|
||||
|
||||
/**
|
||||
* Image widget control.
|
||||
*
|
||||
* See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @class wp.mediaWidgets.controlConstructors.media_audio
|
||||
* @augments wp.mediaWidgets.MediaWidgetControl
|
||||
*/
|
||||
ImageWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_image.prototype */{
|
||||
|
||||
/**
|
||||
* View events.
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
events: _.extend( {}, component.MediaWidgetControl.prototype.events, {
|
||||
'click .media-widget-preview.populated': 'editMedia'
|
||||
} ),
|
||||
|
||||
/**
|
||||
* Render preview.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
renderPreview: function renderPreview() {
|
||||
var control = this, previewContainer, previewTemplate, fieldsContainer, fieldsTemplate, linkInput;
|
||||
if ( ! control.model.get( 'attachment_id' ) && ! control.model.get( 'url' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
previewContainer = control.$el.find( '.media-widget-preview' );
|
||||
previewTemplate = wp.template( 'wp-media-widget-image-preview' );
|
||||
previewContainer.html( previewTemplate( control.previewTemplateProps.toJSON() ) );
|
||||
previewContainer.addClass( 'populated' );
|
||||
|
||||
linkInput = control.$el.find( '.link' );
|
||||
if ( ! linkInput.is( document.activeElement ) ) {
|
||||
fieldsContainer = control.$el.find( '.media-widget-fields' );
|
||||
fieldsTemplate = wp.template( 'wp-media-widget-image-fields' );
|
||||
fieldsContainer.html( fieldsTemplate( control.previewTemplateProps.toJSON() ) );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the media image-edit frame to modify the selected item.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
editMedia: function editMedia() {
|
||||
var control = this, mediaFrame, updateCallback, defaultSync, metadata;
|
||||
|
||||
metadata = control.mapModelToMediaFrameProps( control.model.toJSON() );
|
||||
|
||||
// Needed or else none will not be selected if linkUrl is not also empty.
|
||||
if ( 'none' === metadata.link ) {
|
||||
metadata.linkUrl = '';
|
||||
}
|
||||
|
||||
// Set up the media frame.
|
||||
mediaFrame = wp.media({
|
||||
frame: 'image',
|
||||
state: 'image-details',
|
||||
metadata: metadata
|
||||
});
|
||||
mediaFrame.$el.addClass( 'media-widget' );
|
||||
|
||||
updateCallback = function() {
|
||||
var mediaProps, linkType;
|
||||
|
||||
// Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview.
|
||||
mediaProps = mediaFrame.state().attributes.image.toJSON();
|
||||
linkType = mediaProps.link;
|
||||
mediaProps.link = mediaProps.linkUrl;
|
||||
control.selectedAttachment.set( mediaProps );
|
||||
control.displaySettings.set( 'link', linkType );
|
||||
|
||||
control.model.set( _.extend(
|
||||
control.mapMediaToModelProps( mediaProps ),
|
||||
{ error: false }
|
||||
) );
|
||||
};
|
||||
|
||||
mediaFrame.state( 'image-details' ).on( 'update', updateCallback );
|
||||
mediaFrame.state( 'replace-image' ).on( 'replace', updateCallback );
|
||||
|
||||
// Disable syncing of attachment changes back to server. See <https://core.trac.wordpress.org/ticket/40403>.
|
||||
defaultSync = wp.media.model.Attachment.prototype.sync;
|
||||
wp.media.model.Attachment.prototype.sync = function rejectedSync() {
|
||||
return $.Deferred().rejectWith( this ).promise();
|
||||
};
|
||||
mediaFrame.on( 'close', function onClose() {
|
||||
mediaFrame.detach();
|
||||
wp.media.model.Attachment.prototype.sync = defaultSync;
|
||||
});
|
||||
|
||||
mediaFrame.open();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment).
|
||||
*
|
||||
* @return {Object} Reset/override props.
|
||||
*/
|
||||
getEmbedResetProps: function getEmbedResetProps() {
|
||||
return _.extend(
|
||||
component.MediaWidgetControl.prototype.getEmbedResetProps.call( this ),
|
||||
{
|
||||
size: 'full',
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the instance props from the media selection frame.
|
||||
*
|
||||
* Prevent the image_title attribute from being initially set when adding an image from the media library.
|
||||
*
|
||||
* @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame.
|
||||
* @return {Object} Props.
|
||||
*/
|
||||
getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) {
|
||||
var control = this;
|
||||
return _.omit(
|
||||
component.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call( control, mediaFrame ),
|
||||
'image_title'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Map model props to preview template props.
|
||||
*
|
||||
* @return {Object} Preview template props.
|
||||
*/
|
||||
mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() {
|
||||
var control = this, previewTemplateProps, url;
|
||||
url = control.model.get( 'url' );
|
||||
previewTemplateProps = component.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call( control );
|
||||
previewTemplateProps.currentFilename = url ? url.replace( /\?.*$/, '' ).replace( /^.+\//, '' ) : '';
|
||||
previewTemplateProps.link_url = control.model.get( 'link_url' );
|
||||
return previewTemplateProps;
|
||||
}
|
||||
});
|
||||
|
||||
// Exports.
|
||||
component.controlConstructors.media_image = ImageWidgetControl;
|
||||
component.modelConstructors.media_image = ImageWidgetModel;
|
||||
|
||||
})( wp.mediaWidgets, jQuery );
|
2
wp-admin/js/widgets/media-image-widget.min.js
vendored
Normal file
2
wp-admin/js/widgets/media-image-widget.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
!function(a,o){"use strict";var e=a.MediaWidgetModel.extend({}),t=a.MediaWidgetControl.extend({events:_.extend({},a.MediaWidgetControl.prototype.events,{"click .media-widget-preview.populated":"editMedia"}),renderPreview:function(){var e,t,i=this;(i.model.get("attachment_id")||i.model.get("url"))&&(t=i.$el.find(".media-widget-preview"),e=wp.template("wp-media-widget-image-preview"),t.html(e(i.previewTemplateProps.toJSON())),t.addClass("populated"),i.$el.find(".link").is(document.activeElement)||(e=i.$el.find(".media-widget-fields"),t=wp.template("wp-media-widget-image-fields"),e.html(t(i.previewTemplateProps.toJSON()))))},editMedia:function(){var i,e,a=this,t=a.mapModelToMediaFrameProps(a.model.toJSON());"none"===t.link&&(t.linkUrl=""),(i=wp.media({frame:"image",state:"image-details",metadata:t})).$el.addClass("media-widget"),t=function(){var e=i.state().attributes.image.toJSON(),t=e.link;e.link=e.linkUrl,a.selectedAttachment.set(e),a.displaySettings.set("link",t),a.model.set(_.extend(a.mapMediaToModelProps(e),{error:!1}))},i.state("image-details").on("update",t),i.state("replace-image").on("replace",t),e=wp.media.model.Attachment.prototype.sync,wp.media.model.Attachment.prototype.sync=function(){return o.Deferred().rejectWith(this).promise()},i.on("close",function(){i.detach(),wp.media.model.Attachment.prototype.sync=e}),i.open()},getEmbedResetProps:function(){return _.extend(a.MediaWidgetControl.prototype.getEmbedResetProps.call(this),{size:"full",width:0,height:0})},getModelPropsFromMediaFrame:function(e){return _.omit(a.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call(this,e),"image_title")},mapModelToPreviewTemplateProps:function(){var e=this,t=e.model.get("url"),i=a.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call(e);return i.currentFilename=t?t.replace(/\?.*$/,"").replace(/^.+\//,""):"",i.link_url=e.model.get("link_url"),i}});a.controlConstructors.media_image=t,a.modelConstructors.media_image=e}(wp.mediaWidgets,jQuery);
|
256
wp-admin/js/widgets/media-video-widget.js
Normal file
256
wp-admin/js/widgets/media-video-widget.js
Normal file
@ -0,0 +1,256 @@
|
||||
/**
|
||||
* @output wp-admin/js/widgets/media-video-widget.js
|
||||
*/
|
||||
|
||||
/* eslint consistent-this: [ "error", "control" ] */
|
||||
(function( component ) {
|
||||
'use strict';
|
||||
|
||||
var VideoWidgetModel, VideoWidgetControl, VideoDetailsMediaFrame;
|
||||
|
||||
/**
|
||||
* Custom video details frame that removes the replace-video state.
|
||||
*
|
||||
* @class wp.mediaWidgets.controlConstructors~VideoDetailsMediaFrame
|
||||
* @augments wp.media.view.MediaFrame.VideoDetails
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
VideoDetailsMediaFrame = wp.media.view.MediaFrame.VideoDetails.extend(/** @lends wp.mediaWidgets.controlConstructors~VideoDetailsMediaFrame.prototype */{
|
||||
|
||||
/**
|
||||
* Create the default states.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
createStates: function createStates() {
|
||||
this.states.add([
|
||||
new wp.media.controller.VideoDetails({
|
||||
media: this.media
|
||||
}),
|
||||
|
||||
new wp.media.controller.MediaLibrary({
|
||||
type: 'video',
|
||||
id: 'add-video-source',
|
||||
title: wp.media.view.l10n.videoAddSourceTitle,
|
||||
toolbar: 'add-video-source',
|
||||
media: this.media,
|
||||
menu: false
|
||||
}),
|
||||
|
||||
new wp.media.controller.MediaLibrary({
|
||||
type: 'text',
|
||||
id: 'add-track',
|
||||
title: wp.media.view.l10n.videoAddTrackTitle,
|
||||
toolbar: 'add-track',
|
||||
media: this.media,
|
||||
menu: 'video-details'
|
||||
})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Video widget model.
|
||||
*
|
||||
* See WP_Widget_Video::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @class wp.mediaWidgets.modelConstructors.media_video
|
||||
* @augments wp.mediaWidgets.MediaWidgetModel
|
||||
*/
|
||||
VideoWidgetModel = component.MediaWidgetModel.extend({});
|
||||
|
||||
/**
|
||||
* Video widget control.
|
||||
*
|
||||
* See WP_Widget_Video::enqueue_admin_scripts() for amending prototype from PHP exports.
|
||||
*
|
||||
* @class wp.mediaWidgets.controlConstructors.media_video
|
||||
* @augments wp.mediaWidgets.MediaWidgetControl
|
||||
*/
|
||||
VideoWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_video.prototype */{
|
||||
|
||||
/**
|
||||
* Show display settings.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
showDisplaySettings: false,
|
||||
|
||||
/**
|
||||
* Cache of oembed responses.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
oembedResponses: {},
|
||||
|
||||
/**
|
||||
* Map model props to media frame props.
|
||||
*
|
||||
* @param {Object} modelProps - Model props.
|
||||
* @return {Object} Media frame props.
|
||||
*/
|
||||
mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) {
|
||||
var control = this, mediaFrameProps;
|
||||
mediaFrameProps = component.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call( control, modelProps );
|
||||
mediaFrameProps.link = 'embed';
|
||||
return mediaFrameProps;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches embed data for external videos.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
fetchEmbed: function fetchEmbed() {
|
||||
var control = this, url;
|
||||
url = control.model.get( 'url' );
|
||||
|
||||
// If we already have a local cache of the embed response, return.
|
||||
if ( control.oembedResponses[ url ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is an in-flight embed request, abort it.
|
||||
if ( control.fetchEmbedDfd && 'pending' === control.fetchEmbedDfd.state() ) {
|
||||
control.fetchEmbedDfd.abort();
|
||||
}
|
||||
|
||||
control.fetchEmbedDfd = wp.apiRequest({
|
||||
url: wp.media.view.settings.oEmbedProxyUrl,
|
||||
data: {
|
||||
url: control.model.get( 'url' ),
|
||||
maxwidth: control.model.get( 'width' ),
|
||||
maxheight: control.model.get( 'height' ),
|
||||
discover: false
|
||||
},
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
context: control
|
||||
});
|
||||
|
||||
control.fetchEmbedDfd.done( function( response ) {
|
||||
control.oembedResponses[ url ] = response;
|
||||
control.renderPreview();
|
||||
});
|
||||
|
||||
control.fetchEmbedDfd.fail( function() {
|
||||
control.oembedResponses[ url ] = null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether a url is a supported external host.
|
||||
*
|
||||
* @deprecated since 4.9.
|
||||
*
|
||||
* @return {boolean} Whether url is a supported video host.
|
||||
*/
|
||||
isHostedVideo: function isHostedVideo() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render preview.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
renderPreview: function renderPreview() {
|
||||
var control = this, previewContainer, previewTemplate, attachmentId, attachmentUrl, poster, html = '', isOEmbed = false, mime, error, urlParser, matches;
|
||||
attachmentId = control.model.get( 'attachment_id' );
|
||||
attachmentUrl = control.model.get( 'url' );
|
||||
error = control.model.get( 'error' );
|
||||
|
||||
if ( ! attachmentId && ! attachmentUrl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the selected attachment mime is supported.
|
||||
mime = control.selectedAttachment.get( 'mime' );
|
||||
if ( mime && attachmentId ) {
|
||||
if ( ! _.contains( _.values( wp.media.view.settings.embedMimes ), mime ) ) {
|
||||
error = 'unsupported_file_type';
|
||||
}
|
||||
} else if ( ! attachmentId ) {
|
||||
urlParser = document.createElement( 'a' );
|
||||
urlParser.href = attachmentUrl;
|
||||
matches = urlParser.pathname.toLowerCase().match( /\.(\w+)$/ );
|
||||
if ( matches ) {
|
||||
if ( ! _.contains( _.keys( wp.media.view.settings.embedMimes ), matches[1] ) ) {
|
||||
error = 'unsupported_file_type';
|
||||
}
|
||||
} else {
|
||||
isOEmbed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isOEmbed ) {
|
||||
control.fetchEmbed();
|
||||
if ( control.oembedResponses[ attachmentUrl ] ) {
|
||||
poster = control.oembedResponses[ attachmentUrl ].thumbnail_url;
|
||||
html = control.oembedResponses[ attachmentUrl ].html.replace( /\swidth="\d+"/, ' width="100%"' ).replace( /\sheight="\d+"/, '' );
|
||||
}
|
||||
}
|
||||
|
||||
previewContainer = control.$el.find( '.media-widget-preview' );
|
||||
previewTemplate = wp.template( 'wp-media-widget-video-preview' );
|
||||
|
||||
previewContainer.html( previewTemplate({
|
||||
model: {
|
||||
attachment_id: attachmentId,
|
||||
html: html,
|
||||
src: attachmentUrl,
|
||||
poster: poster
|
||||
},
|
||||
is_oembed: isOEmbed,
|
||||
error: error
|
||||
}));
|
||||
wp.mediaelement.initialize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the media image-edit frame to modify the selected item.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
editMedia: function editMedia() {
|
||||
var control = this, mediaFrame, metadata, updateCallback;
|
||||
|
||||
metadata = control.mapModelToMediaFrameProps( control.model.toJSON() );
|
||||
|
||||
// Set up the media frame.
|
||||
mediaFrame = new VideoDetailsMediaFrame({
|
||||
frame: 'video',
|
||||
state: 'video-details',
|
||||
metadata: metadata
|
||||
});
|
||||
wp.media.frame = mediaFrame;
|
||||
mediaFrame.$el.addClass( 'media-widget' );
|
||||
|
||||
updateCallback = function( mediaFrameProps ) {
|
||||
|
||||
// Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview.
|
||||
control.selectedAttachment.set( mediaFrameProps );
|
||||
|
||||
control.model.set( _.extend(
|
||||
_.omit( control.model.defaults(), 'title' ),
|
||||
control.mapMediaToModelProps( mediaFrameProps ),
|
||||
{ error: false }
|
||||
) );
|
||||
};
|
||||
|
||||
mediaFrame.state( 'video-details' ).on( 'update', updateCallback );
|
||||
mediaFrame.state( 'replace-video' ).on( 'replace', updateCallback );
|
||||
mediaFrame.on( 'close', function() {
|
||||
mediaFrame.detach();
|
||||
});
|
||||
|
||||
mediaFrame.open();
|
||||
}
|
||||
});
|
||||
|
||||
// Exports.
|
||||
component.controlConstructors.media_video = VideoWidgetControl;
|
||||
component.modelConstructors.media_video = VideoWidgetModel;
|
||||
|
||||
})( wp.mediaWidgets );
|
2
wp-admin/js/widgets/media-video-widget.min.js
vendored
Normal file
2
wp-admin/js/widgets/media-video-widget.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
!function(t){"use strict";var i=wp.media.view.MediaFrame.VideoDetails.extend({createStates:function(){this.states.add([new wp.media.controller.VideoDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"video",id:"add-video-source",title:wp.media.view.l10n.videoAddSourceTitle,toolbar:"add-video-source",media:this.media,menu:!1}),new wp.media.controller.MediaLibrary({type:"text",id:"add-track",title:wp.media.view.l10n.videoAddTrackTitle,toolbar:"add-track",media:this.media,menu:"video-details"})])}}),e=t.MediaWidgetModel.extend({}),d=t.MediaWidgetControl.extend({showDisplaySettings:!1,oembedResponses:{},mapModelToMediaFrameProps:function(e){e=t.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(this,e);return e.link="embed",e},fetchEmbed:function(){var t=this,d=t.model.get("url");t.oembedResponses[d]||(t.fetchEmbedDfd&&"pending"===t.fetchEmbedDfd.state()&&t.fetchEmbedDfd.abort(),t.fetchEmbedDfd=wp.apiRequest({url:wp.media.view.settings.oEmbedProxyUrl,data:{url:t.model.get("url"),maxwidth:t.model.get("width"),maxheight:t.model.get("height"),discover:!1},type:"GET",dataType:"json",context:t}),t.fetchEmbedDfd.done(function(e){t.oembedResponses[d]=e,t.renderPreview()}),t.fetchEmbedDfd.fail(function(){t.oembedResponses[d]=null}))},isHostedVideo:function(){return!0},renderPreview:function(){var e,t,d=this,i="",o=!1,a=d.model.get("attachment_id"),s=d.model.get("url"),m=d.model.get("error");(a||s)&&((t=d.selectedAttachment.get("mime"))&&a?_.contains(_.values(wp.media.view.settings.embedMimes),t)||(m="unsupported_file_type"):a||((t=document.createElement("a")).href=s,(t=t.pathname.toLowerCase().match(/\.(\w+)$/))?_.contains(_.keys(wp.media.view.settings.embedMimes),t[1])||(m="unsupported_file_type"):o=!0),o&&(d.fetchEmbed(),d.oembedResponses[s])&&(e=d.oembedResponses[s].thumbnail_url,i=d.oembedResponses[s].html.replace(/\swidth="\d+"/,' width="100%"').replace(/\sheight="\d+"/,"")),t=d.$el.find(".media-widget-preview"),d=wp.template("wp-media-widget-video-preview"),t.html(d({model:{attachment_id:a,html:i,src:s,poster:e},is_oembed:o,error:m})),wp.mediaelement.initialize())},editMedia:function(){var t=this,e=t.mapModelToMediaFrameProps(t.model.toJSON()),d=new i({frame:"video",state:"video-details",metadata:e});(wp.media.frame=d).$el.addClass("media-widget"),e=function(e){t.selectedAttachment.set(e),t.model.set(_.extend(_.omit(t.model.defaults(),"title"),t.mapMediaToModelProps(e),{error:!1}))},d.state("video-details").on("update",e),d.state("replace-video").on("replace",e),d.on("close",function(){d.detach()}),d.open()}});t.controlConstructors.media_video=d,t.modelConstructors.media_video=e}(wp.mediaWidgets);
|
1336
wp-admin/js/widgets/media-widgets.js
Normal file
1336
wp-admin/js/widgets/media-widgets.js
Normal file
File diff suppressed because it is too large
Load Diff
2
wp-admin/js/widgets/media-widgets.min.js
vendored
Normal file
2
wp-admin/js/widgets/media-widgets.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
550
wp-admin/js/widgets/text-widgets.js
Normal file
550
wp-admin/js/widgets/text-widgets.js
Normal file
@ -0,0 +1,550 @@
|
||||
/**
|
||||
* @output wp-admin/js/widgets/text-widgets.js
|
||||
*/
|
||||
|
||||
/* global tinymce, switchEditors */
|
||||
/* eslint consistent-this: [ "error", "control" ] */
|
||||
|
||||
/**
|
||||
* @namespace wp.textWidgets
|
||||
*/
|
||||
wp.textWidgets = ( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
var component = {
|
||||
dismissedPointers: [],
|
||||
idBases: [ 'text' ]
|
||||
};
|
||||
|
||||
component.TextWidgetControl = Backbone.View.extend(/** @lends wp.textWidgets.TextWidgetControl.prototype */{
|
||||
|
||||
/**
|
||||
* View events.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
events: {},
|
||||
|
||||
/**
|
||||
* Text widget control.
|
||||
*
|
||||
* @constructs wp.textWidgets.TextWidgetControl
|
||||
* @augments Backbone.View
|
||||
* @abstract
|
||||
*
|
||||
* @param {Object} options - Options.
|
||||
* @param {jQuery} options.el - Control field container element.
|
||||
* @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
initialize: function initialize( options ) {
|
||||
var control = this;
|
||||
|
||||
if ( ! options.el ) {
|
||||
throw new Error( 'Missing options.el' );
|
||||
}
|
||||
if ( ! options.syncContainer ) {
|
||||
throw new Error( 'Missing options.syncContainer' );
|
||||
}
|
||||
|
||||
Backbone.View.prototype.initialize.call( control, options );
|
||||
control.syncContainer = options.syncContainer;
|
||||
|
||||
control.$el.addClass( 'text-widget-fields' );
|
||||
control.$el.html( wp.template( 'widget-text-control-fields' ) );
|
||||
|
||||
control.customHtmlWidgetPointer = control.$el.find( '.wp-pointer.custom-html-widget-pointer' );
|
||||
if ( control.customHtmlWidgetPointer.length ) {
|
||||
control.customHtmlWidgetPointer.find( '.close' ).on( 'click', function( event ) {
|
||||
event.preventDefault();
|
||||
control.customHtmlWidgetPointer.hide();
|
||||
$( '#' + control.fields.text.attr( 'id' ) + '-html' ).trigger( 'focus' );
|
||||
control.dismissPointers( [ 'text_widget_custom_html' ] );
|
||||
});
|
||||
control.customHtmlWidgetPointer.find( '.add-widget' ).on( 'click', function( event ) {
|
||||
event.preventDefault();
|
||||
control.customHtmlWidgetPointer.hide();
|
||||
control.openAvailableWidgetsPanel();
|
||||
});
|
||||
}
|
||||
|
||||
control.pasteHtmlPointer = control.$el.find( '.wp-pointer.paste-html-pointer' );
|
||||
if ( control.pasteHtmlPointer.length ) {
|
||||
control.pasteHtmlPointer.find( '.close' ).on( 'click', function( event ) {
|
||||
event.preventDefault();
|
||||
control.pasteHtmlPointer.hide();
|
||||
control.editor.focus();
|
||||
control.dismissPointers( [ 'text_widget_custom_html', 'text_widget_paste_html' ] );
|
||||
});
|
||||
}
|
||||
|
||||
control.fields = {
|
||||
title: control.$el.find( '.title' ),
|
||||
text: control.$el.find( '.text' )
|
||||
};
|
||||
|
||||
// Sync input fields to hidden sync fields which actually get sent to the server.
|
||||
_.each( control.fields, function( fieldInput, fieldName ) {
|
||||
fieldInput.on( 'input change', function updateSyncField() {
|
||||
var syncInput = control.syncContainer.find( '.sync-input.' + fieldName );
|
||||
if ( syncInput.val() !== fieldInput.val() ) {
|
||||
syncInput.val( fieldInput.val() );
|
||||
syncInput.trigger( 'change' );
|
||||
}
|
||||
});
|
||||
|
||||
// Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event.
|
||||
fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss pointers for Custom HTML widget.
|
||||
*
|
||||
* @since 4.8.1
|
||||
*
|
||||
* @param {Array} pointers Pointer IDs to dismiss.
|
||||
* @return {void}
|
||||
*/
|
||||
dismissPointers: function dismissPointers( pointers ) {
|
||||
_.each( pointers, function( pointer ) {
|
||||
wp.ajax.post( 'dismiss-wp-pointer', {
|
||||
pointer: pointer
|
||||
});
|
||||
component.dismissedPointers.push( pointer );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Open available widgets panel.
|
||||
*
|
||||
* @since 4.8.1
|
||||
* @return {void}
|
||||
*/
|
||||
openAvailableWidgetsPanel: function openAvailableWidgetsPanel() {
|
||||
var sidebarControl;
|
||||
wp.customize.section.each( function( section ) {
|
||||
if ( section.extended( wp.customize.Widgets.SidebarSection ) && section.expanded() ) {
|
||||
sidebarControl = wp.customize.control( 'sidebars_widgets[' + section.params.sidebarId + ']' );
|
||||
}
|
||||
});
|
||||
if ( ! sidebarControl ) {
|
||||
return;
|
||||
}
|
||||
setTimeout( function() { // Timeout to prevent click event from causing panel to immediately collapse.
|
||||
wp.customize.Widgets.availableWidgetsPanel.open( sidebarControl );
|
||||
wp.customize.Widgets.availableWidgetsPanel.$search.val( 'HTML' ).trigger( 'keyup' );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update input fields from the sync fields.
|
||||
*
|
||||
* This function is called at the widget-updated and widget-synced events.
|
||||
* A field will only be updated if it is not currently focused, to avoid
|
||||
* overwriting content that the user is entering.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
updateFields: function updateFields() {
|
||||
var control = this, syncInput;
|
||||
|
||||
if ( ! control.fields.title.is( document.activeElement ) ) {
|
||||
syncInput = control.syncContainer.find( '.sync-input.title' );
|
||||
control.fields.title.val( syncInput.val() );
|
||||
}
|
||||
|
||||
syncInput = control.syncContainer.find( '.sync-input.text' );
|
||||
if ( control.fields.text.is( ':visible' ) ) {
|
||||
if ( ! control.fields.text.is( document.activeElement ) ) {
|
||||
control.fields.text.val( syncInput.val() );
|
||||
}
|
||||
} else if ( control.editor && ! control.editorFocused && syncInput.val() !== control.fields.text.val() ) {
|
||||
control.editor.setContent( wp.oldEditor.autop( syncInput.val() ) );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize editor.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
initializeEditor: function initializeEditor() {
|
||||
var control = this, changeDebounceDelay = 1000, id, textarea, triggerChangeIfDirty, restoreTextMode = false, needsTextareaChangeTrigger = false, previousValue;
|
||||
textarea = control.fields.text;
|
||||
id = textarea.attr( 'id' );
|
||||
previousValue = textarea.val();
|
||||
|
||||
/**
|
||||
* Trigger change if dirty.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
triggerChangeIfDirty = function() {
|
||||
var updateWidgetBuffer = 300; // See wp.customize.Widgets.WidgetControl._setupUpdateUI() which uses 250ms for updateWidgetDebounced.
|
||||
if ( control.editor.isDirty() ) {
|
||||
|
||||
/*
|
||||
* Account for race condition in customizer where user clicks Save & Publish while
|
||||
* focus was just previously given to the editor. Since updates to the editor
|
||||
* are debounced at 1 second and since widget input changes are only synced to
|
||||
* settings after 250ms, the customizer needs to be put into the processing
|
||||
* state during the time between the change event is triggered and updateWidget
|
||||
* logic starts. Note that the debounced update-widget request should be able
|
||||
* to be removed with the removal of the update-widget request entirely once
|
||||
* widgets are able to mutate their own instance props directly in JS without
|
||||
* having to make server round-trips to call the respective WP_Widget::update()
|
||||
* callbacks. See <https://core.trac.wordpress.org/ticket/33507>.
|
||||
*/
|
||||
if ( wp.customize && wp.customize.state ) {
|
||||
wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() + 1 );
|
||||
_.delay( function() {
|
||||
wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() - 1 );
|
||||
}, updateWidgetBuffer );
|
||||
}
|
||||
|
||||
if ( ! control.editor.isHidden() ) {
|
||||
control.editor.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger change on textarea when it has changed so the widget can enter a dirty state.
|
||||
if ( needsTextareaChangeTrigger && previousValue !== textarea.val() ) {
|
||||
textarea.trigger( 'change' );
|
||||
needsTextareaChangeTrigger = false;
|
||||
previousValue = textarea.val();
|
||||
}
|
||||
};
|
||||
|
||||
// Just-in-time force-update the hidden input fields.
|
||||
control.syncContainer.closest( '.widget' ).find( '[name=savewidget]:first' ).on( 'click', function onClickSaveButton() {
|
||||
triggerChangeIfDirty();
|
||||
});
|
||||
|
||||
/**
|
||||
* Build (or re-build) the visual editor.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function buildEditor() {
|
||||
var editor, onInit, showPointerElement;
|
||||
|
||||
// Abort building if the textarea is gone, likely due to the widget having been deleted entirely.
|
||||
if ( ! document.getElementById( id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The user has disabled TinyMCE.
|
||||
if ( typeof window.tinymce === 'undefined' ) {
|
||||
wp.oldEditor.initialize( id, {
|
||||
quicktags: true,
|
||||
mediaButtons: true
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy any existing editor so that it can be re-initialized after a widget-updated event.
|
||||
if ( tinymce.get( id ) ) {
|
||||
restoreTextMode = tinymce.get( id ).isHidden();
|
||||
wp.oldEditor.remove( id );
|
||||
}
|
||||
|
||||
// Add or enable the `wpview` plugin.
|
||||
$( document ).one( 'wp-before-tinymce-init.text-widget-init', function( event, init ) {
|
||||
// If somebody has removed all plugins, they must have a good reason.
|
||||
// Keep it that way.
|
||||
if ( ! init.plugins ) {
|
||||
return;
|
||||
} else if ( ! /\bwpview\b/.test( init.plugins ) ) {
|
||||
init.plugins += ',wpview';
|
||||
}
|
||||
} );
|
||||
|
||||
wp.oldEditor.initialize( id, {
|
||||
tinymce: {
|
||||
wpautop: true
|
||||
},
|
||||
quicktags: true,
|
||||
mediaButtons: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Show a pointer, focus on dismiss, and speak the contents for a11y.
|
||||
*
|
||||
* @param {jQuery} pointerElement Pointer element.
|
||||
* @return {void}
|
||||
*/
|
||||
showPointerElement = function( pointerElement ) {
|
||||
pointerElement.show();
|
||||
pointerElement.find( '.close' ).trigger( 'focus' );
|
||||
wp.a11y.speak( pointerElement.find( 'h3, p' ).map( function() {
|
||||
return $( this ).text();
|
||||
} ).get().join( '\n\n' ) );
|
||||
};
|
||||
|
||||
editor = window.tinymce.get( id );
|
||||
if ( ! editor ) {
|
||||
throw new Error( 'Failed to initialize editor' );
|
||||
}
|
||||
onInit = function() {
|
||||
|
||||
// When a widget is moved in the DOM the dynamically-created TinyMCE iframe will be destroyed and has to be re-built.
|
||||
$( editor.getWin() ).on( 'pagehide', function() {
|
||||
_.defer( buildEditor );
|
||||
});
|
||||
|
||||
// If a prior mce instance was replaced, and it was in text mode, toggle to text mode.
|
||||
if ( restoreTextMode ) {
|
||||
switchEditors.go( id, 'html' );
|
||||
}
|
||||
|
||||
// Show the pointer.
|
||||
$( '#' + id + '-html' ).on( 'click', function() {
|
||||
control.pasteHtmlPointer.hide(); // Hide the HTML pasting pointer.
|
||||
|
||||
if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_custom_html' ) ) {
|
||||
return;
|
||||
}
|
||||
showPointerElement( control.customHtmlWidgetPointer );
|
||||
});
|
||||
|
||||
// Hide the pointer when switching tabs.
|
||||
$( '#' + id + '-tmce' ).on( 'click', function() {
|
||||
control.customHtmlWidgetPointer.hide();
|
||||
});
|
||||
|
||||
// Show pointer when pasting HTML.
|
||||
editor.on( 'pastepreprocess', function( event ) {
|
||||
var content = event.content;
|
||||
if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_paste_html' ) || ! content || ! /<\w+.*?>/.test( content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the pointer after a slight delay so the user sees what they pasted.
|
||||
_.delay( function() {
|
||||
showPointerElement( control.pasteHtmlPointer );
|
||||
}, 250 );
|
||||
});
|
||||
};
|
||||
|
||||
if ( editor.initialized ) {
|
||||
onInit();
|
||||
} else {
|
||||
editor.on( 'init', onInit );
|
||||
}
|
||||
|
||||
control.editorFocused = false;
|
||||
|
||||
editor.on( 'focus', function onEditorFocus() {
|
||||
control.editorFocused = true;
|
||||
});
|
||||
editor.on( 'paste', function onEditorPaste() {
|
||||
editor.setDirty( true ); // Because pasting doesn't currently set the dirty state.
|
||||
triggerChangeIfDirty();
|
||||
});
|
||||
editor.on( 'NodeChange', function onNodeChange() {
|
||||
needsTextareaChangeTrigger = true;
|
||||
});
|
||||
editor.on( 'NodeChange', _.debounce( triggerChangeIfDirty, changeDebounceDelay ) );
|
||||
editor.on( 'blur hide', function onEditorBlur() {
|
||||
control.editorFocused = false;
|
||||
triggerChangeIfDirty();
|
||||
});
|
||||
|
||||
control.editor = editor;
|
||||
}
|
||||
|
||||
buildEditor();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Mapping of widget ID to instances of TextWidgetControl subclasses.
|
||||
*
|
||||
* @memberOf wp.textWidgets
|
||||
*
|
||||
* @type {Object.<string, wp.textWidgets.TextWidgetControl>}
|
||||
*/
|
||||
component.widgetControls = {};
|
||||
|
||||
/**
|
||||
* Handle widget being added or initialized for the first time at the widget-added event.
|
||||
*
|
||||
* @memberOf wp.textWidgets
|
||||
*
|
||||
* @param {jQuery.Event} event - Event.
|
||||
* @param {jQuery} widgetContainer - Widget container element.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
|
||||
var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer;
|
||||
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();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent initializing already-added widgets.
|
||||
widgetId = widgetForm.find( '.widget-id' ).val();
|
||||
if ( component.widgetControls[ widgetId ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bypass using TinyMCE when widget is in legacy mode.
|
||||
if ( ! widgetForm.find( '.visual' ).val() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a container element for the widget control fields.
|
||||
* 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 );
|
||||
|
||||
widgetControl = new component.TextWidgetControl({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
component.widgetControls[ widgetId ] = widgetControl;
|
||||
|
||||
/*
|
||||
* 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 textarea is visible and an iframe can be embedded
|
||||
* with TinyMCE being able to set contenteditable on it.
|
||||
*/
|
||||
renderWhenAnimationDone = function() {
|
||||
if ( ! widgetContainer.hasClass( 'open' ) ) {
|
||||
setTimeout( renderWhenAnimationDone, animatedCheckDelay );
|
||||
} else {
|
||||
widgetControl.initializeEditor();
|
||||
}
|
||||
};
|
||||
renderWhenAnimationDone();
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup widget in accessibility mode.
|
||||
*
|
||||
* @memberOf wp.textWidgets
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
component.setupAccessibleMode = function setupAccessibleMode() {
|
||||
var widgetForm, idBase, widgetControl, fieldContainer, syncContainer;
|
||||
widgetForm = $( '.editwidget > form' );
|
||||
if ( 0 === widgetForm.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
idBase = widgetForm.find( '.id_base' ).val();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bypass using TinyMCE when widget is in legacy mode.
|
||||
if ( ! widgetForm.find( '.visual' ).val() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetForm.find( '> .widget-inside' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
widgetControl = new component.TextWidgetControl({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
widgetControl.initializeEditor();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.textWidgets
|
||||
*
|
||||
* @param {jQuery.Event} event - Event.
|
||||
* @param {jQuery} widgetContainer - Widget container element.
|
||||
* @return {void}
|
||||
*/
|
||||
component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) {
|
||||
var widgetForm, widgetId, widgetControl, idBase;
|
||||
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
|
||||
|
||||
idBase = widgetForm.find( '> .id_base' ).val();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetId = widgetForm.find( '> .widget-id' ).val();
|
||||
widgetControl = component.widgetControls[ widgetId ];
|
||||
if ( ! widgetControl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetControl.updateFields();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.textWidgets.init().
|
||||
*
|
||||
* @memberOf wp.textWidgets
|
||||
*
|
||||
* @return {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.
|
||||
component.setupAccessibleMode();
|
||||
});
|
||||
};
|
||||
|
||||
return component;
|
||||
})( jQuery );
|
2
wp-admin/js/widgets/text-widgets.min.js
vendored
Normal file
2
wp-admin/js/widgets/text-widgets.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user