]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/tinymce/plugins/wpeditimage/plugin.js
WordPress 4.0.1
[autoinstalls/wordpress.git] / wp-includes / js / tinymce / plugins / wpeditimage / plugin.js
1 /* global tinymce */
2 tinymce.PluginManager.add( 'wpeditimage', function( editor ) {
3         var serializer,
4                 toolbarActive = false,
5                 editingImage = false;
6
7         function parseShortcode( content ) {
8                 return content.replace( /(?:<p>)?\[(?:wp_)?caption([^\]]+)\]([\s\S]+?)\[\/(?:wp_)?caption\](?:<\/p>)?/g, function( a, b, c ) {
9                         var id, align, classes, caption, img, width,
10                                 trim = tinymce.trim;
11
12                         id = b.match( /id=['"]([^'"]*)['"] ?/ );
13                         if ( id ) {
14                                 b = b.replace( id[0], '' );
15                         }
16
17                         align = b.match( /align=['"]([^'"]*)['"] ?/ );
18                         if ( align ) {
19                                 b = b.replace( align[0], '' );
20                         }
21
22                         classes = b.match( /class=['"]([^'"]*)['"] ?/ );
23                         if ( classes ) {
24                                 b = b.replace( classes[0], '' );
25                         }
26
27                         width = b.match( /width=['"]([0-9]*)['"] ?/ );
28                         if ( width ) {
29                                 b = b.replace( width[0], '' );
30                         }
31
32                         c = trim( c );
33                         img = c.match( /((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)([\s\S]*)/i );
34
35                         if ( img && img[2] ) {
36                                 caption = trim( img[2] );
37                                 img = trim( img[1] );
38                         } else {
39                                 // old captions shortcode style
40                                 caption = trim( b ).replace( /caption=['"]/, '' ).replace( /['"]$/, '' );
41                                 img = c;
42                         }
43
44                         id = ( id && id[1] ) ? id[1].replace( /[<>&]+/g,  '' ) : '';
45                         align = ( align && align[1] ) ? align[1] : 'alignnone';
46                         classes = ( classes && classes[1] ) ? ' ' + classes[1].replace( /[<>&]+/g,  '' ) : '';
47
48                         if ( ! width && img ) {
49                                 width = img.match( /width=['"]([0-9]*)['"]/ );
50                         }
51
52                         if ( width && width[1] ) {
53                                 width = width[1];
54                         }
55
56                         if ( ! width || ! caption ) {
57                                 return c;
58                         }
59
60                         width = parseInt( width, 10 );
61                         if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
62                                 width += 10;
63                         }
64
65                         return '<div class="mceTemp"><dl id="' + id + '" class="wp-caption ' + align + classes + '" style="width: ' + width + 'px">' +
66                                 '<dt class="wp-caption-dt">'+ img +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl></div>';
67                 });
68         }
69
70         function getShortcode( content ) {
71                 return content.replace( /<div (?:id="attachment_|class="mceTemp)[^>]*>([\s\S]+?)<\/div>/g, function( a, b ) {
72                         var out = '';
73
74                         if ( b.indexOf('<img ') === -1 ) {
75                                 // Broken caption. The user managed to drag the image out?
76                                 // Try to return the caption text as a paragraph.
77                                 out = b.match( /<dd [^>]+>([\s\S]+?)<\/dd>/i );
78
79                                 if ( out && out[1] ) {
80                                         return '<p>' + out[1] + '</p>';
81                                 }
82
83                                 return '';
84                         }
85
86                         out = b.replace( /\s*<dl ([^>]+)>\s*<dt [^>]+>([\s\S]+?)<\/dt>\s*<dd [^>]+>([\s\S]*?)<\/dd>\s*<\/dl>\s*/gi, function( a, b, c, caption ) {
87                                 var id, classes, align, width;
88
89                                 width = c.match( /width="([0-9]*)"/ );
90                                 width = ( width && width[1] ) ? width[1] : '';
91
92                                 if ( ! width || ! caption ) {
93                                         return c;
94                                 }
95
96                                 id = b.match( /id="([^"]*)"/ );
97                                 id = ( id && id[1] ) ? id[1] : '';
98
99                                 classes = b.match( /class="([^"]*)"/ );
100                                 classes = ( classes && classes[1] ) ? classes[1] : '';
101
102                                 align = classes.match( /align[a-z]+/i ) || 'alignnone';
103                                 classes = classes.replace( /wp-caption ?|align[a-z]+ ?/gi, '' );
104
105                                 if ( classes ) {
106                                         classes = ' class="' + classes + '"';
107                                 }
108
109                                 caption = caption.replace( /\r\n|\r/g, '\n' ).replace( /<[a-zA-Z0-9]+( [^<>]+)?>/g, function( a ) {
110                                         // no line breaks inside HTML tags
111                                         return a.replace( /[\r\n\t]+/, ' ' );
112                                 });
113
114                                 // convert remaining line breaks to <br>
115                                 caption = caption.replace( /\s*\n\s*/g, '<br />' );
116
117                                 return '[caption id="' + id + '" align="' + align + '" width="' + width + '"' + classes + ']' + c + ' ' + caption + '[/caption]';
118                         });
119
120                         if ( out.indexOf('[caption') === -1 ) {
121                                 // the caption html seems broken, try to find the image that may be wrapped in a link
122                                 // and may be followed by <p> with the caption text.
123                                 out = b.replace( /[\s\S]*?((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)(<p>[\s\S]*<\/p>)?[\s\S]*/gi, '<p>$1</p>$2' );
124                         }
125
126                         return out;
127                 });
128         }
129
130         function extractImageData( imageNode ) {
131                 var classes, extraClasses, metadata, captionBlock, caption, link, width, height,
132                         captionClassName = [],
133                         dom = editor.dom,
134                         isIntRegExp = /^\d+$/;
135
136                 // default attributes
137                 metadata = {
138                         attachment_id: false,
139                         size: 'custom',
140                         caption: '',
141                         align: 'none',
142                         extraClasses: '',
143                         link: false,
144                         linkUrl: '',
145                         linkClassName: '',
146                         linkTargetBlank: false,
147                         linkRel: '',
148                         title: ''
149                 };
150
151                 metadata.url = dom.getAttrib( imageNode, 'src' );
152                 metadata.alt = dom.getAttrib( imageNode, 'alt' );
153                 metadata.title = dom.getAttrib( imageNode, 'title' );
154
155                 width = dom.getAttrib( imageNode, 'width' );
156                 height = dom.getAttrib( imageNode, 'height' );
157
158                 if ( ! isIntRegExp.test( width ) || parseInt( width, 10 ) < 1 ) {
159                         width = imageNode.naturalWidth || imageNode.width;
160                 }
161
162                 if ( ! isIntRegExp.test( height ) || parseInt( height, 10 ) < 1 ) {
163                         height = imageNode.naturalHeight || imageNode.height;
164                 }
165
166                 metadata.customWidth = metadata.width = width;
167                 metadata.customHeight = metadata.height = height;
168
169                 classes = tinymce.explode( imageNode.className, ' ' );
170                 extraClasses = [];
171
172                 tinymce.each( classes, function( name ) {
173
174                         if ( /^wp-image/.test( name ) ) {
175                                 metadata.attachment_id = parseInt( name.replace( 'wp-image-', '' ), 10 );
176                         } else if ( /^align/.test( name ) ) {
177                                 metadata.align = name.replace( 'align', '' );
178                         } else if ( /^size/.test( name ) ) {
179                                 metadata.size = name.replace( 'size-', '' );
180                         } else {
181                                 extraClasses.push( name );
182                         }
183
184                 } );
185
186                 metadata.extraClasses = extraClasses.join( ' ' );
187
188                 // Extract caption
189                 captionBlock = dom.getParents( imageNode, '.wp-caption' );
190
191                 if ( captionBlock.length ) {
192                         captionBlock = captionBlock[0];
193
194                         classes = captionBlock.className.split( ' ' );
195                         tinymce.each( classes, function( name ) {
196                                 if ( /^align/.test( name ) ) {
197                                         metadata.align = name.replace( 'align', '' );
198                                 } else if ( name && name !== 'wp-caption' ) {
199                                         captionClassName.push( name );
200                                 }
201                         } );
202
203                         metadata.captionClassName = captionClassName.join( ' ' );
204
205                         caption = dom.select( 'dd.wp-caption-dd', captionBlock );
206                         if ( caption.length ) {
207                                 caption = caption[0];
208
209                                 metadata.caption = editor.serializer.serialize( caption )
210                                         .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' );
211                         }
212                 }
213
214                 // Extract linkTo
215                 if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' ) {
216                         link = imageNode.parentNode;
217                         metadata.linkUrl = dom.getAttrib( link, 'href' );
218                         metadata.linkTargetBlank = dom.getAttrib( link, 'target' ) === '_blank' ? true : false;
219                         metadata.linkRel = dom.getAttrib( link, 'rel' );
220                         metadata.linkClassName = link.className;
221                 }
222
223                 return metadata;
224         }
225
226         function hasTextContent( node ) {
227                 return node && !! ( node.textContent || node.innerText );
228         }
229
230         // Verify HTML in captions
231         function verifyHTML( caption ) {
232                 if ( ! caption || ( caption.indexOf( '<' ) === -1 && caption.indexOf( '>' ) === -1 ) ) {
233                         return caption;
234                 }
235
236                 if ( ! serializer ) {
237                         serializer = new tinymce.html.Serializer( {}, editor.schema );
238                 }
239
240                 return serializer.serialize( editor.parser.parse( caption, { forced_root_block: false } ) );
241         }
242
243         function updateImage( imageNode, imageData ) {
244                 var classes, className, node, html, parent, wrap, linkNode,
245                         captionNode, dd, dl, id, attrs, linkAttrs, width, height, align,
246                         dom = editor.dom;
247
248                 classes = tinymce.explode( imageData.extraClasses, ' ' );
249
250                 if ( ! classes ) {
251                         classes = [];
252                 }
253
254                 if ( ! imageData.caption ) {
255                         classes.push( 'align' + imageData.align );
256                 }
257
258                 if ( imageData.attachment_id ) {
259                         classes.push( 'wp-image-' + imageData.attachment_id );
260                         if ( imageData.size && imageData.size !== 'custom' ) {
261                                 classes.push( 'size-' + imageData.size );
262                         }
263                 }
264
265                 width = imageData.width;
266                 height = imageData.height;
267
268                 if ( imageData.size === 'custom' ) {
269                         width = imageData.customWidth;
270                         height = imageData.customHeight;
271                 }
272
273                 attrs = {
274                         src: imageData.url,
275                         width: width || null,
276                         height: height || null,
277                         alt: imageData.alt,
278                         title: imageData.title || null,
279                         'class': classes.join( ' ' ) || null
280                 };
281
282                 dom.setAttribs( imageNode, attrs );
283
284                 linkAttrs = {
285                         href: imageData.linkUrl,
286                         rel: imageData.linkRel || null,
287                         target: imageData.linkTargetBlank ? '_blank': null,
288                         'class': imageData.linkClassName || null
289                 };
290
291                 if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) {
292                         // Update or remove an existing link wrapped around the image
293                         if ( imageData.linkUrl ) {
294                                 dom.setAttribs( imageNode.parentNode, linkAttrs );
295                         } else {
296                                 dom.remove( imageNode.parentNode, true );
297                         }
298                 } else if ( imageData.linkUrl ) {
299                         if ( linkNode = dom.getParent( imageNode, 'a' ) ) {
300                                 // The image is inside a link together with other nodes,
301                                 // or is nested in another node, move it out
302                                 dom.insertAfter( imageNode, linkNode );
303                         }
304
305                         // Add link wrapped around the image
306                         linkNode = dom.create( 'a', linkAttrs );
307                         imageNode.parentNode.insertBefore( linkNode, imageNode );
308                         linkNode.appendChild( imageNode );
309                 }
310
311                 captionNode = editor.dom.getParent( imageNode, '.mceTemp' );
312
313                 if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) {
314                         node = imageNode.parentNode;
315                 } else {
316                         node = imageNode;
317                 }
318
319                 if ( imageData.caption ) {
320                         imageData.caption = verifyHTML( imageData.caption );
321
322                         id = imageData.attachment_id ? 'attachment_' + imageData.attachment_id : null;
323                         align = 'align' + ( imageData.align || 'none' );
324                         className = 'wp-caption ' + align;
325
326                         if ( imageData.captionClassName ) {
327                                 className += ' ' + imageData.captionClassName.replace( /[<>&]+/g,  '' );
328                         }
329
330                         if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
331                                 width = parseInt( width, 10 );
332                                 width += 10;
333                         }
334
335                         if ( captionNode ) {
336                                 dl = dom.select( 'dl.wp-caption', captionNode );
337
338                                 if ( dl.length ) {
339                                         dom.setAttribs( dl, {
340                                                 id: id,
341                                                 'class': className,
342                                                 style: 'width: ' + width + 'px'
343                                         } );
344                                 }
345
346                                 dd = dom.select( '.wp-caption-dd', captionNode );
347
348                                 if ( dd.length ) {
349                                         dom.setHTML( dd[0], imageData.caption );
350                                 }
351
352                         } else {
353                                 id = id ? 'id="'+ id +'" ' : '';
354
355                                 // should create a new function for generating the caption markup
356                                 html =  '<dl ' + id + 'class="' + className +'" style="width: '+ width +'px">' +
357                                         '<dt class="wp-caption-dt">' + dom.getOuterHTML( node ) + '</dt><dd class="wp-caption-dd">'+ imageData.caption +'</dd></dl>';
358
359                                 if ( parent = dom.getParent( node, 'p' ) ) {
360                                         wrap = dom.create( 'div', { 'class': 'mceTemp' }, html );
361                                         parent.parentNode.insertBefore( wrap, parent );
362                                         dom.remove( node );
363
364                                         if ( dom.isEmpty( parent ) ) {
365                                                 dom.remove( parent );
366                                         }
367                                 } else {
368                                         dom.setOuterHTML( node, '<div class="mceTemp">' + html + '</div>' );
369                                 }
370                         }
371                 } else if ( captionNode ) {
372                         // Remove the caption wrapper and place the image in new paragraph
373                         parent = dom.create( 'p' );
374                         captionNode.parentNode.insertBefore( parent, captionNode );
375                         parent.appendChild( node );
376                         dom.remove( captionNode );
377                 }
378
379                 if ( wp.media.events ) {
380                         wp.media.events.trigger( 'editor:image-update', {
381                                 editor: editor,
382                                 metadata: imageData,
383                                 image: imageNode
384                         } );
385                 }
386
387                 editor.nodeChanged();
388                 // Refresh the toolbar
389                 addToolbar( imageNode );
390         }
391
392         function editImage( img ) {
393                 var frame, callback, metadata;
394
395                 if ( typeof wp === 'undefined' || ! wp.media ) {
396                         editor.execCommand( 'mceImage' );
397                         return;
398                 }
399
400                 metadata = extractImageData( img );
401
402                 // Manipulate the metadata by reference that is fed into the PostImage model used in the media modal
403                 wp.media.events.trigger( 'editor:image-edit', {
404                         editor: editor,
405                         metadata: metadata,
406                         image: img
407                 } );
408
409                 frame = wp.media({
410                         frame: 'image',
411                         state: 'image-details',
412                         metadata: metadata
413                 } );
414
415                 wp.media.events.trigger( 'editor:frame-create', { frame: frame } );
416
417                 callback = function( imageData ) {
418                         editor.focus();
419                         editor.undoManager.transact( function() {
420                                 updateImage( img, imageData );
421                         } );
422                         frame.detach();
423                 };
424
425                 frame.state('image-details').on( 'update', callback );
426                 frame.state('replace-image').on( 'replace', callback );
427                 frame.on( 'close', function() {
428                         editor.focus();
429                         frame.detach();
430                         editingImage = false;
431                 });
432
433                 frame.open();
434         }
435
436         function removeImage( node ) {
437                 var wrap;
438
439                 if ( node.nodeName === 'DIV' && editor.dom.hasClass( node, 'mceTemp' ) ) {
440                         wrap = node;
441                 } else if ( node.nodeName === 'IMG' || node.nodeName === 'DT' || node.nodeName === 'A' ) {
442                         wrap = editor.dom.getParent( node, 'div.mceTemp' );
443                 }
444
445                 if ( wrap ) {
446                         if ( wrap.nextSibling ) {
447                                 editor.selection.select( wrap.nextSibling );
448                         } else if ( wrap.previousSibling ) {
449                                 editor.selection.select( wrap.previousSibling );
450                         } else {
451                                 editor.selection.select( wrap.parentNode );
452                         }
453
454                         editor.selection.collapse( true );
455                         editor.dom.remove( wrap );
456                 } else {
457                         editor.dom.remove( node );
458                 }
459
460                 removeToolbar();
461                 editor.nodeChanged();
462                 editor.undoManager.add();
463         }
464
465         function addToolbar( node ) {
466                 var rectangle, toolbarHtml, toolbar, left,
467                         dom = editor.dom;
468
469                 removeToolbar();
470
471                 // Don't add to placeholders
472                 if ( ! node || node.nodeName !== 'IMG' || isPlaceholder( node ) ) {
473                         return;
474                 }
475
476                 dom.setAttrib( node, 'data-wp-imgselect', 1 );
477                 rectangle = dom.getRect( node );
478
479                 toolbarHtml = '<i class="dashicons dashicons-edit edit" data-mce-bogus="all"></i>' +
480                         '<i class="dashicons dashicons-no-alt remove" data-mce-bogus="all"></i>';
481
482                 toolbar = dom.create( 'p', {
483                         'id': 'wp-image-toolbar',
484                         'data-mce-bogus': 'all',
485                         'contenteditable': false
486                 }, toolbarHtml );
487
488                 if ( editor.rtl ) {
489                         left = rectangle.x + rectangle.w - 82;
490                 } else {
491                         left = rectangle.x;
492                 }
493
494                 editor.getBody().appendChild( toolbar );
495                 dom.setStyles( toolbar, {
496                         top: rectangle.y,
497                         left: left
498                 });
499
500                 toolbarActive = true;
501         }
502
503         function removeToolbar() {
504                 var toolbar = editor.dom.get( 'wp-image-toolbar' );
505
506                 if ( toolbar ) {
507                         editor.dom.remove( toolbar );
508                 }
509
510                 editor.dom.setAttrib( editor.dom.select( 'img[data-wp-imgselect]' ), 'data-wp-imgselect', null );
511
512                 editingImage = false;
513                 toolbarActive = false;
514         }
515
516         function isPlaceholder( node ) {
517                 var dom = editor.dom;
518
519                 if ( dom.hasClass( node, 'mceItem' ) || dom.getAttrib( node, 'data-mce-placeholder' ) ||
520                         dom.getAttrib( node, 'data-mce-object' ) ) {
521
522                         return true;
523                 }
524
525                 return false;
526         }
527
528         function isToolbarButton( node ) {
529                 return ( node && node.nodeName === 'I' && node.parentNode.id === 'wp-image-toolbar' );
530         }
531
532         function edit( event ) {
533                 var image,
534                         node = event.target,
535                         dom = editor.dom;
536
537                 // Don't trigger on right-click
538                 if ( event.button && event.button > 1 ) {
539                         return;
540                 }
541
542                 if ( isToolbarButton( node ) ) {
543                         image = dom.select( 'img[data-wp-imgselect]' )[0];
544
545                         if ( image ) {
546                                 editor.selection.select( image );
547
548                                 if ( dom.hasClass( node, 'remove' ) ) {
549                                         removeImage( image );
550                                 } else if ( dom.hasClass( node, 'edit' ) ) {
551                                         if ( ! editingImage ) {
552                                                 editImage( image );
553                                                 editingImage = true;
554                                         }
555                                 }
556                         }
557
558                         event.preventDefault();
559                 } else if ( node.nodeName === 'IMG' && ! editor.dom.getAttrib( node, 'data-wp-imgselect' ) && ! isPlaceholder( node ) ) {
560                         addToolbar( node );
561                 } else if ( node.nodeName !== 'IMG' ) {
562                         removeToolbar();
563                 }
564         }
565
566         if ( 'ontouchend' in document ) {
567                 editor.on( 'click', function( event ) {
568                         var target = event.target;
569
570                         if ( editingImage && target.nodeName === 'IMG' ) {
571                                 event.preventDefault();
572                         }
573
574                         if ( isToolbarButton( target ) ) {
575                                 event.preventDefault();
576                                 event.stopPropagation();
577                         }
578                 });
579         }
580
581         editor.on( 'mouseup touchend', edit );
582
583         editor.on( 'init', function() {
584                 var dom = editor.dom,
585                         captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions';
586
587                 dom.addClass( editor.getBody(), captionClass );
588
589                 // Add caption field to the default image dialog
590                 editor.on( 'wpLoadImageForm', function( event ) {
591                         if ( editor.getParam( 'wpeditimage_disable_captions' ) ) {
592                                 return;
593                         }
594
595                         var captionField = {
596                                 type: 'textbox',
597                                 flex: 1,
598                                 name: 'caption',
599                                 minHeight: 60,
600                                 multiline: true,
601                                 scroll: true,
602                                 label: 'Image caption'
603                         };
604
605                         event.data.splice( event.data.length - 1, 0, captionField );
606                 });
607
608                 // Fix caption parent width for images added from URL
609                 editor.on( 'wpNewImageRefresh', function( event ) {
610                         var parent, captionWidth;
611
612                         if ( parent = dom.getParent( event.node, 'dl.wp-caption' ) ) {
613                                 if ( ! parent.style.width ) {
614                                         captionWidth = parseInt( event.node.clientWidth, 10 ) + 10;
615                                         captionWidth = captionWidth ? captionWidth + 'px' : '50%';
616                                         dom.setStyle( parent, 'width', captionWidth );
617                                 }
618                         }
619                 });
620
621                 editor.on( 'wpImageFormSubmit', function( event ) {
622                         var data = event.imgData.data,
623                                 imgNode = event.imgData.node,
624                                 caption = event.imgData.caption,
625                                 captionId = '',
626                                 captionAlign = '',
627                                 captionWidth = '',
628                                 wrap, parent, node, html, imgId;
629
630                         // Temp image id so we can find the node later
631                         data.id = '__wp-temp-img-id';
632                         // Cancel the original callback
633                         event.imgData.cancel = true;
634
635                         if ( ! data.style ) {
636                                 data.style = null;
637                         }
638
639                         if ( ! data.src ) {
640                                 // Delete the image and the caption
641                                 if ( imgNode ) {
642                                         if ( wrap = dom.getParent( imgNode, 'div.mceTemp' ) ) {
643                                                 dom.remove( wrap );
644                                         } else if ( imgNode.parentNode.nodeName === 'A' ) {
645                                                 dom.remove( imgNode.parentNode );
646                                         } else {
647                                                 dom.remove( imgNode );
648                                         }
649
650                                         editor.nodeChanged();
651                                 }
652                                 return;
653                         }
654
655                         if ( caption ) {
656                                 caption = caption.replace( /\r\n|\r/g, '\n' ).replace( /<\/?[a-zA-Z0-9]+( [^<>]+)?>/g, function( a ) {
657                                         // No line breaks inside HTML tags
658                                         return a.replace( /[\r\n\t]+/, ' ' );
659                                 });
660
661                                 // Convert remaining line breaks to <br>
662                                 caption = caption.replace( /(<br[^>]*>)\s*\n\s*/g, '$1' ).replace( /\s*\n\s*/g, '<br />' );
663                                 caption = verifyHTML( caption );
664                         }
665
666                         if ( ! imgNode ) {
667                                 // New image inserted
668                                 html = dom.createHTML( 'img', data );
669
670                                 if ( caption ) {
671                                         node = editor.selection.getNode();
672
673                                         if ( data.width ) {
674                                                 captionWidth = parseInt( data.width, 10 );
675
676                                                 if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
677                                                         captionWidth += 10;
678                                                 }
679
680                                                 captionWidth = ' style="width: ' + captionWidth + 'px"';
681                                         }
682
683                                         html = '<dl class="wp-caption alignnone"' + captionWidth + '>' +
684                                                 '<dt class="wp-caption-dt">'+ html +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl>';
685
686                                         if ( node.nodeName === 'P' ) {
687                                                 parent = node;
688                                         } else {
689                                                 parent = dom.getParent( node, 'p' );
690                                         }
691
692                                         if ( parent && parent.nodeName === 'P' ) {
693                                                 wrap = dom.create( 'div', { 'class': 'mceTemp' }, html );
694                                                 parent.parentNode.insertBefore( wrap, parent );
695                                                 editor.selection.select( wrap );
696                                                 editor.nodeChanged();
697
698                                                 if ( dom.isEmpty( parent ) ) {
699                                                         dom.remove( parent );
700                                                 }
701                                         } else {
702                                                 editor.selection.setContent( '<div class="mceTemp">' + html + '</div>' );
703                                         }
704                                 } else {
705                                         editor.selection.setContent( html );
706                                 }
707                         } else {
708                                 // Edit existing image
709
710                                 // Store the original image id if any
711                                 imgId = imgNode.id || null;
712                                 // Update the image node
713                                 dom.setAttribs( imgNode, data );
714                                 wrap = dom.getParent( imgNode, 'dl.wp-caption' );
715
716                                 if ( caption ) {
717                                         if ( wrap ) {
718                                                 if ( parent = dom.select( 'dd.wp-caption-dd', wrap )[0] ) {
719                                                         parent.innerHTML = caption;
720                                                 }
721                                         } else {
722                                                 if ( imgNode.className ) {
723                                                         captionId = imgNode.className.match( /wp-image-([0-9]+)/ );
724                                                         captionAlign = imgNode.className.match( /align(left|right|center|none)/ );
725                                                 }
726
727                                                 if ( captionAlign ) {
728                                                         captionAlign = captionAlign[0];
729                                                         imgNode.className = imgNode.className.replace( /align(left|right|center|none)/g, '' );
730                                                 } else {
731                                                         captionAlign = 'alignnone';
732                                                 }
733
734                                                 captionAlign = ' class="wp-caption ' + captionAlign + '"';
735
736                                                 if ( captionId ) {
737                                                         captionId = ' id="attachment_' + captionId[1] + '"';
738                                                 }
739
740                                                 captionWidth = data.width || imgNode.clientWidth;
741
742                                                 if ( captionWidth ) {
743                                                         captionWidth = parseInt( captionWidth, 10 );
744
745                                                         if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
746                                                                 captionWidth += 10;
747                                                         }
748
749                                                         captionWidth = ' style="width: '+ captionWidth +'px"';
750                                                 }
751
752                                                 if ( imgNode.parentNode && imgNode.parentNode.nodeName === 'A' ) {
753                                                         html = dom.getOuterHTML( imgNode.parentNode );
754                                                         node = imgNode.parentNode;
755                                                 } else {
756                                                         html = dom.getOuterHTML( imgNode );
757                                                         node = imgNode;
758                                                 }
759
760                                                 html = '<dl ' + captionId + captionAlign + captionWidth + '>' +
761                                                         '<dt class="wp-caption-dt">'+ html +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl>';
762
763                                                 if ( parent = dom.getParent( imgNode, 'p' ) ) {
764                                                         wrap = dom.create( 'div', { 'class': 'mceTemp' }, html );
765                                                         dom.insertAfter( wrap, parent );
766                                                         editor.selection.select( wrap );
767                                                         editor.nodeChanged();
768
769                                                         // Delete the old image node
770                                                         dom.remove( node );
771
772                                                         if ( dom.isEmpty( parent ) ) {
773                                                                 dom.remove( parent );
774                                                         }
775                                                 } else {
776                                                         editor.selection.setContent( '<div class="mceTemp">' + html + '</div>' );
777                                                 }
778                                         }
779                                 } else {
780                                         if ( wrap ) {
781                                                 // Remove the caption wrapper and place the image in new paragraph
782                                                 if ( imgNode.parentNode.nodeName === 'A' ) {
783                                                         html = dom.getOuterHTML( imgNode.parentNode );
784                                                 } else {
785                                                         html = dom.getOuterHTML( imgNode );
786                                                 }
787
788                                                 parent = dom.create( 'p', {}, html );
789                                                 dom.insertAfter( parent, wrap.parentNode );
790                                                 editor.selection.select( parent );
791                                                 editor.nodeChanged();
792                                                 dom.remove( wrap.parentNode );
793                                         }
794                                 }
795                         }
796
797                         imgNode = dom.get('__wp-temp-img-id');
798                         dom.setAttrib( imgNode, 'id', imgId );
799                         event.imgData.node = imgNode;
800                 });
801
802                 editor.on( 'wpLoadImageData', function( event ) {
803                         var parent,
804                                 data = event.imgData.data,
805                                 imgNode = event.imgData.node;
806
807                         if ( parent = dom.getParent( imgNode, 'dl.wp-caption' ) ) {
808                                 parent = dom.select( 'dd.wp-caption-dd', parent )[0];
809
810                                 if ( parent ) {
811                                         data.caption = editor.serializer.serialize( parent )
812                                                 .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' );
813                                 }
814                         }
815                 });
816
817                 dom.bind( editor.getDoc(), 'dragstart', function( event ) {
818                         var node = editor.selection.getNode();
819
820                         // Prevent dragging images out of the caption elements
821                         if ( node.nodeName === 'IMG' && dom.getParent( node, '.wp-caption' ) ) {
822                                 event.preventDefault();
823                         }
824
825                         // Remove toolbar to avoid an orphaned toolbar when dragging an image to a new location
826                         removeToolbar();
827                 });
828
829                 // Prevent IE11 from making dl.wp-caption resizable
830                 if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) {
831                         // The 'mscontrolselect' event is supported only in IE11+
832                         dom.bind( editor.getBody(), 'mscontrolselect', function( event ) {
833                                 if ( event.target.nodeName === 'IMG' && dom.getParent( event.target, '.wp-caption' ) ) {
834                                         // Hide the thick border with resize handles around dl.wp-caption
835                                         editor.getBody().focus(); // :(
836                                 } else if ( event.target.nodeName === 'DL' && dom.hasClass( event.target, 'wp-caption' ) ) {
837                                         // Trigger the thick border with resize handles...
838                                         // This will make the caption text editable.
839                                         event.target.focus();
840                                 }
841                         });
842
843                         editor.on( 'click', function( event ) {
844                                 if ( event.target.nodeName === 'IMG' && dom.getAttrib( event.target, 'data-wp-imgselect' ) &&
845                                         dom.getParent( event.target, 'dl.wp-caption' ) ) {
846
847                                         editor.getBody().focus();
848                                 }
849                         });
850                 }
851         });
852
853         editor.on( 'ObjectResized', function( event ) {
854                 var node = event.target;
855
856                 if ( node.nodeName === 'IMG' ) {
857                         editor.undoManager.transact( function() {
858                                 var parent, width,
859                                         dom = editor.dom;
860
861                                 node.className = node.className.replace( /\bsize-[^ ]+/, '' );
862
863                                 if ( parent = dom.getParent( node, '.wp-caption' ) ) {
864                                         width = event.width || dom.getAttrib( node, 'width' );
865
866                                         if ( width ) {
867                                                 width = parseInt( width, 10 );
868
869                                                 if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) {
870                                                         width += 10;
871                                                 }
872
873                                                 dom.setStyle( parent, 'width', width + 'px' );
874                                         }
875                                 }
876                                 // refresh toolbar
877                                 addToolbar( node );
878                         });
879                 }
880     });
881
882         editor.on( 'BeforeExecCommand', function( event ) {
883                 var node, p, DL, align,
884                         cmd = event.command,
885                         dom = editor.dom;
886
887                 if ( cmd === 'mceInsertContent' ) {
888                         // When inserting content, if the caret is inside a caption create new paragraph under
889                         // and move the caret there
890                         if ( node = dom.getParent( editor.selection.getNode(), 'div.mceTemp' ) ) {
891                                 p = dom.create( 'p' );
892                                 dom.insertAfter( p, node );
893                                 editor.selection.setCursorLocation( p, 0 );
894                                 editor.nodeChanged();
895                         }
896                 } else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' ) {
897                         node = editor.selection.getNode();
898                         align = cmd.substr(7).toLowerCase();
899                         align = 'align' + align;
900                         DL = dom.getParent( node, 'dl.wp-caption' );
901
902                         removeToolbar();
903
904                         if ( DL ) {
905                                 // When inside an image caption, set the align* class on dl.wp-caption
906                                 if ( dom.hasClass( DL, align ) ) {
907                                         dom.removeClass( DL, align );
908                                         dom.addClass( DL, 'alignnone' );
909                                 } else {
910                                         DL.className = DL.className.replace( /align[^ ]+/g, '' );
911                                         dom.addClass( DL, align );
912                                 }
913
914                                 if ( node.nodeName === 'IMG' ) {
915                                         // Re-select the image to update resize handles, etc.
916                                         editor.nodeChanged();
917                                 }
918
919                                 event.preventDefault();
920                         }
921
922                         if ( node.nodeName === 'IMG' ) {
923                                 if ( dom.hasClass( node, align ) ) {
924                                         // The align class is being removed
925                                         dom.addClass( node, 'alignnone' );
926                                 } else {
927                                         dom.removeClass( node, 'alignnone' );
928                                 }
929                         }
930                 }
931         });
932
933         editor.on( 'keydown', function( event ) {
934                 var node, wrap, P, spacer,
935                         selection = editor.selection,
936                         keyCode = event.keyCode,
937                         dom = editor.dom,
938                         VK = tinymce.util.VK;
939
940                 if ( keyCode === VK.ENTER ) {
941                         // When pressing Enter inside a caption move the caret to a new parapraph under it
942                         node = selection.getNode();
943                         wrap = dom.getParent( node, 'div.mceTemp' );
944
945                         if ( wrap ) {
946                                 dom.events.cancel( event ); // Doesn't cancel all :(
947
948                                 // Remove any extra dt and dd cleated on pressing Enter...
949                                 tinymce.each( dom.select( 'dt, dd', wrap ), function( element ) {
950                                         if ( dom.isEmpty( element ) ) {
951                                                 dom.remove( element );
952                                         }
953                                 });
954
955                                 spacer = tinymce.Env.ie && tinymce.Env.ie < 11 ? '' : '<br data-mce-bogus="1" />';
956                                 P = dom.create( 'p', null, spacer );
957
958                                 if ( node.nodeName === 'DD' ) {
959                                         dom.insertAfter( P, wrap );
960                                 } else {
961                                         wrap.parentNode.insertBefore( P, wrap );
962                                 }
963
964                                 editor.nodeChanged();
965                                 selection.setCursorLocation( P, 0 );
966                         }
967                 } else if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
968                         node = selection.getNode();
969
970                         if ( node.nodeName === 'DIV' && dom.hasClass( node, 'mceTemp' ) ) {
971                                 wrap = node;
972                         } else if ( node.nodeName === 'IMG' || node.nodeName === 'DT' || node.nodeName === 'A' ) {
973                                 wrap = dom.getParent( node, 'div.mceTemp' );
974                         }
975
976                         if ( wrap ) {
977                                 dom.events.cancel( event );
978                                 removeImage( node );
979                                 return false;
980                         }
981
982                         removeToolbar();
983                 }
984
985                 // Most key presses will replace the image so we need to remove the toolbar
986                 if ( toolbarActive ) {
987                         if ( event.ctrlKey || event.metaKey || event.altKey || ( keyCode < 48 && keyCode !== VK.SPACEBAR ) ) {
988                                 return;
989                         }
990
991                         removeToolbar();
992                 }
993         });
994
995         editor.on( 'mousedown', function( event ) {
996                 if ( isToolbarButton( event.target ) ) {
997                         if ( tinymce.Env.ie ) {
998                                 // Stop IE > 8 from making the wrapper resizable on mousedown
999                                 event.preventDefault();
1000                         }
1001                 } else if ( event.target.nodeName !== 'IMG' ) {
1002                         removeToolbar();
1003                 }
1004         });
1005
1006         // Remove from undo levels
1007         editor.on( 'BeforeAddUndo', function( event ) {
1008                 event.level.content = event.level.content.replace( / data-wp-imgselect="1"/g, '' );
1009         });
1010
1011         // After undo/redo FF seems to set the image height very slowly when it is set to 'auto' in the CSS.
1012         // This causes image.getBoundingClientRect() to return wrong values and the resize handles are shown in wrong places.
1013         // Collapse the selection to remove the resize handles.
1014         if ( tinymce.Env.gecko ) {
1015                 editor.on( 'undo redo', function() {
1016                         if ( editor.selection.getNode().nodeName === 'IMG' ) {
1017                                 editor.selection.collapse();
1018                         }
1019                 });
1020         }
1021
1022         editor.on( 'cut wpview-selected', function() {
1023                 removeToolbar();
1024         });
1025
1026         editor.wpSetImgCaption = function( content ) {
1027                 return parseShortcode( content );
1028         };
1029
1030         editor.wpGetImgCaption = function( content ) {
1031                 return getShortcode( content );
1032         };
1033
1034         editor.on( 'BeforeSetContent', function( event ) {
1035                 if ( event.format !== 'raw' ) {
1036                         event.content = editor.wpSetImgCaption( event.content );
1037                 }
1038         });
1039
1040         editor.on( 'PostProcess', function( event ) {
1041                 if ( event.get ) {
1042                         event.content = editor.wpGetImgCaption( event.content );
1043                         event.content = event.content.replace( / data-wp-imgselect="1"/g, '' );
1044                 }
1045         });
1046
1047         return {
1048                 _do_shcode: parseShortcode,
1049                 _get_shcode: getShortcode
1050         };
1051 });