src = src.replace( /[?#].*/, '' );
if ( srcset.indexOf( src ) === -1 ) {
$imageNode.attr( 'srcset', null ).attr( 'sizes', null );
wp.media.events.trigger( 'editor:image-update', {
function editImage( img ) {
var frame, callback, metadata, imageNode;
if ( typeof wp === 'undefined' || ! wp.media ) {
editor.execCommand( 'mceImage' );
metadata = extractImageData( img );
// Mark the image node so we can select it later.
editor.$( img ).attr( 'data-wp-editing', 1 );
// Manipulate the metadata by reference that is fed into the PostImage model used in the media modal.
wp.media.events.trigger( 'editor:image-edit', {
wp.media.events.trigger( 'editor:frame-create', { frame: frame } );
callback = function( imageData ) {
editor.undoManager.transact( function() {
updateImage( imageNode, imageData );
frame.state('image-details').on( 'update', callback );
frame.state('replace-image').on( 'replace', callback );
frame.on( 'close', function() {
* To be able to update the image node, we need to find it here,
* and use it in the callback.
imageNode = editor.$( 'img[data-wp-editing]' )
imageNode.removeAttr( 'data-wp-editing' );
function removeImage( node ) {
var wrap = editor.dom.getParent( node, 'div.mceTemp' );
if ( ! wrap && node.nodeName === 'IMG' ) {
wrap = editor.dom.getParent( node, 'a' );
if ( wrap.nextSibling ) {
editor.selection.select( wrap.nextSibling );
} else if ( wrap.previousSibling ) {
editor.selection.select( wrap.previousSibling );
editor.selection.select( wrap.parentNode );
editor.selection.collapse( true );
editor.dom.remove( wrap );
editor.dom.remove( node );
editor.undoManager.add();
editor.on( 'init', function() {
captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions';
dom.addClass( editor.getBody(), captionClass );
// Prevent IE11 from making dl.wp-caption resizable.
if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) {
// The 'mscontrolselect' event is supported only in IE11+.
dom.bind( editor.getBody(), 'mscontrolselect', function( event ) {
if ( event.target.nodeName === 'IMG' && dom.getParent( event.target, '.wp-caption' ) ) {
// Hide the thick border with resize handles around dl.wp-caption.
editor.getBody().focus(); // :(
} else if ( event.target.nodeName === 'DL' && dom.hasClass( event.target, 'wp-caption' ) ) {
// Trigger the thick border with resize handles...
// This will make the caption text editable.
editor.on( 'ObjectResized', function( event ) {
if ( node.nodeName === 'IMG' ) {
editor.undoManager.transact( function() {
node.className = node.className.replace( /\bsize-[^ ]+/, '' );
if ( parent = dom.getParent( node, '.wp-caption' ) ) {
width = event.width || dom.getAttrib( node, 'width' );
width = parseInt( width, 10 );
if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
dom.setStyle( parent, 'width', width + 'px' );
editor.on( 'pastePostProcess', function( event ) {
// Pasting in a caption node.
if ( editor.dom.getParent( editor.selection.getNode(), 'dd.wp-caption-dd' ) ) {
// Remove "non-block" elements that should not be in captions.
editor.$( 'img, audio, video, object, embed, iframe, script, style', event.node ).remove();
editor.$( '*', event.node ).each( function( i, node ) {
if ( editor.dom.isBlock( node ) ) {
// Insert <br> where the blocks used to be. Makes it look better after pasting in the caption.
if ( tinymce.trim( node.textContent || node.innerText ) ) {
editor.dom.insertAfter( editor.dom.create( 'br' ), node );
editor.dom.remove( node, true );
editor.dom.remove( node );
editor.$( 'br', event.node ).each( function( i, node ) {
if ( ! node.nextSibling || node.nextSibling.nodeName === 'BR' ||
! node.previousSibling || node.previousSibling.nodeName === 'BR' ) {
editor.dom.remove( node );
// Pasted HTML is cleaned up for inserting in the caption.
editor.on( 'BeforeExecCommand', function( event ) {
var node, p, DL, align, replacement, captionParent,
if ( cmd === 'mceInsertContent' || cmd === 'Indent' || cmd === 'Outdent' ) {
node = editor.selection.getNode();
captionParent = dom.getParent( node, 'div.mceTemp' );
if ( cmd === 'mceInsertContent' ) {
* We are in the caption element, and in 'paste' context,
* and the pasted HTML was cleaned up on 'pastePostProcess' above.
* Let it be pasted in the caption.
* The paste is somewhere else in the caption DL element.
* Prevent pasting in there as it will break the caption.
* Make new paragraph under the caption DL and move the caret there.
dom.insertAfter( p, captionParent );
editor.selection.setCursorLocation( p, 0 );
* If the image is selected and the user pastes "over" it,
* replace both the image and the caption elements with the pasted content.
* This matches the behavior when pasting over non-caption images.
if ( node.nodeName === 'IMG' ) {
editor.$( captionParent ).remove();
// Clicking Indent or Outdent while an image with a caption is selected breaks the caption.
event.stopImmediatePropagation();
} else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' || cmd === 'wpAlignNone' ) {
node = editor.selection.getNode();
align = 'align' + cmd.slice( 7 ).toLowerCase();
DL = editor.dom.getParent( node, '.wp-caption' );
if ( node.nodeName !== 'IMG' && ! DL ) {
if ( editor.dom.hasClass( node, align ) ) {
replacement = ' alignnone';
replacement = ' ' + align;
node.className = trim( node.className.replace( / ?align(left|center|right|none)/g, '' ) + replacement );
editor.fire( 'ExecCommand', {
editor.on( 'keydown', function( event ) {
var node, wrap, P, spacer,
selection = editor.selection,
if ( keyCode === VK.ENTER ) {
// When pressing Enter inside a caption move the caret to a new parapraph under it.
node = selection.getNode();
wrap = dom.getParent( node, 'div.mceTemp' );
dom.events.cancel( event ); // Doesn't cancel all :(
// Remove any extra dt and dd cleated on pressing Enter...
tinymce.each( dom.select( 'dt, dd', wrap ), function( element ) {
if ( dom.isEmpty( element ) ) {
spacer = tinymce.Env.ie && tinymce.Env.ie < 11 ? '' : '<br data-mce-bogus="1" />';
P = dom.create( 'p', null, spacer );
if ( node.nodeName === 'DD' ) {
dom.insertAfter( P, wrap );
wrap.parentNode.insertBefore( P, wrap );
selection.setCursorLocation( P, 0 );
} else if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
node = selection.getNode();
if ( node.nodeName === 'DIV' && dom.hasClass( node, 'mceTemp' ) ) {
} else if ( node.nodeName === 'IMG' || node.nodeName === 'DT' || node.nodeName === 'A' ) {
wrap = dom.getParent( node, 'div.mceTemp' );
dom.events.cancel( event );
* After undo/redo FF seems to set the image height very slowly when it is set to 'auto' in the CSS.
* This causes image.getBoundingClientRect() to return wrong values and the resize handles are shown in wrong places.
* Collapse the selection to remove the resize handles.
if ( tinymce.Env.gecko ) {
editor.on( 'undo redo', function() {
if ( editor.selection.getNode().nodeName === 'IMG' ) {
editor.selection.collapse();
editor.wpSetImgCaption = function( content ) {
return parseShortcode( content );
editor.wpGetImgCaption = function( content ) {
return getShortcode( content );
editor.on( 'beforeGetContent', function( event ) {
if ( event.format !== 'raw' ) {
editor.$( 'img[id="__wp-temp-img-id"]' ).removeAttr( 'id' );
editor.on( 'BeforeSetContent', function( event ) {
if ( event.format !== 'raw' ) {
event.content = editor.wpSetImgCaption( event.content );
editor.on( 'PostProcess', function( event ) {
event.content = editor.wpGetImgCaption( event.content );
editor.on( 'dragstart', function() {
var node = editor.selection.getNode();
if ( node.nodeName === 'IMG' ) {
wrap = editor.dom.getParent( node, '.mceTemp' );
if ( ! wrap && node.parentNode.nodeName === 'A' && ! hasTextContent( node.parentNode ) ) {
editor.on( 'drop', function( event ) {
rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint( event.clientX, event.clientY, editor.getDoc() );
// Don't allow anything to be dropped in a captioned image.
if ( rng && dom.getParent( rng.startContainer, '.mceTemp' ) ) {
editor.undoManager.transact( function() {
editor.selection.setRng( rng );
editor.selection.setNode( wrap );
editor.wp = editor.wp || {};
editor.wp.isPlaceholder = isPlaceholder;
_do_shcode: parseShortcode,
_get_shcode: getShortcode