wp/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     1 /* global tinymce */
     1 /* global tinymce */
     2 tinymce.PluginManager.add( 'wpeditimage', function( editor ) {
     2 tinymce.PluginManager.add( 'wpeditimage', function( editor ) {
     3 	var toolbar, serializer,
     3 	var toolbar, serializer, touchOnImage, pasteInCaption,
     4 		each = tinymce.each,
     4 		each = tinymce.each,
       
     5 		trim = tinymce.trim,
     5 		iOS = tinymce.Env.iOS;
     6 		iOS = tinymce.Env.iOS;
     6 
     7 
     7 	function isPlaceholder( node ) {
     8 	function isPlaceholder( node ) {
     8 		return !! ( editor.dom.getAttrib( node, 'data-mce-placeholder' ) || editor.dom.getAttrib( node, 'data-mce-object' ) );
     9 		return !! ( editor.dom.getAttrib( node, 'data-mce-placeholder' ) || editor.dom.getAttrib( node, 'data-mce-object' ) );
     9 	}
    10 	}
    15 			removeImage( editor.selection.getNode() );
    16 			removeImage( editor.selection.getNode() );
    16 		}
    17 		}
    17 	} );
    18 	} );
    18 
    19 
    19 	editor.addButton( 'wp_img_edit', {
    20 	editor.addButton( 'wp_img_edit', {
    20 		tooltip: 'Edit ', // trailing space is needed, used for context
    21 		tooltip: 'Edit|button', // '|button' is not displayed, only used for context
    21 		icon: 'dashicon dashicons-edit',
    22 		icon: 'dashicon dashicons-edit',
    22 		onclick: function() {
    23 		onclick: function() {
    23 			editImage( editor.selection.getNode() );
    24 			editImage( editor.selection.getNode() );
    24 		}
    25 		}
    25 	} );
    26 	} );
    34 
    35 
    35 		editor.addButton( 'wp_img_' + name, {
    36 		editor.addButton( 'wp_img_' + name, {
    36 			tooltip: tooltip,
    37 			tooltip: tooltip,
    37 			icon: 'dashicon dashicons-align-' + direction,
    38 			icon: 'dashicon dashicons-align-' + direction,
    38 			cmd: 'alignnone' === name ? 'wpAlignNone' : 'Justify' + direction.slice( 0, 1 ).toUpperCase() + direction.slice( 1 ),
    39 			cmd: 'alignnone' === name ? 'wpAlignNone' : 'Justify' + direction.slice( 0, 1 ).toUpperCase() + direction.slice( 1 ),
    39 			onPostRender: function() {
    40 			onPostRender: function() {
    40 				var self = this;
    41 				var self = this;
    41 
    42 
    42 				editor.on( 'NodeChange', function( event ) {
    43 				editor.on( 'NodeChange', function( event ) {
    43 					var node;
    44 					var node;
    44 
    45 
    58 			}
    59 			}
    59 		} );
    60 		} );
    60 	} );
    61 	} );
    61 
    62 
    62 	editor.once( 'preinit', function() {
    63 	editor.once( 'preinit', function() {
    63 		toolbar = editor.wp._createToolbar( [
    64 		if ( editor.wp && editor.wp._createToolbar ) {
    64 			'wp_img_alignleft',
    65 			toolbar = editor.wp._createToolbar( [
    65 			'wp_img_aligncenter',
    66 				'wp_img_alignleft',
    66 			'wp_img_alignright',
    67 				'wp_img_aligncenter',
    67 			'wp_img_alignnone',
    68 				'wp_img_alignright',
    68 			'wp_img_edit',
    69 				'wp_img_alignnone',
    69 			'wp_img_remove'
    70 				'wp_img_edit',
    70 		] );
    71 				'wp_img_remove'
       
    72 			] );
       
    73 		}
    71 	} );
    74 	} );
    72 
    75 
    73 	editor.on( 'wptoolbar', function( event ) {
    76 	editor.on( 'wptoolbar', function( event ) {
    74 		if ( event.element.nodeName === 'IMG' && ! isPlaceholder( event.element ) ) {
    77 		if ( event.element.nodeName === 'IMG' && ! isPlaceholder( event.element ) ) {
    75 			event.toolbar = toolbar;
    78 			event.toolbar = toolbar;
    76 		}
    79 		}
    77 	} );
    80 	} );
    78 
    81 
    79 	// Safari on iOS fails to select image nodes in contentEditoble mode on touch/click.
    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.
    80 	// Select them again.
    88 	// Select them again.
    81 	if ( iOS ) {
    89 	if ( iOS ) {
    82 		editor.on( 'click', function( event ) {
    90 		editor.on( 'init', function() {
    83 			if ( event.target.nodeName === 'IMG' ) {
    91 			editor.on( 'touchstart', function( event ) {
    84 				var node = event.target;
    92 				if ( event.target.nodeName === 'IMG' && ! isNonEditable( event.target ) ) {
    85 
    93 					touchOnImage = true;
    86 				window.setTimeout( function() {
    94 				}
    87 					editor.selection.select( node );
    95 			});
    88 					editor.nodeChanged();
    96 
    89 				}, 200 );
    97 			editor.dom.bind( editor.getDoc(), 'touchmove', function() {
    90 			} else {
    98 				touchOnImage = false;
    91 				toolbar.hide();
    99 			});
    92 			}
   100 
    93 		} );
   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 		});
    94 	}
   116 	}
    95 
   117 
    96 	function parseShortcode( content ) {
   118 	function parseShortcode( content ) {
    97 		return content.replace( /(?:<p>)?\[(?:wp_)?caption([^\]]+)\]([\s\S]+?)\[\/(?:wp_)?caption\](?:<\/p>)?/g, function( a, b, c ) {
   119 		return content.replace( /(?:<p>)?\[(?:wp_)?caption([^\]]+)\]([\s\S]+?)\[\/(?:wp_)?caption\](?:<\/p>)?/g, function( a, b, c ) {
    98 			var id, align, classes, caption, img, width,
   120 			var id, align, classes, caption, img, width;
    99 				trim = tinymce.trim;
       
   100 
   121 
   101 			id = b.match( /id=['"]([^'"]*)['"] ?/ );
   122 			id = b.match( /id=['"]([^'"]*)['"] ?/ );
   102 			if ( id ) {
   123 			if ( id ) {
   103 				b = b.replace( id[0], '' );
   124 				b = b.replace( id[0], '' );
   104 			}
   125 			}
   155 				'<dt class="wp-caption-dt">'+ img +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl></div>';
   176 				'<dt class="wp-caption-dt">'+ img +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl></div>';
   156 		});
   177 		});
   157 	}
   178 	}
   158 
   179 
   159 	function getShortcode( content ) {
   180 	function getShortcode( content ) {
   160 		return content.replace( /<div (?:id="attachment_|class="mceTemp)[^>]*>([\s\S]+?)<\/div>/g, function( a, b ) {
   181 		return content.replace( /(?:<div [^>]+mceTemp[^>]+>)?\s*(<dl [^>]+wp-caption[^>]+>[\s\S]+?<\/dl>)\s*(?:<\/div>)?/g, function( all, dl ) {
   161 			var out = '';
   182 			var out = '';
   162 
   183 
   163 			if ( b.indexOf('<img ') === -1 ) {
   184 			if ( dl.indexOf('<img ') === -1 || dl.indexOf('</p>') !== -1 ) {
   164 				// Broken caption. The user managed to drag the image out?
   185 				// Broken caption. The user managed to drag the image out or type in the wrapper div?
   165 				// Try to return the caption text as a paragraph.
   186 				// Remove the <dl>, <dd> and <dt> and return the remaining text.
   166 				out = b.match( /<dd [^>]+>([\s\S]+?)<\/dd>/i );
   187 				return dl.replace( /<d[ldt]( [^>]+)?>/g, '' ).replace( /<\/d[ldt]>/g, '' );
   167 
   188 			}
   168 				if ( out && out[1] ) {
   189 
   169 					return '<p>' + out[1] + '</p>';
   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 ) {
   170 				}
       
   171 
       
   172 				return '';
       
   173 			}
       
   174 
       
   175 			out = b.replace( /\s*<dl ([^>]+)>\s*<dt [^>]+>([\s\S]+?)<\/dt>\s*<dd [^>]+>([\s\S]*?)<\/dd>\s*<\/dl>\s*/gi, function( a, b, c, caption ) {
       
   176 				var id, classes, align, width;
   191 				var id, classes, align, width;
   177 
   192 
   178 				width = c.match( /width="([0-9]*)"/ );
   193 				width = c.match( /width="([0-9]*)"/ );
   179 				width = ( width && width[1] ) ? width[1] : '';
   194 				width = ( width && width[1] ) ? width[1] : '';
   180 
   195 
   210 			});
   225 			});
   211 
   226 
   212 			if ( out.indexOf('[caption') === -1 ) {
   227 			if ( out.indexOf('[caption') === -1 ) {
   213 				// the caption html seems broken, try to find the image that may be wrapped in a link
   228 				// the caption html seems broken, try to find the image that may be wrapped in a link
   214 				// and may be followed by <p> with the caption text.
   229 				// and may be followed by <p> with the caption text.
   215 				out = b.replace( /[\s\S]*?((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)(<p>[\s\S]*<\/p>)?[\s\S]*/gi, '<p>$1</p>$2' );
   230 				out = dl.replace( /[\s\S]*?((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)(<p>[\s\S]*<\/p>)?[\s\S]*/gi, '<p>$1</p>$2' );
   216 			}
   231 			}
   217 
   232 
   218 			return out;
   233 			return out;
   219 		});
   234 		});
   220 	}
   235 	}
   314 
   329 
   315 		return metadata;
   330 		return metadata;
   316 	}
   331 	}
   317 
   332 
   318 	function hasTextContent( node ) {
   333 	function hasTextContent( node ) {
   319 		return node && !! ( node.textContent || node.innerText );
   334 		return node && !! ( node.textContent || node.innerText ).replace( /\ufeff/g, '' );
   320 	}
   335 	}
   321 
   336 
   322 	// Verify HTML in captions
   337 	// Verify HTML in captions
   323 	function verifyHTML( caption ) {
   338 	function verifyHTML( caption ) {
   324 		if ( ! caption || ( caption.indexOf( '<' ) === -1 && caption.indexOf( '>' ) === -1 ) ) {
   339 		if ( ! caption || ( caption.indexOf( '<' ) === -1 && caption.indexOf( '>' ) === -1 ) ) {
   333 	}
   348 	}
   334 
   349 
   335 	function updateImage( imageNode, imageData ) {
   350 	function updateImage( imageNode, imageData ) {
   336 		var classes, className, node, html, parent, wrap, linkNode,
   351 		var classes, className, node, html, parent, wrap, linkNode,
   337 			captionNode, dd, dl, id, attrs, linkAttrs, width, height, align,
   352 			captionNode, dd, dl, id, attrs, linkAttrs, width, height, align,
       
   353 			$imageNode, srcset, src,
   338 			dom = editor.dom;
   354 			dom = editor.dom;
   339 
   355 
   340 		classes = tinymce.explode( imageData.extraClasses, ' ' );
   356 		classes = tinymce.explode( imageData.extraClasses, ' ' );
   341 
   357 
   342 		if ( ! classes ) {
   358 		if ( ! classes ) {
   364 
   380 
   365 		attrs = {
   381 		attrs = {
   366 			src: imageData.url,
   382 			src: imageData.url,
   367 			width: width || null,
   383 			width: width || null,
   368 			height: height || null,
   384 			height: height || null,
   369 			alt: imageData.alt,
       
   370 			title: imageData.title || null,
   385 			title: imageData.title || null,
   371 			'class': classes.join( ' ' ) || null
   386 			'class': classes.join( ' ' ) || null
   372 		};
   387 		};
   373 
   388 
   374 		dom.setAttribs( imageNode, attrs );
   389 		dom.setAttribs( imageNode, attrs );
       
   390 
       
   391 		// Preserve empty alt attributes.
       
   392 		editor.$( imageNode ).attr( 'alt', imageData.alt || '' );
   375 
   393 
   376 		linkAttrs = {
   394 		linkAttrs = {
   377 			href: imageData.linkUrl,
   395 			href: imageData.linkUrl,
   378 			rel: imageData.linkRel || null,
   396 			rel: imageData.linkRel || null,
   379 			target: imageData.linkTargetBlank ? '_blank': null,
   397 			target: imageData.linkTargetBlank ? '_blank': null,
   466 			// Remove the caption wrapper and place the image in new paragraph
   484 			// Remove the caption wrapper and place the image in new paragraph
   467 			parent = dom.create( 'p' );
   485 			parent = dom.create( 'p' );
   468 			captionNode.parentNode.insertBefore( parent, captionNode );
   486 			captionNode.parentNode.insertBefore( parent, captionNode );
   469 			parent.appendChild( node );
   487 			parent.appendChild( node );
   470 			dom.remove( captionNode );
   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 			}
   471 		}
   502 		}
   472 
   503 
   473 		if ( wp.media.events ) {
   504 		if ( wp.media.events ) {
   474 			wp.media.events.trigger( 'editor:image-update', {
   505 			wp.media.events.trigger( 'editor:image-update', {
   475 				editor: editor,
   506 				editor: editor,
   523 
   554 
   524 		frame.open();
   555 		frame.open();
   525 	}
   556 	}
   526 
   557 
   527 	function removeImage( node ) {
   558 	function removeImage( node ) {
   528 		var wrap;
   559 		var wrap = editor.dom.getParent( node, 'div.mceTemp' );
   529 
   560 
   530 		if ( node.nodeName === 'DIV' && editor.dom.hasClass( node, 'mceTemp' ) ) {
   561 		if ( ! wrap && node.nodeName === 'IMG' ) {
   531 			wrap = node;
   562 			wrap = editor.dom.getParent( node, 'a' );
   532 		} else if ( node.nodeName === 'IMG' || node.nodeName === 'DT' || node.nodeName === 'A' ) {
       
   533 			wrap = editor.dom.getParent( node, 'div.mceTemp' );
       
   534 		}
   563 		}
   535 
   564 
   536 		if ( wrap ) {
   565 		if ( wrap ) {
   537 			if ( wrap.nextSibling ) {
   566 			if ( wrap.nextSibling ) {
   538 				editor.selection.select( wrap.nextSibling );
   567 				editor.selection.select( wrap.nextSibling );
   555 	editor.on( 'init', function() {
   584 	editor.on( 'init', function() {
   556 		var dom = editor.dom,
   585 		var dom = editor.dom,
   557 			captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions';
   586 			captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions';
   558 
   587 
   559 		dom.addClass( editor.getBody(), captionClass );
   588 		dom.addClass( editor.getBody(), captionClass );
   560 
       
   561 		// Add caption field to the default image dialog
       
   562 		editor.on( 'wpLoadImageForm', function( event ) {
       
   563 			if ( editor.getParam( 'wpeditimage_disable_captions' ) ) {
       
   564 				return;
       
   565 			}
       
   566 
       
   567 			var captionField = {
       
   568 				type: 'textbox',
       
   569 				flex: 1,
       
   570 				name: 'caption',
       
   571 				minHeight: 60,
       
   572 				multiline: true,
       
   573 				scroll: true,
       
   574 				label: 'Image caption'
       
   575 			};
       
   576 
       
   577 			event.data.splice( event.data.length - 1, 0, captionField );
       
   578 		});
       
   579 
       
   580 		// Fix caption parent width for images added from URL
       
   581 		editor.on( 'wpNewImageRefresh', function( event ) {
       
   582 			var parent, captionWidth;
       
   583 
       
   584 			if ( parent = dom.getParent( event.node, 'dl.wp-caption' ) ) {
       
   585 				if ( ! parent.style.width ) {
       
   586 					captionWidth = parseInt( event.node.clientWidth, 10 ) + 10;
       
   587 					captionWidth = captionWidth ? captionWidth + 'px' : '50%';
       
   588 					dom.setStyle( parent, 'width', captionWidth );
       
   589 				}
       
   590 			}
       
   591 		});
       
   592 
       
   593 		editor.on( 'wpImageFormSubmit', function( event ) {
       
   594 			var data = event.imgData.data,
       
   595 				imgNode = event.imgData.node,
       
   596 				caption = event.imgData.caption,
       
   597 				captionId = '',
       
   598 				captionAlign = '',
       
   599 				captionWidth = '',
       
   600 				wrap, parent, node, html, imgId;
       
   601 
       
   602 			// Temp image id so we can find the node later
       
   603 			data.id = '__wp-temp-img-id';
       
   604 			// Cancel the original callback
       
   605 			event.imgData.cancel = true;
       
   606 
       
   607 			if ( ! data.style ) {
       
   608 				data.style = null;
       
   609 			}
       
   610 
       
   611 			if ( ! data.src ) {
       
   612 				// Delete the image and the caption
       
   613 				if ( imgNode ) {
       
   614 					if ( wrap = dom.getParent( imgNode, 'div.mceTemp' ) ) {
       
   615 						dom.remove( wrap );
       
   616 					} else if ( imgNode.parentNode.nodeName === 'A' ) {
       
   617 						dom.remove( imgNode.parentNode );
       
   618 					} else {
       
   619 						dom.remove( imgNode );
       
   620 					}
       
   621 
       
   622 					editor.nodeChanged();
       
   623 				}
       
   624 				return;
       
   625 			}
       
   626 
       
   627 			if ( caption ) {
       
   628 				caption = caption.replace( /\r\n|\r/g, '\n' ).replace( /<\/?[a-zA-Z0-9]+( [^<>]+)?>/g, function( a ) {
       
   629 					// No line breaks inside HTML tags
       
   630 					return a.replace( /[\r\n\t]+/, ' ' );
       
   631 				});
       
   632 
       
   633 				// Convert remaining line breaks to <br>
       
   634 				caption = caption.replace( /(<br[^>]*>)\s*\n\s*/g, '$1' ).replace( /\s*\n\s*/g, '<br />' );
       
   635 				caption = verifyHTML( caption );
       
   636 			}
       
   637 
       
   638 			if ( ! imgNode ) {
       
   639 				// New image inserted
       
   640 				html = dom.createHTML( 'img', data );
       
   641 
       
   642 				if ( caption ) {
       
   643 					node = editor.selection.getNode();
       
   644 
       
   645 					if ( data.width ) {
       
   646 						captionWidth = parseInt( data.width, 10 );
       
   647 
       
   648 						if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
       
   649 							captionWidth += 10;
       
   650 						}
       
   651 
       
   652 						captionWidth = ' style="width: ' + captionWidth + 'px"';
       
   653 					}
       
   654 
       
   655 					html = '<dl class="wp-caption alignnone"' + captionWidth + '>' +
       
   656 						'<dt class="wp-caption-dt">'+ html +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl>';
       
   657 
       
   658 					if ( node.nodeName === 'P' ) {
       
   659 						parent = node;
       
   660 					} else {
       
   661 						parent = dom.getParent( node, 'p' );
       
   662 					}
       
   663 
       
   664 					if ( parent && parent.nodeName === 'P' ) {
       
   665 						wrap = dom.create( 'div', { 'class': 'mceTemp' }, html );
       
   666 						parent.parentNode.insertBefore( wrap, parent );
       
   667 						editor.selection.select( wrap );
       
   668 						editor.nodeChanged();
       
   669 
       
   670 						if ( dom.isEmpty( parent ) ) {
       
   671 							dom.remove( parent );
       
   672 						}
       
   673 					} else {
       
   674 						editor.selection.setContent( '<div class="mceTemp">' + html + '</div>' );
       
   675 					}
       
   676 				} else {
       
   677 					editor.selection.setContent( html );
       
   678 				}
       
   679 			} else {
       
   680 				// Edit existing image
       
   681 
       
   682 				// Store the original image id if any
       
   683 				imgId = imgNode.id || null;
       
   684 				// Update the image node
       
   685 				dom.setAttribs( imgNode, data );
       
   686 				wrap = dom.getParent( imgNode, 'dl.wp-caption' );
       
   687 
       
   688 				if ( caption ) {
       
   689 					if ( wrap ) {
       
   690 						if ( parent = dom.select( 'dd.wp-caption-dd', wrap )[0] ) {
       
   691 							parent.innerHTML = caption;
       
   692 						}
       
   693 					} else {
       
   694 						if ( imgNode.className ) {
       
   695 							captionId = imgNode.className.match( /wp-image-([0-9]+)/ );
       
   696 							captionAlign = imgNode.className.match( /align(left|right|center|none)/ );
       
   697 						}
       
   698 
       
   699 						if ( captionAlign ) {
       
   700 							captionAlign = captionAlign[0];
       
   701 							imgNode.className = imgNode.className.replace( /align(left|right|center|none)/g, '' );
       
   702 						} else {
       
   703 							captionAlign = 'alignnone';
       
   704 						}
       
   705 
       
   706 						captionAlign = ' class="wp-caption ' + captionAlign + '"';
       
   707 
       
   708 						if ( captionId ) {
       
   709 							captionId = ' id="attachment_' + captionId[1] + '"';
       
   710 						}
       
   711 
       
   712 						captionWidth = data.width || imgNode.clientWidth;
       
   713 
       
   714 						if ( captionWidth ) {
       
   715 							captionWidth = parseInt( captionWidth, 10 );
       
   716 
       
   717 							if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
       
   718 								captionWidth += 10;
       
   719 							}
       
   720 
       
   721 							captionWidth = ' style="width: '+ captionWidth +'px"';
       
   722 						}
       
   723 
       
   724 						if ( imgNode.parentNode && imgNode.parentNode.nodeName === 'A' ) {
       
   725 							node = imgNode.parentNode;
       
   726 						} else {
       
   727 							node = imgNode;
       
   728 						}
       
   729 
       
   730 						html = '<dl ' + captionId + captionAlign + captionWidth + '>' +
       
   731 							'<dt class="wp-caption-dt"></dt><dd class="wp-caption-dd">'+ caption +'</dd></dl>';
       
   732 
       
   733 						wrap = dom.create( 'div', { 'class': 'mceTemp' }, html );
       
   734 
       
   735 						if ( parent = dom.getParent( node, 'p' ) ) {
       
   736 							parent.parentNode.insertBefore( wrap, parent );
       
   737 						} else {
       
   738 							node.parentNode.insertBefore( wrap, node );
       
   739 						}
       
   740 
       
   741 						editor.$( wrap ).find( 'dt.wp-caption-dt' ).append( node );
       
   742 
       
   743 						if ( parent && dom.isEmpty( parent ) ) {
       
   744 							dom.remove( parent );
       
   745 						}
       
   746 					}
       
   747 				} else {
       
   748 					if ( wrap ) {
       
   749 						// Remove the caption wrapper and place the image in new paragraph
       
   750 						if ( imgNode.parentNode.nodeName === 'A' ) {
       
   751 							html = dom.getOuterHTML( imgNode.parentNode );
       
   752 						} else {
       
   753 							html = dom.getOuterHTML( imgNode );
       
   754 						}
       
   755 
       
   756 						parent = dom.create( 'p', {}, html );
       
   757 						dom.insertAfter( parent, wrap.parentNode );
       
   758 						editor.selection.select( parent );
       
   759 						editor.nodeChanged();
       
   760 						dom.remove( wrap.parentNode );
       
   761 					}
       
   762 				}
       
   763 			}
       
   764 
       
   765 			imgNode = dom.get('__wp-temp-img-id');
       
   766 			dom.setAttrib( imgNode, 'id', imgId );
       
   767 			event.imgData.node = imgNode;
       
   768 		});
       
   769 
       
   770 		editor.on( 'wpLoadImageData', function( event ) {
       
   771 			var parent,
       
   772 				data = event.imgData.data,
       
   773 				imgNode = event.imgData.node;
       
   774 
       
   775 			if ( parent = dom.getParent( imgNode, 'dl.wp-caption' ) ) {
       
   776 				parent = dom.select( 'dd.wp-caption-dd', parent )[0];
       
   777 
       
   778 				if ( parent ) {
       
   779 					data.caption = editor.serializer.serialize( parent )
       
   780 						.replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' );
       
   781 				}
       
   782 			}
       
   783 		});
       
   784 
       
   785 		dom.bind( editor.getDoc(), 'dragstart', function( event ) {
       
   786 			var node = editor.selection.getNode();
       
   787 
       
   788 			// Prevent dragging images out of the caption elements
       
   789 			if ( node.nodeName === 'IMG' && dom.getParent( node, '.wp-caption' ) ) {
       
   790 				event.preventDefault();
       
   791 			}
       
   792 		});
       
   793 
   589 
   794 		// Prevent IE11 from making dl.wp-caption resizable
   590 		// Prevent IE11 from making dl.wp-caption resizable
   795 		if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) {
   591 		if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) {
   796 			// The 'mscontrolselect' event is supported only in IE11+
   592 			// The 'mscontrolselect' event is supported only in IE11+
   797 			dom.bind( editor.getBody(), 'mscontrolselect', function( event ) {
   593 			dom.bind( editor.getBody(), 'mscontrolselect', function( event ) {
   830 						dom.setStyle( parent, 'width', width + 'px' );
   626 						dom.setStyle( parent, 'width', width + 'px' );
   831 					}
   627 					}
   832 				}
   628 				}
   833 			});
   629 			});
   834 		}
   630 		}
   835     });
   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 	});
   836 
   664 
   837 	editor.on( 'BeforeExecCommand', function( event ) {
   665 	editor.on( 'BeforeExecCommand', function( event ) {
   838 		var node, p, DL, align, replacement,
   666 		var node, p, DL, align, replacement, captionParent,
   839 			cmd = event.command,
   667 			cmd = event.command,
   840 			dom = editor.dom;
   668 			dom = editor.dom;
   841 
   669 
   842 		if ( cmd === 'mceInsertContent' ) {
   670 		if ( cmd === 'mceInsertContent' || cmd === 'Indent' || cmd === 'Outdent' ) {
   843 			// When inserting content, if the caret is inside a caption create new paragraph under
   671 			node = editor.selection.getNode();
   844 			// and move the caret there
   672 			captionParent = dom.getParent( node, 'div.mceTemp' );
   845 			if ( node = dom.getParent( editor.selection.getNode(), 'div.mceTemp' ) ) {
   673 
   846 				p = dom.create( 'p' );
   674 			if ( captionParent ) {
   847 				dom.insertAfter( p, node );
   675 				if ( cmd === 'mceInsertContent' ) {
   848 				editor.selection.setCursorLocation( p, 0 );
   676 					if ( pasteInCaption ) {
   849 				editor.nodeChanged();
   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 				}
   850 			}
   706 			}
   851 		} else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' || cmd === 'wpAlignNone' ) {
   707 		} else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' || cmd === 'wpAlignNone' ) {
   852 			node = editor.selection.getNode();
   708 			node = editor.selection.getNode();
   853 			align = 'align' + cmd.slice( 7 ).toLowerCase();
   709 			align = 'align' + cmd.slice( 7 ).toLowerCase();
   854 			DL = editor.dom.getParent( node, '.wp-caption' );
   710 			DL = editor.dom.getParent( node, '.wp-caption' );
   863 				replacement = ' alignnone';
   719 				replacement = ' alignnone';
   864 			} else {
   720 			} else {
   865 				replacement = ' ' + align;
   721 				replacement = ' ' + align;
   866 			}
   722 			}
   867 
   723 
   868 			node.className = node.className.replace( / ?align(left|center|right|none)/g, '' ) + replacement;
   724 			node.className = trim( node.className.replace( / ?align(left|center|right|none)/g, '' ) + replacement );
   869 
   725 
   870 			editor.nodeChanged();
   726 			editor.nodeChanged();
   871 			event.preventDefault();
   727 			event.preventDefault();
   872 
   728 
   873 			if ( toolbar ) {
   729 			if ( toolbar ) {
   950 
   806 
   951 	editor.wpGetImgCaption = function( content ) {
   807 	editor.wpGetImgCaption = function( content ) {
   952 		return getShortcode( content );
   808 		return getShortcode( content );
   953 	};
   809 	};
   954 
   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 
   955 	editor.on( 'BeforeSetContent', function( event ) {
   817 	editor.on( 'BeforeSetContent', function( event ) {
   956 		if ( event.format !== 'raw' ) {
   818 		if ( event.format !== 'raw' ) {
   957 			event.content = editor.wpSetImgCaption( event.content );
   819 			event.content = editor.wpSetImgCaption( event.content );
   958 		}
   820 		}
   959 	});
   821 	});
   961 	editor.on( 'PostProcess', function( event ) {
   823 	editor.on( 'PostProcess', function( event ) {
   962 		if ( event.get ) {
   824 		if ( event.get ) {
   963 			event.content = editor.wpGetImgCaption( event.content );
   825 			event.content = editor.wpGetImgCaption( event.content );
   964 		}
   826 		}
   965 	});
   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 	} )();
   966 
   867 
   967 	// Add to editor.wp
   868 	// Add to editor.wp
   968 	editor.wp = editor.wp || {};
   869 	editor.wp = editor.wp || {};
   969 	editor.wp.isPlaceholder = isPlaceholder;
   870 	editor.wp.isPlaceholder = isPlaceholder;
   970 
   871