home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / js / tinymce / plugins / wpeditimage / plugin.js next >
Encoding:
Text File  |  2017-08-26  |  24.6 KB  |  878 lines

  1. /* global tinymce */
  2. tinymce.PluginManager.add( 'wpeditimage', function( editor ) {
  3.     var toolbar, serializer, touchOnImage, pasteInCaption,
  4.         each = tinymce.each,
  5.         trim = tinymce.trim,
  6.         iOS = tinymce.Env.iOS;
  7.  
  8.     function isPlaceholder( node ) {
  9.         return !! ( editor.dom.getAttrib( node, 'data-mce-placeholder' ) || editor.dom.getAttrib( node, 'data-mce-object' ) );
  10.     }
  11.  
  12.     editor.addButton( 'wp_img_remove', {
  13.         tooltip: 'Remove',
  14.         icon: 'dashicon dashicons-no',
  15.         onclick: function() {
  16.             removeImage( editor.selection.getNode() );
  17.         }
  18.     } );
  19.  
  20.     editor.addButton( 'wp_img_edit', {
  21.         tooltip: 'Edit ', // trailing space is needed, used for context
  22.         icon: 'dashicon dashicons-edit',
  23.         onclick: function() {
  24.             editImage( editor.selection.getNode() );
  25.         }
  26.     } );
  27.  
  28.     each( {
  29.         alignleft: 'Align left',
  30.         aligncenter: 'Align center',
  31.         alignright: 'Align right',
  32.         alignnone: 'No alignment'
  33.     }, function( tooltip, name ) {
  34.         var direction = name.slice( 5 );
  35.  
  36.         editor.addButton( 'wp_img_' + name, {
  37.             tooltip: tooltip,
  38.             icon: 'dashicon dashicons-align-' + direction,
  39.             cmd: 'alignnone' === name ? 'wpAlignNone' : 'Justify' + direction.slice( 0, 1 ).toUpperCase() + direction.slice( 1 ),
  40.             onPostRender: function() {
  41.                 var self = this;
  42.  
  43.                 editor.on( 'NodeChange', function( event ) {
  44.                     var node;
  45.  
  46.                     // Don't bother.
  47.                     if ( event.element.nodeName !== 'IMG' ) {
  48.                         return;
  49.                     }
  50.  
  51.                     node = editor.dom.getParent( event.element, '.wp-caption' ) || event.element;
  52.  
  53.                     if ( 'alignnone' === name ) {
  54.                         self.active( ! /\balign(left|center|right)\b/.test( node.className ) );
  55.                     } else {
  56.                         self.active( editor.dom.hasClass( node, name ) );
  57.                     }
  58.                 } );
  59.             }
  60.         } );
  61.     } );
  62.  
  63.     editor.once( 'preinit', function() {
  64.         if ( editor.wp && editor.wp._createToolbar ) {
  65.             toolbar = editor.wp._createToolbar( [
  66.                 'wp_img_alignleft',
  67.                 'wp_img_aligncenter',
  68.                 'wp_img_alignright',
  69.                 'wp_img_alignnone',
  70.                 'wp_img_edit',
  71.                 'wp_img_remove'
  72.             ] );
  73.         }
  74.     } );
  75.  
  76.     editor.on( 'wptoolbar', function( event ) {
  77.         if ( event.element.nodeName === 'IMG' && ! isPlaceholder( event.element ) ) {
  78.             event.toolbar = toolbar;
  79.         }
  80.     } );
  81.  
  82.     function isNonEditable( node ) {
  83.         var parent = editor.$( node ).parents( '[contenteditable]' );
  84.         return parent && parent.attr( 'contenteditable' ) === 'false';
  85.     }
  86.  
  87.     // Safari on iOS fails to select images in contentEditoble mode on touch.
  88.     // Select them again.
  89.     if ( iOS ) {
  90.         editor.on( 'init', function() {
  91.             editor.on( 'touchstart', function( event ) {
  92.                 if ( event.target.nodeName === 'IMG' && ! isNonEditable( event.target ) ) {
  93.                     touchOnImage = true;
  94.                 }
  95.             });
  96.  
  97.             editor.dom.bind( editor.getDoc(), 'touchmove', function() {
  98.                 touchOnImage = false;
  99.             });
  100.  
  101.             editor.on( 'touchend', function( event ) {
  102.                 if ( touchOnImage && event.target.nodeName === 'IMG' && ! isNonEditable( event.target ) ) {
  103.                     var node = event.target;
  104.  
  105.                     touchOnImage = false;
  106.  
  107.                     window.setTimeout( function() {
  108.                         editor.selection.select( node );
  109.                         editor.nodeChanged();
  110.                     }, 100 );
  111.                 } else if ( toolbar ) {
  112.                     toolbar.hide();
  113.                 }
  114.             });
  115.         });
  116.     }
  117.  
  118.     function parseShortcode( content ) {
  119.         return content.replace( /(?:<p>)?\[(?:wp_)?caption([^\]]+)\]([\s\S]+?)\[\/(?:wp_)?caption\](?:<\/p>)?/g, function( a, b, c ) {
  120.             var id, align, classes, caption, img, width;
  121.  
  122.             id = b.match( /id=['"]([^'"]*)['"] ?/ );
  123.             if ( id ) {
  124.                 b = b.replace( id[0], '' );
  125.             }
  126.  
  127.             align = b.match( /align=['"]([^'"]*)['"] ?/ );
  128.             if ( align ) {
  129.                 b = b.replace( align[0], '' );
  130.             }
  131.  
  132.             classes = b.match( /class=['"]([^'"]*)['"] ?/ );
  133.             if ( classes ) {
  134.                 b = b.replace( classes[0], '' );
  135.             }
  136.  
  137.             width = b.match( /width=['"]([0-9]*)['"] ?/ );
  138.             if ( width ) {
  139.                 b = b.replace( width[0], '' );
  140.             }
  141.  
  142.             c = trim( c );
  143.             img = c.match( /((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)([\s\S]*)/i );
  144.  
  145.             if ( img && img[2] ) {
  146.                 caption = trim( img[2] );
  147.                 img = trim( img[1] );
  148.             } else {
  149.                 // old captions shortcode style
  150.                 caption = trim( b ).replace( /caption=['"]/, '' ).replace( /['"]$/, '' );
  151.                 img = c;
  152.             }
  153.  
  154.             id = ( id && id[1] ) ? id[1].replace( /[<>&]+/g,  '' ) : '';
  155.             align = ( align && align[1] ) ? align[1] : 'alignnone';
  156.             classes = ( classes && classes[1] ) ? ' ' + classes[1].replace( /[<>&]+/g,  '' ) : '';
  157.  
  158.             if ( ! width && img ) {
  159.                 width = img.match( /width=['"]([0-9]*)['"]/ );
  160.             }
  161.  
  162.             if ( width && width[1] ) {
  163.                 width = width[1];
  164.             }
  165.  
  166.             if ( ! width || ! caption ) {
  167.                 return c;
  168.             }
  169.  
  170.             width = parseInt( width, 10 );
  171.             if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
  172.                 width += 10;
  173.             }
  174.  
  175.             return '<div class="mceTemp"><dl id="' + id + '" class="wp-caption ' + align + classes + '" style="width: ' + width + 'px">' +
  176.                 '<dt class="wp-caption-dt">'+ img +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl></div>';
  177.         });
  178.     }
  179.  
  180.     function getShortcode( content ) {
  181.         return content.replace( /(?:<div [^>]+mceTemp[^>]+>)?\s*(<dl [^>]+wp-caption[^>]+>[\s\S]+?<\/dl>)\s*(?:<\/div>)?/g, function( all, dl ) {
  182.             var out = '';
  183.  
  184.             if ( dl.indexOf('<img ') === -1 || dl.indexOf('</p>') !== -1 ) {
  185.                 // Broken caption. The user managed to drag the image out or type in the wrapper div?
  186.                 // Remove the <dl>, <dd> and <dt> and return the remaining text.
  187.                 return dl.replace( /<d[ldt]( [^>]+)?>/g, '' ).replace( /<\/d[ldt]>/g, '' );
  188.             }
  189.  
  190.             out = dl.replace( /\s*<dl ([^>]+)>\s*<dt [^>]+>([\s\S]+?)<\/dt>\s*<dd [^>]+>([\s\S]*?)<\/dd>\s*<\/dl>\s*/gi, function( a, b, c, caption ) {
  191.                 var id, classes, align, width;
  192.  
  193.                 width = c.match( /width="([0-9]*)"/ );
  194.                 width = ( width && width[1] ) ? width[1] : '';
  195.  
  196.                 classes = b.match( /class="([^"]*)"/ );
  197.                 classes = ( classes && classes[1] ) ? classes[1] : '';
  198.                 align = classes.match( /align[a-z]+/i ) || 'alignnone';
  199.  
  200.                 if ( ! width || ! caption ) {
  201.                     if ( 'alignnone' !== align[0] ) {
  202.                         c = c.replace( /><img/, ' class="' + align[0] + '"><img' );
  203.                     }
  204.                     return c;
  205.                 }
  206.  
  207.                 id = b.match( /id="([^"]*)"/ );
  208.                 id = ( id && id[1] ) ? id[1] : '';
  209.  
  210.                 classes = classes.replace( /wp-caption ?|align[a-z]+ ?/gi, '' );
  211.  
  212.                 if ( classes ) {
  213.                     classes = ' class="' + classes + '"';
  214.                 }
  215.  
  216.                 caption = caption.replace( /\r\n|\r/g, '\n' ).replace( /<[a-zA-Z0-9]+( [^<>]+)?>/g, function( a ) {
  217.                     // no line breaks inside HTML tags
  218.                     return a.replace( /[\r\n\t]+/, ' ' );
  219.                 });
  220.  
  221.                 // convert remaining line breaks to <br>
  222.                 caption = caption.replace( /\s*\n\s*/g, '<br />' );
  223.  
  224.                 return '[caption id="' + id + '" align="' + align + '" width="' + width + '"' + classes + ']' + c + ' ' + caption + '[/caption]';
  225.             });
  226.  
  227.             if ( out.indexOf('[caption') === -1 ) {
  228.                 // the caption html seems broken, try to find the image that may be wrapped in a link
  229.                 // and may be followed by <p> with the caption text.
  230.                 out = dl.replace( /[\s\S]*?((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)(<p>[\s\S]*<\/p>)?[\s\S]*/gi, '<p>$1</p>$2' );
  231.             }
  232.  
  233.             return out;
  234.         });
  235.     }
  236.  
  237.     function extractImageData( imageNode ) {
  238.         var classes, extraClasses, metadata, captionBlock, caption, link, width, height,
  239.             captionClassName = [],
  240.             dom = editor.dom,
  241.             isIntRegExp = /^\d+$/;
  242.  
  243.         // default attributes
  244.         metadata = {
  245.             attachment_id: false,
  246.             size: 'custom',
  247.             caption: '',
  248.             align: 'none',
  249.             extraClasses: '',
  250.             link: false,
  251.             linkUrl: '',
  252.             linkClassName: '',
  253.             linkTargetBlank: false,
  254.             linkRel: '',
  255.             title: ''
  256.         };
  257.  
  258.         metadata.url = dom.getAttrib( imageNode, 'src' );
  259.         metadata.alt = dom.getAttrib( imageNode, 'alt' );
  260.         metadata.title = dom.getAttrib( imageNode, 'title' );
  261.  
  262.         width = dom.getAttrib( imageNode, 'width' );
  263.         height = dom.getAttrib( imageNode, 'height' );
  264.  
  265.         if ( ! isIntRegExp.test( width ) || parseInt( width, 10 ) < 1 ) {
  266.             width = imageNode.naturalWidth || imageNode.width;
  267.         }
  268.  
  269.         if ( ! isIntRegExp.test( height ) || parseInt( height, 10 ) < 1 ) {
  270.             height = imageNode.naturalHeight || imageNode.height;
  271.         }
  272.  
  273.         metadata.customWidth = metadata.width = width;
  274.         metadata.customHeight = metadata.height = height;
  275.  
  276.         classes = tinymce.explode( imageNode.className, ' ' );
  277.         extraClasses = [];
  278.  
  279.         tinymce.each( classes, function( name ) {
  280.  
  281.             if ( /^wp-image/.test( name ) ) {
  282.                 metadata.attachment_id = parseInt( name.replace( 'wp-image-', '' ), 10 );
  283.             } else if ( /^align/.test( name ) ) {
  284.                 metadata.align = name.replace( 'align', '' );
  285.             } else if ( /^size/.test( name ) ) {
  286.                 metadata.size = name.replace( 'size-', '' );
  287.             } else {
  288.                 extraClasses.push( name );
  289.             }
  290.  
  291.         } );
  292.  
  293.         metadata.extraClasses = extraClasses.join( ' ' );
  294.  
  295.         // Extract caption
  296.         captionBlock = dom.getParents( imageNode, '.wp-caption' );
  297.  
  298.         if ( captionBlock.length ) {
  299.             captionBlock = captionBlock[0];
  300.  
  301.             classes = captionBlock.className.split( ' ' );
  302.             tinymce.each( classes, function( name ) {
  303.                 if ( /^align/.test( name ) ) {
  304.                     metadata.align = name.replace( 'align', '' );
  305.                 } else if ( name && name !== 'wp-caption' ) {
  306.                     captionClassName.push( name );
  307.                 }
  308.             } );
  309.  
  310.             metadata.captionClassName = captionClassName.join( ' ' );
  311.  
  312.             caption = dom.select( 'dd.wp-caption-dd', captionBlock );
  313.             if ( caption.length ) {
  314.                 caption = caption[0];
  315.  
  316.                 metadata.caption = editor.serializer.serialize( caption )
  317.                     .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' );
  318.             }
  319.         }
  320.  
  321.         // Extract linkTo
  322.         if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' ) {
  323.             link = imageNode.parentNode;
  324.             metadata.linkUrl = dom.getAttrib( link, 'href' );
  325.             metadata.linkTargetBlank = dom.getAttrib( link, 'target' ) === '_blank' ? true : false;
  326.             metadata.linkRel = dom.getAttrib( link, 'rel' );
  327.             metadata.linkClassName = link.className;
  328.         }
  329.  
  330.         return metadata;
  331.     }
  332.  
  333.     function hasTextContent( node ) {
  334.         return node && !! ( node.textContent || node.innerText ).replace( /\ufeff/g, '' );
  335.     }
  336.  
  337.     // Verify HTML in captions
  338.     function verifyHTML( caption ) {
  339.         if ( ! caption || ( caption.indexOf( '<' ) === -1 && caption.indexOf( '>' ) === -1 ) ) {
  340.             return caption;
  341.         }
  342.  
  343.         if ( ! serializer ) {
  344.             serializer = new tinymce.html.Serializer( {}, editor.schema );
  345.         }
  346.  
  347.         return serializer.serialize( editor.parser.parse( caption, { forced_root_block: false } ) );
  348.     }
  349.  
  350.     function updateImage( imageNode, imageData ) {
  351.         var classes, className, node, html, parent, wrap, linkNode,
  352.             captionNode, dd, dl, id, attrs, linkAttrs, width, height, align,
  353.             $imageNode, srcset, src,
  354.             dom = editor.dom;
  355.  
  356.         classes = tinymce.explode( imageData.extraClasses, ' ' );
  357.  
  358.         if ( ! classes ) {
  359.             classes = [];
  360.         }
  361.  
  362.         if ( ! imageData.caption ) {
  363.             classes.push( 'align' + imageData.align );
  364.         }
  365.  
  366.         if ( imageData.attachment_id ) {
  367.             classes.push( 'wp-image-' + imageData.attachment_id );
  368.             if ( imageData.size && imageData.size !== 'custom' ) {
  369.                 classes.push( 'size-' + imageData.size );
  370.             }
  371.         }
  372.  
  373.         width = imageData.width;
  374.         height = imageData.height;
  375.  
  376.         if ( imageData.size === 'custom' ) {
  377.             width = imageData.customWidth;
  378.             height = imageData.customHeight;
  379.         }
  380.  
  381.         attrs = {
  382.             src: imageData.url,
  383.             width: width || null,
  384.             height: height || null,
  385.             title: imageData.title || null,
  386.             'class': classes.join( ' ' ) || null
  387.         };
  388.  
  389.         dom.setAttribs( imageNode, attrs );
  390.  
  391.         // Preserve empty alt attributes.
  392.         editor.$( imageNode ).attr( 'alt', imageData.alt || '' );
  393.  
  394.         linkAttrs = {
  395.             href: imageData.linkUrl,
  396.             rel: imageData.linkRel || null,
  397.             target: imageData.linkTargetBlank ? '_blank': null,
  398.             'class': imageData.linkClassName || null
  399.         };
  400.  
  401.         if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) {
  402.             // Update or remove an existing link wrapped around the image
  403.             if ( imageData.linkUrl ) {
  404.                 dom.setAttribs( imageNode.parentNode, linkAttrs );
  405.             } else {
  406.                 dom.remove( imageNode.parentNode, true );
  407.             }
  408.         } else if ( imageData.linkUrl ) {
  409.             if ( linkNode = dom.getParent( imageNode, 'a' ) ) {
  410.                 // The image is inside a link together with other nodes,
  411.                 // or is nested in another node, move it out
  412.                 dom.insertAfter( imageNode, linkNode );
  413.             }
  414.  
  415.             // Add link wrapped around the image
  416.             linkNode = dom.create( 'a', linkAttrs );
  417.             imageNode.parentNode.insertBefore( linkNode, imageNode );
  418.             linkNode.appendChild( imageNode );
  419.         }
  420.  
  421.         captionNode = editor.dom.getParent( imageNode, '.mceTemp' );
  422.  
  423.         if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) {
  424.             node = imageNode.parentNode;
  425.         } else {
  426.             node = imageNode;
  427.         }
  428.  
  429.         if ( imageData.caption ) {
  430.             imageData.caption = verifyHTML( imageData.caption );
  431.  
  432.             id = imageData.attachment_id ? 'attachment_' + imageData.attachment_id : null;
  433.             align = 'align' + ( imageData.align || 'none' );
  434.             className = 'wp-caption ' + align;
  435.  
  436.             if ( imageData.captionClassName ) {
  437.                 className += ' ' + imageData.captionClassName.replace( /[<>&]+/g,  '' );
  438.             }
  439.  
  440.             if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
  441.                 width = parseInt( width, 10 );
  442.                 width += 10;
  443.             }
  444.  
  445.             if ( captionNode ) {
  446.                 dl = dom.select( 'dl.wp-caption', captionNode );
  447.  
  448.                 if ( dl.length ) {
  449.                     dom.setAttribs( dl, {
  450.                         id: id,
  451.                         'class': className,
  452.                         style: 'width: ' + width + 'px'
  453.                     } );
  454.                 }
  455.  
  456.                 dd = dom.select( '.wp-caption-dd', captionNode );
  457.  
  458.                 if ( dd.length ) {
  459.                     dom.setHTML( dd[0], imageData.caption );
  460.                 }
  461.  
  462.             } else {
  463.                 id = id ? 'id="'+ id +'" ' : '';
  464.  
  465.                 // should create a new function for generating the caption markup
  466.                 html =  '<dl ' + id + 'class="' + className +'" style="width: '+ width +'px">' +
  467.                     '<dt class="wp-caption-dt"></dt><dd class="wp-caption-dd">'+ imageData.caption +'</dd></dl>';
  468.  
  469.                 wrap = dom.create( 'div', { 'class': 'mceTemp' }, html );
  470.  
  471.                 if ( parent = dom.getParent( node, 'p' ) ) {
  472.                     parent.parentNode.insertBefore( wrap, parent );
  473.                 } else {
  474.                     node.parentNode.insertBefore( wrap, node );
  475.                 }
  476.  
  477.                 editor.$( wrap ).find( 'dt.wp-caption-dt' ).append( node );
  478.  
  479.                 if ( parent && dom.isEmpty( parent ) ) {
  480.                     dom.remove( parent );
  481.                 }
  482.             }
  483.         } else if ( captionNode ) {
  484.             // Remove the caption wrapper and place the image in new paragraph
  485.             parent = dom.create( 'p' );
  486.             captionNode.parentNode.insertBefore( parent, captionNode );
  487.             parent.appendChild( node );
  488.             dom.remove( captionNode );
  489.         }
  490.  
  491.         $imageNode = editor.$( imageNode );
  492.         srcset = $imageNode.attr( 'srcset' );
  493.         src = $imageNode.attr( 'src' );
  494.  
  495.         // Remove srcset and sizes if the image file was edited or the image was replaced.
  496.         if ( srcset && src ) {
  497.             src = src.replace( /[?#].*/, '' );
  498.  
  499.             if ( srcset.indexOf( src ) === -1 ) {
  500.                 $imageNode.attr( 'srcset', null ).attr( 'sizes', null );
  501.             }
  502.         }
  503.  
  504.         if ( wp.media.events ) {
  505.             wp.media.events.trigger( 'editor:image-update', {
  506.                 editor: editor,
  507.                 metadata: imageData,
  508.                 image: imageNode
  509.             } );
  510.         }
  511.  
  512.         editor.nodeChanged();
  513.     }
  514.  
  515.     function editImage( img ) {
  516.         var frame, callback, metadata;
  517.  
  518.         if ( typeof wp === 'undefined' || ! wp.media ) {
  519.             editor.execCommand( 'mceImage' );
  520.             return;
  521.         }
  522.  
  523.         metadata = extractImageData( img );
  524.  
  525.         // Manipulate the metadata by reference that is fed into the PostImage model used in the media modal
  526.         wp.media.events.trigger( 'editor:image-edit', {
  527.             editor: editor,
  528.             metadata: metadata,
  529.             image: img
  530.         } );
  531.  
  532.         frame = wp.media({
  533.             frame: 'image',
  534.             state: 'image-details',
  535.             metadata: metadata
  536.         } );
  537.  
  538.         wp.media.events.trigger( 'editor:frame-create', { frame: frame } );
  539.  
  540.         callback = function( imageData ) {
  541.             editor.focus();
  542.             editor.undoManager.transact( function() {
  543.                 updateImage( img, imageData );
  544.             } );
  545.             frame.detach();
  546.         };
  547.  
  548.         frame.state('image-details').on( 'update', callback );
  549.         frame.state('replace-image').on( 'replace', callback );
  550.         frame.on( 'close', function() {
  551.             editor.focus();
  552.             frame.detach();
  553.         });
  554.  
  555.         frame.open();
  556.     }
  557.  
  558.     function removeImage( node ) {
  559.         var wrap = editor.dom.getParent( node, 'div.mceTemp' );
  560.  
  561.         if ( ! wrap && node.nodeName === 'IMG' ) {
  562.             wrap = editor.dom.getParent( node, 'a' );
  563.         }
  564.  
  565.         if ( wrap ) {
  566.             if ( wrap.nextSibling ) {
  567.                 editor.selection.select( wrap.nextSibling );
  568.             } else if ( wrap.previousSibling ) {
  569.                 editor.selection.select( wrap.previousSibling );
  570.             } else {
  571.                 editor.selection.select( wrap.parentNode );
  572.             }
  573.  
  574.             editor.selection.collapse( true );
  575.             editor.dom.remove( wrap );
  576.         } else {
  577.             editor.dom.remove( node );
  578.         }
  579.  
  580.         editor.nodeChanged();
  581.         editor.undoManager.add();
  582.     }
  583.  
  584.     editor.on( 'init', function() {
  585.         var dom = editor.dom,
  586.             captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions';
  587.  
  588.         dom.addClass( editor.getBody(), captionClass );
  589.  
  590.         // Prevent IE11 from making dl.wp-caption resizable
  591.         if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) {
  592.             // The 'mscontrolselect' event is supported only in IE11+
  593.             dom.bind( editor.getBody(), 'mscontrolselect', function( event ) {
  594.                 if ( event.target.nodeName === 'IMG' && dom.getParent( event.target, '.wp-caption' ) ) {
  595.                     // Hide the thick border with resize handles around dl.wp-caption
  596.                     editor.getBody().focus(); // :(
  597.                 } else if ( event.target.nodeName === 'DL' && dom.hasClass( event.target, 'wp-caption' ) ) {
  598.                     // Trigger the thick border with resize handles...
  599.                     // This will make the caption text editable.
  600.                     event.target.focus();
  601.                 }
  602.             });
  603.         }
  604.     });
  605.  
  606.     editor.on( 'ObjectResized', function( event ) {
  607.         var node = event.target;
  608.  
  609.         if ( node.nodeName === 'IMG' ) {
  610.             editor.undoManager.transact( function() {
  611.                 var parent, width,
  612.                     dom = editor.dom;
  613.  
  614.                 node.className = node.className.replace( /\bsize-[^ ]+/, '' );
  615.  
  616.                 if ( parent = dom.getParent( node, '.wp-caption' ) ) {
  617.                     width = event.width || dom.getAttrib( node, 'width' );
  618.  
  619.                     if ( width ) {
  620.                         width = parseInt( width, 10 );
  621.  
  622.                         if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
  623.                             width += 10;
  624.                         }
  625.  
  626.                         dom.setStyle( parent, 'width', width + 'px' );
  627.                     }
  628.                 }
  629.             });
  630.         }
  631.     });
  632.  
  633.     editor.on( 'pastePostProcess', function( event ) {
  634.         // Pasting in a caption node.
  635.         if ( editor.dom.getParent( editor.selection.getNode(), 'dd.wp-caption-dd' ) ) {
  636.             // Remove "non-block" elements that should not be in captions.
  637.             editor.$( 'img, audio, video, object, embed, iframe, script, style', event.node ).remove();
  638.  
  639.             editor.$( '*', event.node ).each( function( i, node ) {
  640.                 if ( editor.dom.isBlock( node ) ) {
  641.                     // Insert <br> where the blocks used to be. Makes it look better after pasting in the caption.
  642.                     if ( tinymce.trim( node.textContent || node.innerText ) ) {
  643.                         editor.dom.insertAfter( editor.dom.create( 'br' ), node );
  644.                         editor.dom.remove( node, true );
  645.                     } else {
  646.                         editor.dom.remove( node );
  647.                     }
  648.                 }
  649.             });
  650.  
  651.             // Trim <br> tags.
  652.             editor.$( 'br',  event.node ).each( function( i, node ) {
  653.                 if ( ! node.nextSibling || node.nextSibling.nodeName === 'BR' ||
  654.                     ! node.previousSibling || node.previousSibling.nodeName === 'BR' ) {
  655.  
  656.                     editor.dom.remove( node );
  657.                 }
  658.             } );
  659.  
  660.             // Pasted HTML is cleaned up for inserting in the caption.
  661.             pasteInCaption = true;
  662.         }
  663.     });
  664.  
  665.     editor.on( 'BeforeExecCommand', function( event ) {
  666.         var node, p, DL, align, replacement, captionParent,
  667.             cmd = event.command,
  668.             dom = editor.dom;
  669.  
  670.         if ( cmd === 'mceInsertContent' || cmd === 'Indent' || cmd === 'Outdent' ) {
  671.             node = editor.selection.getNode();
  672.             captionParent = dom.getParent( node, 'div.mceTemp' );
  673.  
  674.             if ( captionParent ) {
  675.                 if ( cmd === 'mceInsertContent' ) {
  676.                     if ( pasteInCaption ) {
  677.                         pasteInCaption = false;
  678.                         // We are in the caption element, and in 'paste' context,
  679.                         // and the pasted HTML was cleaned up on 'pastePostProcess' above.
  680.                         // Let it be pasted in the caption.
  681.                         return;
  682.                     }
  683.  
  684.                     // The paste is somewhere else in the caption DL element.
  685.                     // Prevent pasting in there as it will break the caption.
  686.                     // Make new paragraph under the caption DL and move the caret there.
  687.                     p = dom.create( 'p' );
  688.                     dom.insertAfter( p, captionParent );
  689.                     editor.selection.setCursorLocation( p, 0 );
  690.  
  691.                     // If the image is selected and the user pastes "over" it,
  692.                     // replace both the image and the caption elements with the pasted content.
  693.                     // This matches the behavior when pasting over non-caption images.
  694.                     if ( node.nodeName === 'IMG' ) {
  695.                         editor.$( captionParent ).remove();
  696.                     }
  697.  
  698.                     editor.nodeChanged();
  699.                 } else {
  700.                     // Clicking Indent or Outdent while an image with a caption is selected breaks the caption.
  701.                     // See #38313.
  702.                     event.preventDefault();
  703.                     event.stopImmediatePropagation();
  704.                     return false;
  705.                 }
  706.             }
  707.         } else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' || cmd === 'wpAlignNone' ) {
  708.             node = editor.selection.getNode();
  709.             align = 'align' + cmd.slice( 7 ).toLowerCase();
  710.             DL = editor.dom.getParent( node, '.wp-caption' );
  711.  
  712.             if ( node.nodeName !== 'IMG' && ! DL ) {
  713.                 return;
  714.             }
  715.  
  716.             node = DL || node;
  717.  
  718.             if ( editor.dom.hasClass( node, align ) ) {
  719.                 replacement = ' alignnone';
  720.             } else {
  721.                 replacement = ' ' + align;
  722.             }
  723.  
  724.             node.className = trim( node.className.replace( / ?align(left|center|right|none)/g, '' ) + replacement );
  725.  
  726.             editor.nodeChanged();
  727.             event.preventDefault();
  728.  
  729.             if ( toolbar ) {
  730.                 toolbar.reposition();
  731.             }
  732.  
  733.             editor.fire( 'ExecCommand', {
  734.                 command: cmd,
  735.                 ui: event.ui,
  736.                 value: event.value
  737.             } );
  738.         }
  739.     });
  740.  
  741.     editor.on( 'keydown', function( event ) {
  742.         var node, wrap, P, spacer,
  743.             selection = editor.selection,
  744.             keyCode = event.keyCode,
  745.             dom = editor.dom,
  746.             VK = tinymce.util.VK;
  747.  
  748.         if ( keyCode === VK.ENTER ) {
  749.             // When pressing Enter inside a caption move the caret to a new parapraph under it
  750.             node = selection.getNode();
  751.             wrap = dom.getParent( node, 'div.mceTemp' );
  752.  
  753.             if ( wrap ) {
  754.                 dom.events.cancel( event ); // Doesn't cancel all :(
  755.  
  756.                 // Remove any extra dt and dd cleated on pressing Enter...
  757.                 tinymce.each( dom.select( 'dt, dd', wrap ), function( element ) {
  758.                     if ( dom.isEmpty( element ) ) {
  759.                         dom.remove( element );
  760.                     }
  761.                 });
  762.  
  763.                 spacer = tinymce.Env.ie && tinymce.Env.ie < 11 ? '' : '<br data-mce-bogus="1" />';
  764.                 P = dom.create( 'p', null, spacer );
  765.  
  766.                 if ( node.nodeName === 'DD' ) {
  767.                     dom.insertAfter( P, wrap );
  768.                 } else {
  769.                     wrap.parentNode.insertBefore( P, wrap );
  770.                 }
  771.  
  772.                 editor.nodeChanged();
  773.                 selection.setCursorLocation( P, 0 );
  774.             }
  775.         } else if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
  776.             node = selection.getNode();
  777.  
  778.             if ( node.nodeName === 'DIV' && dom.hasClass( node, 'mceTemp' ) ) {
  779.                 wrap = node;
  780.             } else if ( node.nodeName === 'IMG' || node.nodeName === 'DT' || node.nodeName === 'A' ) {
  781.                 wrap = dom.getParent( node, 'div.mceTemp' );
  782.             }
  783.  
  784.             if ( wrap ) {
  785.                 dom.events.cancel( event );
  786.                 removeImage( node );
  787.                 return false;
  788.             }
  789.         }
  790.     });
  791.  
  792.     // After undo/redo FF seems to set the image height very slowly when it is set to 'auto' in the CSS.
  793.     // This causes image.getBoundingClientRect() to return wrong values and the resize handles are shown in wrong places.
  794.     // Collapse the selection to remove the resize handles.
  795.     if ( tinymce.Env.gecko ) {
  796.         editor.on( 'undo redo', function() {
  797.             if ( editor.selection.getNode().nodeName === 'IMG' ) {
  798.                 editor.selection.collapse();
  799.             }
  800.         });
  801.     }
  802.  
  803.     editor.wpSetImgCaption = function( content ) {
  804.         return parseShortcode( content );
  805.     };
  806.  
  807.     editor.wpGetImgCaption = function( content ) {
  808.         return getShortcode( content );
  809.     };
  810.  
  811.     editor.on( 'beforeGetContent', function( event ) {
  812.         if ( event.format !== 'raw' ) {
  813.             editor.$( 'img[id="__wp-temp-img-id"]' ).attr( 'id', null );
  814.         }
  815.     });
  816.  
  817.     editor.on( 'BeforeSetContent', function( event ) {
  818.         if ( event.format !== 'raw' ) {
  819.             event.content = editor.wpSetImgCaption( event.content );
  820.         }
  821.     });
  822.  
  823.     editor.on( 'PostProcess', function( event ) {
  824.         if ( event.get ) {
  825.             event.content = editor.wpGetImgCaption( event.content );
  826.         }
  827.     });
  828.  
  829.     ( function() {
  830.         var wrap;
  831.  
  832.         editor.on( 'dragstart', function() {
  833.             var node = editor.selection.getNode();
  834.  
  835.             if ( node.nodeName === 'IMG' ) {
  836.                 wrap = editor.dom.getParent( node, '.mceTemp' );
  837.  
  838.                 if ( ! wrap && node.parentNode.nodeName === 'A' && ! hasTextContent( node.parentNode ) ) {
  839.                     wrap = node.parentNode;
  840.                 }
  841.             }
  842.         } );
  843.  
  844.         editor.on( 'drop', function( event ) {
  845.             var dom = editor.dom,
  846.                 rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint( event.clientX, event.clientY, editor.getDoc() );
  847.  
  848.             // Don't allow anything to be dropped in a captioned image.
  849.             if ( rng && dom.getParent( rng.startContainer, '.mceTemp' ) ) {
  850.                 event.preventDefault();
  851.             } else if ( wrap ) {
  852.                 event.preventDefault();
  853.  
  854.                 editor.undoManager.transact( function() {
  855.                     if ( rng ) {
  856.                         editor.selection.setRng( rng );
  857.                     }
  858.  
  859.                     editor.selection.setNode( wrap );
  860.                     dom.remove( wrap );
  861.                 } );
  862.             }
  863.  
  864.             wrap = null;
  865.         } );
  866.     } )();
  867.  
  868.     // Add to editor.wp
  869.     editor.wp = editor.wp || {};
  870.     editor.wp.isPlaceholder = isPlaceholder;
  871.  
  872.     // Back-compat.
  873.     return {
  874.         _do_shcode: parseShortcode,
  875.         _get_shcode: getShortcode
  876.     };
  877. });
  878.