ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

plugin.js (25642B)


      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|button', // '|button' is not displayed, only 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, imageNode,
    352 			captionNode, dd, dl, id, attrs, linkAttrs, width, height, align,
    353 			$imageNode, srcset, src,
    354 			dom = editor.dom;
    355 
    356 		if ( ! $imageNode || ! $imageNode.length ) {
    357 			return;
    358 		}
    359 
    360 		imageNode = $imageNode[0];
    361 		classes = tinymce.explode( imageData.extraClasses, ' ' );
    362 
    363 		if ( ! classes ) {
    364 			classes = [];
    365 		}
    366 
    367 		if ( ! imageData.caption ) {
    368 			classes.push( 'align' + imageData.align );
    369 		}
    370 
    371 		if ( imageData.attachment_id ) {
    372 			classes.push( 'wp-image-' + imageData.attachment_id );
    373 			if ( imageData.size && imageData.size !== 'custom' ) {
    374 				classes.push( 'size-' + imageData.size );
    375 			}
    376 		}
    377 
    378 		width = imageData.width;
    379 		height = imageData.height;
    380 
    381 		if ( imageData.size === 'custom' ) {
    382 			width = imageData.customWidth;
    383 			height = imageData.customHeight;
    384 		}
    385 
    386 		attrs = {
    387 			src: imageData.url,
    388 			width: width || null,
    389 			height: height || null,
    390 			title: imageData.title || null,
    391 			'class': classes.join( ' ' ) || null
    392 		};
    393 
    394 		dom.setAttribs( imageNode, attrs );
    395 
    396 		// Preserve empty alt attributes.
    397 		$imageNode.attr( 'alt', imageData.alt || '' );
    398 
    399 		linkAttrs = {
    400 			href: imageData.linkUrl,
    401 			rel: imageData.linkRel || null,
    402 			target: imageData.linkTargetBlank ? '_blank': null,
    403 			'class': imageData.linkClassName || null
    404 		};
    405 
    406 		if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) {
    407 			// Update or remove an existing link wrapped around the image.
    408 			if ( imageData.linkUrl ) {
    409 				dom.setAttribs( imageNode.parentNode, linkAttrs );
    410 			} else {
    411 				dom.remove( imageNode.parentNode, true );
    412 			}
    413 		} else if ( imageData.linkUrl ) {
    414 			if ( linkNode = dom.getParent( imageNode, 'a' ) ) {
    415 				// The image is inside a link together with other nodes,
    416 				// or is nested in another node, move it out.
    417 				dom.insertAfter( imageNode, linkNode );
    418 			}
    419 
    420 			// Add link wrapped around the image.
    421 			linkNode = dom.create( 'a', linkAttrs );
    422 			imageNode.parentNode.insertBefore( linkNode, imageNode );
    423 			linkNode.appendChild( imageNode );
    424 		}
    425 
    426 		captionNode = editor.dom.getParent( imageNode, '.mceTemp' );
    427 
    428 		if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) {
    429 			node = imageNode.parentNode;
    430 		} else {
    431 			node = imageNode;
    432 		}
    433 
    434 		if ( imageData.caption ) {
    435 			imageData.caption = verifyHTML( imageData.caption );
    436 
    437 			id = imageData.attachment_id ? 'attachment_' + imageData.attachment_id : null;
    438 			align = 'align' + ( imageData.align || 'none' );
    439 			className = 'wp-caption ' + align;
    440 
    441 			if ( imageData.captionClassName ) {
    442 				className += ' ' + imageData.captionClassName.replace( /[<>&]+/g,  '' );
    443 			}
    444 
    445 			if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
    446 				width = parseInt( width, 10 );
    447 				width += 10;
    448 			}
    449 
    450 			if ( captionNode ) {
    451 				dl = dom.select( 'dl.wp-caption', captionNode );
    452 
    453 				if ( dl.length ) {
    454 					dom.setAttribs( dl, {
    455 						id: id,
    456 						'class': className,
    457 						style: 'width: ' + width + 'px'
    458 					} );
    459 				}
    460 
    461 				dd = dom.select( '.wp-caption-dd', captionNode );
    462 
    463 				if ( dd.length ) {
    464 					dom.setHTML( dd[0], imageData.caption );
    465 				}
    466 
    467 			} else {
    468 				id = id ? 'id="'+ id +'" ' : '';
    469 
    470 				// Should create a new function for generating the caption markup.
    471 				html =  '<dl ' + id + 'class="' + className +'" style="width: '+ width +'px">' +
    472 					'<dt class="wp-caption-dt"></dt><dd class="wp-caption-dd">'+ imageData.caption +'</dd></dl>';
    473 
    474 				wrap = dom.create( 'div', { 'class': 'mceTemp' }, html );
    475 
    476 				if ( parent = dom.getParent( node, 'p' ) ) {
    477 					parent.parentNode.insertBefore( wrap, parent );
    478 				} else {
    479 					node.parentNode.insertBefore( wrap, node );
    480 				}
    481 
    482 				editor.$( wrap ).find( 'dt.wp-caption-dt' ).append( node );
    483 
    484 				if ( parent && dom.isEmpty( parent ) ) {
    485 					dom.remove( parent );
    486 				}
    487 			}
    488 		} else if ( captionNode ) {
    489 			// Remove the caption wrapper and place the image in new paragraph.
    490 			parent = dom.create( 'p' );
    491 			captionNode.parentNode.insertBefore( parent, captionNode );
    492 			parent.appendChild( node );
    493 			dom.remove( captionNode );
    494 		}
    495 
    496 		$imageNode = editor.$( imageNode );
    497 		srcset = $imageNode.attr( 'srcset' );
    498 		src = $imageNode.attr( 'src' );
    499 
    500 		// Remove srcset and sizes if the image file was edited or the image was replaced.
    501 		if ( srcset && src ) {
    502 			src = src.replace( /[?#].*/, '' );
    503 
    504 			if ( srcset.indexOf( src ) === -1 ) {
    505 				$imageNode.attr( 'srcset', null ).attr( 'sizes', null );
    506 			}
    507 		}
    508 
    509 		if ( wp.media.events ) {
    510 			wp.media.events.trigger( 'editor:image-update', {
    511 				editor: editor,
    512 				metadata: imageData,
    513 				image: imageNode
    514 			} );
    515 		}
    516 
    517 		editor.nodeChanged();
    518 	}
    519 
    520 	function editImage( img ) {
    521 		var frame, callback, metadata, imageNode;
    522 
    523 		if ( typeof wp === 'undefined' || ! wp.media ) {
    524 			editor.execCommand( 'mceImage' );
    525 			return;
    526 		}
    527 
    528 		metadata = extractImageData( img );
    529 
    530 		// Mark the image node so we can select it later.
    531 		editor.$( img ).attr( 'data-wp-editing', 1 );
    532 
    533 		// Manipulate the metadata by reference that is fed into the PostImage model used in the media modal.
    534 		wp.media.events.trigger( 'editor:image-edit', {
    535 			editor: editor,
    536 			metadata: metadata,
    537 			image: img
    538 		} );
    539 
    540 		frame = wp.media({
    541 			frame: 'image',
    542 			state: 'image-details',
    543 			metadata: metadata
    544 		} );
    545 
    546 		wp.media.events.trigger( 'editor:frame-create', { frame: frame } );
    547 
    548 		callback = function( imageData ) {
    549 			editor.undoManager.transact( function() {
    550 				updateImage( imageNode, imageData );
    551 			} );
    552 			frame.detach();
    553 		};
    554 
    555 		frame.state('image-details').on( 'update', callback );
    556 		frame.state('replace-image').on( 'replace', callback );
    557 		frame.on( 'close', function() {
    558 			editor.focus();
    559 			frame.detach();
    560 
    561 			/*
    562 			 * `close` fires first...
    563 			 * To be able to update the image node, we need to find it here,
    564 			 * and use it in the callback.
    565 			 */
    566 			imageNode = editor.$( 'img[data-wp-editing]' )
    567 			imageNode.removeAttr( 'data-wp-editing' );
    568 		});
    569 
    570 		frame.open();
    571 	}
    572 
    573 	function removeImage( node ) {
    574 		var wrap = editor.dom.getParent( node, 'div.mceTemp' );
    575 
    576 		if ( ! wrap && node.nodeName === 'IMG' ) {
    577 			wrap = editor.dom.getParent( node, 'a' );
    578 		}
    579 
    580 		if ( wrap ) {
    581 			if ( wrap.nextSibling ) {
    582 				editor.selection.select( wrap.nextSibling );
    583 			} else if ( wrap.previousSibling ) {
    584 				editor.selection.select( wrap.previousSibling );
    585 			} else {
    586 				editor.selection.select( wrap.parentNode );
    587 			}
    588 
    589 			editor.selection.collapse( true );
    590 			editor.dom.remove( wrap );
    591 		} else {
    592 			editor.dom.remove( node );
    593 		}
    594 
    595 		editor.nodeChanged();
    596 		editor.undoManager.add();
    597 	}
    598 
    599 	editor.on( 'init', function() {
    600 		var dom = editor.dom,
    601 			captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions';
    602 
    603 		dom.addClass( editor.getBody(), captionClass );
    604 
    605 		// Prevent IE11 from making dl.wp-caption resizable.
    606 		if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) {
    607 			// The 'mscontrolselect' event is supported only in IE11+.
    608 			dom.bind( editor.getBody(), 'mscontrolselect', function( event ) {
    609 				if ( event.target.nodeName === 'IMG' && dom.getParent( event.target, '.wp-caption' ) ) {
    610 					// Hide the thick border with resize handles around dl.wp-caption.
    611 					editor.getBody().focus(); // :(
    612 				} else if ( event.target.nodeName === 'DL' && dom.hasClass( event.target, 'wp-caption' ) ) {
    613 					// Trigger the thick border with resize handles...
    614 					// This will make the caption text editable.
    615 					event.target.focus();
    616 				}
    617 			});
    618 		}
    619 	});
    620 
    621 	editor.on( 'ObjectResized', function( event ) {
    622 		var node = event.target;
    623 
    624 		if ( node.nodeName === 'IMG' ) {
    625 			editor.undoManager.transact( function() {
    626 				var parent, width,
    627 					dom = editor.dom;
    628 
    629 				node.className = node.className.replace( /\bsize-[^ ]+/, '' );
    630 
    631 				if ( parent = dom.getParent( node, '.wp-caption' ) ) {
    632 					width = event.width || dom.getAttrib( node, 'width' );
    633 
    634 					if ( width ) {
    635 						width = parseInt( width, 10 );
    636 
    637 						if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
    638 							width += 10;
    639 						}
    640 
    641 						dom.setStyle( parent, 'width', width + 'px' );
    642 					}
    643 				}
    644 			});
    645 		}
    646 	});
    647 
    648 	editor.on( 'pastePostProcess', function( event ) {
    649 		// Pasting in a caption node.
    650 		if ( editor.dom.getParent( editor.selection.getNode(), 'dd.wp-caption-dd' ) ) {
    651 			// Remove "non-block" elements that should not be in captions.
    652 			editor.$( 'img, audio, video, object, embed, iframe, script, style', event.node ).remove();
    653 
    654 			editor.$( '*', event.node ).each( function( i, node ) {
    655 				if ( editor.dom.isBlock( node ) ) {
    656 					// Insert <br> where the blocks used to be. Makes it look better after pasting in the caption.
    657 					if ( tinymce.trim( node.textContent || node.innerText ) ) {
    658 						editor.dom.insertAfter( editor.dom.create( 'br' ), node );
    659 						editor.dom.remove( node, true );
    660 					} else {
    661 						editor.dom.remove( node );
    662 					}
    663 				}
    664 			});
    665 
    666 			// Trim <br> tags.
    667 			editor.$( 'br',  event.node ).each( function( i, node ) {
    668 				if ( ! node.nextSibling || node.nextSibling.nodeName === 'BR' ||
    669 					! node.previousSibling || node.previousSibling.nodeName === 'BR' ) {
    670 
    671 					editor.dom.remove( node );
    672 				}
    673 			} );
    674 
    675 			// Pasted HTML is cleaned up for inserting in the caption.
    676 			pasteInCaption = true;
    677 		}
    678 	});
    679 
    680 	editor.on( 'BeforeExecCommand', function( event ) {
    681 		var node, p, DL, align, replacement, captionParent,
    682 			cmd = event.command,
    683 			dom = editor.dom;
    684 
    685 		if ( cmd === 'mceInsertContent' || cmd === 'Indent' || cmd === 'Outdent' ) {
    686 			node = editor.selection.getNode();
    687 			captionParent = dom.getParent( node, 'div.mceTemp' );
    688 
    689 			if ( captionParent ) {
    690 				if ( cmd === 'mceInsertContent' ) {
    691 					if ( pasteInCaption ) {
    692 						pasteInCaption = false;
    693 						/*
    694 						 * We are in the caption element, and in 'paste' context,
    695 						 * and the pasted HTML was cleaned up on 'pastePostProcess' above.
    696 						 * Let it be pasted in the caption.
    697 						 */
    698 						return;
    699 					}
    700 
    701 					/*
    702 					 * The paste is somewhere else in the caption DL element.
    703 					 * Prevent pasting in there as it will break the caption.
    704 					 * Make new paragraph under the caption DL and move the caret there.
    705 					 */
    706 					p = dom.create( 'p' );
    707 					dom.insertAfter( p, captionParent );
    708 					editor.selection.setCursorLocation( p, 0 );
    709 
    710 					/*
    711 					 * If the image is selected and the user pastes "over" it,
    712 					 * replace both the image and the caption elements with the pasted content.
    713 					 * This matches the behavior when pasting over non-caption images.
    714 					 */
    715 					if ( node.nodeName === 'IMG' ) {
    716 						editor.$( captionParent ).remove();
    717 					}
    718 
    719 					editor.nodeChanged();
    720 				} else {
    721 					// Clicking Indent or Outdent while an image with a caption is selected breaks the caption.
    722 					// See #38313.
    723 					event.preventDefault();
    724 					event.stopImmediatePropagation();
    725 					return false;
    726 				}
    727 			}
    728 		} else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' || cmd === 'wpAlignNone' ) {
    729 			node = editor.selection.getNode();
    730 			align = 'align' + cmd.slice( 7 ).toLowerCase();
    731 			DL = editor.dom.getParent( node, '.wp-caption' );
    732 
    733 			if ( node.nodeName !== 'IMG' && ! DL ) {
    734 				return;
    735 			}
    736 
    737 			node = DL || node;
    738 
    739 			if ( editor.dom.hasClass( node, align ) ) {
    740 				replacement = ' alignnone';
    741 			} else {
    742 				replacement = ' ' + align;
    743 			}
    744 
    745 			node.className = trim( node.className.replace( / ?align(left|center|right|none)/g, '' ) + replacement );
    746 
    747 			editor.nodeChanged();
    748 			event.preventDefault();
    749 
    750 			if ( toolbar ) {
    751 				toolbar.reposition();
    752 			}
    753 
    754 			editor.fire( 'ExecCommand', {
    755 				command: cmd,
    756 				ui: event.ui,
    757 				value: event.value
    758 			} );
    759 		}
    760 	});
    761 
    762 	editor.on( 'keydown', function( event ) {
    763 		var node, wrap, P, spacer,
    764 			selection = editor.selection,
    765 			keyCode = event.keyCode,
    766 			dom = editor.dom,
    767 			VK = tinymce.util.VK;
    768 
    769 		if ( keyCode === VK.ENTER ) {
    770 			// When pressing Enter inside a caption move the caret to a new parapraph under it.
    771 			node = selection.getNode();
    772 			wrap = dom.getParent( node, 'div.mceTemp' );
    773 
    774 			if ( wrap ) {
    775 				dom.events.cancel( event ); // Doesn't cancel all :(
    776 
    777 				// Remove any extra dt and dd cleated on pressing Enter...
    778 				tinymce.each( dom.select( 'dt, dd', wrap ), function( element ) {
    779 					if ( dom.isEmpty( element ) ) {
    780 						dom.remove( element );
    781 					}
    782 				});
    783 
    784 				spacer = tinymce.Env.ie && tinymce.Env.ie < 11 ? '' : '<br data-mce-bogus="1" />';
    785 				P = dom.create( 'p', null, spacer );
    786 
    787 				if ( node.nodeName === 'DD' ) {
    788 					dom.insertAfter( P, wrap );
    789 				} else {
    790 					wrap.parentNode.insertBefore( P, wrap );
    791 				}
    792 
    793 				editor.nodeChanged();
    794 				selection.setCursorLocation( P, 0 );
    795 			}
    796 		} else if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
    797 			node = selection.getNode();
    798 
    799 			if ( node.nodeName === 'DIV' && dom.hasClass( node, 'mceTemp' ) ) {
    800 				wrap = node;
    801 			} else if ( node.nodeName === 'IMG' || node.nodeName === 'DT' || node.nodeName === 'A' ) {
    802 				wrap = dom.getParent( node, 'div.mceTemp' );
    803 			}
    804 
    805 			if ( wrap ) {
    806 				dom.events.cancel( event );
    807 				removeImage( node );
    808 				return false;
    809 			}
    810 		}
    811 	});
    812 
    813 	/*
    814 	 * After undo/redo FF seems to set the image height very slowly when it is set to 'auto' in the CSS.
    815 	 * This causes image.getBoundingClientRect() to return wrong values and the resize handles are shown in wrong places.
    816 	 * Collapse the selection to remove the resize handles.
    817 	 */
    818 	if ( tinymce.Env.gecko ) {
    819 		editor.on( 'undo redo', function() {
    820 			if ( editor.selection.getNode().nodeName === 'IMG' ) {
    821 				editor.selection.collapse();
    822 			}
    823 		});
    824 	}
    825 
    826 	editor.wpSetImgCaption = function( content ) {
    827 		return parseShortcode( content );
    828 	};
    829 
    830 	editor.wpGetImgCaption = function( content ) {
    831 		return getShortcode( content );
    832 	};
    833 
    834 	editor.on( 'beforeGetContent', function( event ) {
    835 		if ( event.format !== 'raw' ) {
    836 			editor.$( 'img[id="__wp-temp-img-id"]' ).removeAttr( 'id' );
    837 		}
    838 	});
    839 
    840 	editor.on( 'BeforeSetContent', function( event ) {
    841 		if ( event.format !== 'raw' ) {
    842 			event.content = editor.wpSetImgCaption( event.content );
    843 		}
    844 	});
    845 
    846 	editor.on( 'PostProcess', function( event ) {
    847 		if ( event.get ) {
    848 			event.content = editor.wpGetImgCaption( event.content );
    849 		}
    850 	});
    851 
    852 	( function() {
    853 		var wrap;
    854 
    855 		editor.on( 'dragstart', function() {
    856 			var node = editor.selection.getNode();
    857 
    858 			if ( node.nodeName === 'IMG' ) {
    859 				wrap = editor.dom.getParent( node, '.mceTemp' );
    860 
    861 				if ( ! wrap && node.parentNode.nodeName === 'A' && ! hasTextContent( node.parentNode ) ) {
    862 					wrap = node.parentNode;
    863 				}
    864 			}
    865 		} );
    866 
    867 		editor.on( 'drop', function( event ) {
    868 			var dom = editor.dom,
    869 				rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint( event.clientX, event.clientY, editor.getDoc() );
    870 
    871 			// Don't allow anything to be dropped in a captioned image.
    872 			if ( rng && dom.getParent( rng.startContainer, '.mceTemp' ) ) {
    873 				event.preventDefault();
    874 			} else if ( wrap ) {
    875 				event.preventDefault();
    876 
    877 				editor.undoManager.transact( function() {
    878 					if ( rng ) {
    879 						editor.selection.setRng( rng );
    880 					}
    881 
    882 					editor.selection.setNode( wrap );
    883 					dom.remove( wrap );
    884 				} );
    885 			}
    886 
    887 			wrap = null;
    888 		} );
    889 	} )();
    890 
    891 	// Add to editor.wp.
    892 	editor.wp = editor.wp || {};
    893 	editor.wp.isPlaceholder = isPlaceholder;
    894 
    895 	// Back-compat.
    896 	return {
    897 		_do_shcode: parseShortcode,
    898 		_get_shcode: getShortcode
    899 	};
    900 });