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