16 removeImage( editor.selection.getNode() ); |
16 removeImage( editor.selection.getNode() ); |
17 } |
17 } |
18 } ); |
18 } ); |
19 |
19 |
20 editor.addButton( 'wp_img_edit', { |
20 editor.addButton( 'wp_img_edit', { |
21 tooltip: 'Edit|button', // '|button' is not displayed, only used for context |
21 tooltip: 'Edit|button', // '|button' is not displayed, only used for context. |
22 icon: 'dashicon dashicons-edit', |
22 icon: 'dashicon dashicons-edit', |
23 onclick: function() { |
23 onclick: function() { |
24 editImage( editor.selection.getNode() ); |
24 editImage( editor.selection.getNode() ); |
25 } |
25 } |
26 } ); |
26 } ); |
144 |
144 |
145 if ( img && img[2] ) { |
145 if ( img && img[2] ) { |
146 caption = trim( img[2] ); |
146 caption = trim( img[2] ); |
147 img = trim( img[1] ); |
147 img = trim( img[1] ); |
148 } else { |
148 } else { |
149 // old captions shortcode style |
149 // Old captions shortcode style. |
150 caption = trim( b ).replace( /caption=['"]/, '' ).replace( /['"]$/, '' ); |
150 caption = trim( b ).replace( /caption=['"]/, '' ).replace( /['"]$/, '' ); |
151 img = c; |
151 img = c; |
152 } |
152 } |
153 |
153 |
154 id = ( id && id[1] ) ? id[1].replace( /[<>&]+/g, '' ) : ''; |
154 id = ( id && id[1] ) ? id[1].replace( /[<>&]+/g, '' ) : ''; |
212 if ( classes ) { |
212 if ( classes ) { |
213 classes = ' class="' + classes + '"'; |
213 classes = ' class="' + classes + '"'; |
214 } |
214 } |
215 |
215 |
216 caption = caption.replace( /\r\n|\r/g, '\n' ).replace( /<[a-zA-Z0-9]+( [^<>]+)?>/g, function( a ) { |
216 caption = caption.replace( /\r\n|\r/g, '\n' ).replace( /<[a-zA-Z0-9]+( [^<>]+)?>/g, function( a ) { |
217 // no line breaks inside HTML tags |
217 // No line breaks inside HTML tags. |
218 return a.replace( /[\r\n\t]+/, ' ' ); |
218 return a.replace( /[\r\n\t]+/, ' ' ); |
219 }); |
219 }); |
220 |
220 |
221 // convert remaining line breaks to <br> |
221 // Convert remaining line breaks to <br>. |
222 caption = caption.replace( /\s*\n\s*/g, '<br />' ); |
222 caption = caption.replace( /\s*\n\s*/g, '<br />' ); |
223 |
223 |
224 return '[caption id="' + id + '" align="' + align + '" width="' + width + '"' + classes + ']' + c + ' ' + caption + '[/caption]'; |
224 return '[caption id="' + id + '" align="' + align + '" width="' + width + '"' + classes + ']' + c + ' ' + caption + '[/caption]'; |
225 }); |
225 }); |
226 |
226 |
227 if ( out.indexOf('[caption') === -1 ) { |
227 if ( out.indexOf('[caption') === -1 ) { |
228 // the caption html seems broken, try to find the image that may be wrapped in a link |
228 // The caption html seems broken, try to find the image that may be wrapped in a link |
229 // and may be followed by <p> with the caption text. |
229 // and may be followed by <p> with the caption text. |
230 out = dl.replace( /[\s\S]*?((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)(<p>[\s\S]*<\/p>)?[\s\S]*/gi, '<p>$1</p>$2' ); |
230 out = dl.replace( /[\s\S]*?((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)(<p>[\s\S]*<\/p>)?[\s\S]*/gi, '<p>$1</p>$2' ); |
231 } |
231 } |
232 |
232 |
233 return out; |
233 return out; |
238 var classes, extraClasses, metadata, captionBlock, caption, link, width, height, |
238 var classes, extraClasses, metadata, captionBlock, caption, link, width, height, |
239 captionClassName = [], |
239 captionClassName = [], |
240 dom = editor.dom, |
240 dom = editor.dom, |
241 isIntRegExp = /^\d+$/; |
241 isIntRegExp = /^\d+$/; |
242 |
242 |
243 // default attributes |
243 // Default attributes. |
244 metadata = { |
244 metadata = { |
245 attachment_id: false, |
245 attachment_id: false, |
246 size: 'custom', |
246 size: 'custom', |
247 caption: '', |
247 caption: '', |
248 align: 'none', |
248 align: 'none', |
316 metadata.caption = editor.serializer.serialize( caption ) |
316 metadata.caption = editor.serializer.serialize( caption ) |
317 .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' ); |
317 .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' ); |
318 } |
318 } |
319 } |
319 } |
320 |
320 |
321 // Extract linkTo |
321 // Extract linkTo. |
322 if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' ) { |
322 if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' ) { |
323 link = imageNode.parentNode; |
323 link = imageNode.parentNode; |
324 metadata.linkUrl = dom.getAttrib( link, 'href' ); |
324 metadata.linkUrl = dom.getAttrib( link, 'href' ); |
325 metadata.linkTargetBlank = dom.getAttrib( link, 'target' ) === '_blank' ? true : false; |
325 metadata.linkTargetBlank = dom.getAttrib( link, 'target' ) === '_blank' ? true : false; |
326 metadata.linkRel = dom.getAttrib( link, 'rel' ); |
326 metadata.linkRel = dom.getAttrib( link, 'rel' ); |
332 |
332 |
333 function hasTextContent( node ) { |
333 function hasTextContent( node ) { |
334 return node && !! ( node.textContent || node.innerText ).replace( /\ufeff/g, '' ); |
334 return node && !! ( node.textContent || node.innerText ).replace( /\ufeff/g, '' ); |
335 } |
335 } |
336 |
336 |
337 // Verify HTML in captions |
337 // Verify HTML in captions. |
338 function verifyHTML( caption ) { |
338 function verifyHTML( caption ) { |
339 if ( ! caption || ( caption.indexOf( '<' ) === -1 && caption.indexOf( '>' ) === -1 ) ) { |
339 if ( ! caption || ( caption.indexOf( '<' ) === -1 && caption.indexOf( '>' ) === -1 ) ) { |
340 return caption; |
340 return caption; |
341 } |
341 } |
342 |
342 |
402 target: imageData.linkTargetBlank ? '_blank': null, |
402 target: imageData.linkTargetBlank ? '_blank': null, |
403 'class': imageData.linkClassName || null |
403 'class': imageData.linkClassName || null |
404 }; |
404 }; |
405 |
405 |
406 if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) { |
406 if ( imageNode.parentNode && imageNode.parentNode.nodeName === 'A' && ! hasTextContent( imageNode.parentNode ) ) { |
407 // Update or remove an existing link wrapped around the image |
407 // Update or remove an existing link wrapped around the image. |
408 if ( imageData.linkUrl ) { |
408 if ( imageData.linkUrl ) { |
409 dom.setAttribs( imageNode.parentNode, linkAttrs ); |
409 dom.setAttribs( imageNode.parentNode, linkAttrs ); |
410 } else { |
410 } else { |
411 dom.remove( imageNode.parentNode, true ); |
411 dom.remove( imageNode.parentNode, true ); |
412 } |
412 } |
413 } else if ( imageData.linkUrl ) { |
413 } else if ( imageData.linkUrl ) { |
414 if ( linkNode = dom.getParent( imageNode, 'a' ) ) { |
414 if ( linkNode = dom.getParent( imageNode, 'a' ) ) { |
415 // The image is inside a link together with other nodes, |
415 // The image is inside a link together with other nodes, |
416 // or is nested in another node, move it out |
416 // or is nested in another node, move it out. |
417 dom.insertAfter( imageNode, linkNode ); |
417 dom.insertAfter( imageNode, linkNode ); |
418 } |
418 } |
419 |
419 |
420 // Add link wrapped around the image |
420 // Add link wrapped around the image. |
421 linkNode = dom.create( 'a', linkAttrs ); |
421 linkNode = dom.create( 'a', linkAttrs ); |
422 imageNode.parentNode.insertBefore( linkNode, imageNode ); |
422 imageNode.parentNode.insertBefore( linkNode, imageNode ); |
423 linkNode.appendChild( imageNode ); |
423 linkNode.appendChild( imageNode ); |
424 } |
424 } |
425 |
425 |
465 } |
465 } |
466 |
466 |
467 } else { |
467 } else { |
468 id = id ? 'id="'+ id +'" ' : ''; |
468 id = id ? 'id="'+ id +'" ' : ''; |
469 |
469 |
470 // should create a new function for generating the caption markup |
470 // Should create a new function for generating the caption markup. |
471 html = '<dl ' + id + 'class="' + className +'" style="width: '+ width +'px">' + |
471 html = '<dl ' + id + 'class="' + className +'" style="width: '+ width +'px">' + |
472 '<dt class="wp-caption-dt"></dt><dd class="wp-caption-dd">'+ imageData.caption +'</dd></dl>'; |
472 '<dt class="wp-caption-dt"></dt><dd class="wp-caption-dd">'+ imageData.caption +'</dd></dl>'; |
473 |
473 |
474 wrap = dom.create( 'div', { 'class': 'mceTemp' }, html ); |
474 wrap = dom.create( 'div', { 'class': 'mceTemp' }, html ); |
475 |
475 |
484 if ( parent && dom.isEmpty( parent ) ) { |
484 if ( parent && dom.isEmpty( parent ) ) { |
485 dom.remove( parent ); |
485 dom.remove( parent ); |
486 } |
486 } |
487 } |
487 } |
488 } else if ( captionNode ) { |
488 } else if ( captionNode ) { |
489 // Remove the caption wrapper and place the image in new paragraph |
489 // Remove the caption wrapper and place the image in new paragraph. |
490 parent = dom.create( 'p' ); |
490 parent = dom.create( 'p' ); |
491 captionNode.parentNode.insertBefore( parent, captionNode ); |
491 captionNode.parentNode.insertBefore( parent, captionNode ); |
492 parent.appendChild( node ); |
492 parent.appendChild( node ); |
493 dom.remove( captionNode ); |
493 dom.remove( captionNode ); |
494 } |
494 } |
528 metadata = extractImageData( img ); |
528 metadata = extractImageData( img ); |
529 |
529 |
530 // Mark the image node so we can select it later. |
530 // Mark the image node so we can select it later. |
531 editor.$( img ).attr( 'data-wp-editing', 1 ); |
531 editor.$( img ).attr( 'data-wp-editing', 1 ); |
532 |
532 |
533 // Manipulate the metadata by reference that is fed into the PostImage model used in the media modal |
533 // Manipulate the metadata by reference that is fed into the PostImage model used in the media modal. |
534 wp.media.events.trigger( 'editor:image-edit', { |
534 wp.media.events.trigger( 'editor:image-edit', { |
535 editor: editor, |
535 editor: editor, |
536 metadata: metadata, |
536 metadata: metadata, |
537 image: img |
537 image: img |
538 } ); |
538 } ); |
556 frame.state('replace-image').on( 'replace', callback ); |
556 frame.state('replace-image').on( 'replace', callback ); |
557 frame.on( 'close', function() { |
557 frame.on( 'close', function() { |
558 editor.focus(); |
558 editor.focus(); |
559 frame.detach(); |
559 frame.detach(); |
560 |
560 |
561 // `close` fires first... |
561 /* |
562 // To be able to update the image node, we need to find it here, |
562 * `close` fires first... |
563 // and use it in the callback. |
563 * To be able to update the image node, we need to find it here, |
|
564 * and use it in the callback. |
|
565 */ |
564 imageNode = editor.$( 'img[data-wp-editing]' ) |
566 imageNode = editor.$( 'img[data-wp-editing]' ) |
565 imageNode.removeAttr( 'data-wp-editing' ); |
567 imageNode.removeAttr( 'data-wp-editing' ); |
566 }); |
568 }); |
567 |
569 |
568 frame.open(); |
570 frame.open(); |
598 var dom = editor.dom, |
600 var dom = editor.dom, |
599 captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions'; |
601 captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions'; |
600 |
602 |
601 dom.addClass( editor.getBody(), captionClass ); |
603 dom.addClass( editor.getBody(), captionClass ); |
602 |
604 |
603 // Prevent IE11 from making dl.wp-caption resizable |
605 // Prevent IE11 from making dl.wp-caption resizable. |
604 if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) { |
606 if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) { |
605 // The 'mscontrolselect' event is supported only in IE11+ |
607 // The 'mscontrolselect' event is supported only in IE11+. |
606 dom.bind( editor.getBody(), 'mscontrolselect', function( event ) { |
608 dom.bind( editor.getBody(), 'mscontrolselect', function( event ) { |
607 if ( event.target.nodeName === 'IMG' && dom.getParent( event.target, '.wp-caption' ) ) { |
609 if ( event.target.nodeName === 'IMG' && dom.getParent( event.target, '.wp-caption' ) ) { |
608 // Hide the thick border with resize handles around dl.wp-caption |
610 // Hide the thick border with resize handles around dl.wp-caption. |
609 editor.getBody().focus(); // :( |
611 editor.getBody().focus(); // :( |
610 } else if ( event.target.nodeName === 'DL' && dom.hasClass( event.target, 'wp-caption' ) ) { |
612 } else if ( event.target.nodeName === 'DL' && dom.hasClass( event.target, 'wp-caption' ) ) { |
611 // Trigger the thick border with resize handles... |
613 // Trigger the thick border with resize handles... |
612 // This will make the caption text editable. |
614 // This will make the caption text editable. |
613 event.target.focus(); |
615 event.target.focus(); |
686 |
688 |
687 if ( captionParent ) { |
689 if ( captionParent ) { |
688 if ( cmd === 'mceInsertContent' ) { |
690 if ( cmd === 'mceInsertContent' ) { |
689 if ( pasteInCaption ) { |
691 if ( pasteInCaption ) { |
690 pasteInCaption = false; |
692 pasteInCaption = false; |
691 // We are in the caption element, and in 'paste' context, |
693 /* |
692 // and the pasted HTML was cleaned up on 'pastePostProcess' above. |
694 * We are in the caption element, and in 'paste' context, |
693 // Let it be pasted in the caption. |
695 * and the pasted HTML was cleaned up on 'pastePostProcess' above. |
|
696 * Let it be pasted in the caption. |
|
697 */ |
694 return; |
698 return; |
695 } |
699 } |
696 |
700 |
697 // The paste is somewhere else in the caption DL element. |
701 /* |
698 // Prevent pasting in there as it will break the caption. |
702 * The paste is somewhere else in the caption DL element. |
699 // Make new paragraph under the caption DL and move the caret there. |
703 * Prevent pasting in there as it will break the caption. |
|
704 * Make new paragraph under the caption DL and move the caret there. |
|
705 */ |
700 p = dom.create( 'p' ); |
706 p = dom.create( 'p' ); |
701 dom.insertAfter( p, captionParent ); |
707 dom.insertAfter( p, captionParent ); |
702 editor.selection.setCursorLocation( p, 0 ); |
708 editor.selection.setCursorLocation( p, 0 ); |
703 |
709 |
704 // If the image is selected and the user pastes "over" it, |
710 /* |
705 // replace both the image and the caption elements with the pasted content. |
711 * If the image is selected and the user pastes "over" it, |
706 // This matches the behavior when pasting over non-caption images. |
712 * replace both the image and the caption elements with the pasted content. |
|
713 * This matches the behavior when pasting over non-caption images. |
|
714 */ |
707 if ( node.nodeName === 'IMG' ) { |
715 if ( node.nodeName === 'IMG' ) { |
708 editor.$( captionParent ).remove(); |
716 editor.$( captionParent ).remove(); |
709 } |
717 } |
710 |
718 |
711 editor.nodeChanged(); |
719 editor.nodeChanged(); |
712 } else { |
720 } else { |
713 // Clicking Indent or Outdent while an image with a caption is selected breaks the caption. |
721 // Clicking Indent or Outdent while an image with a caption is selected breaks the caption. |
714 // See #38313. |
722 // See #38313. |
757 keyCode = event.keyCode, |
765 keyCode = event.keyCode, |
758 dom = editor.dom, |
766 dom = editor.dom, |
759 VK = tinymce.util.VK; |
767 VK = tinymce.util.VK; |
760 |
768 |
761 if ( keyCode === VK.ENTER ) { |
769 if ( keyCode === VK.ENTER ) { |
762 // When pressing Enter inside a caption move the caret to a new parapraph under it |
770 // When pressing Enter inside a caption move the caret to a new parapraph under it. |
763 node = selection.getNode(); |
771 node = selection.getNode(); |
764 wrap = dom.getParent( node, 'div.mceTemp' ); |
772 wrap = dom.getParent( node, 'div.mceTemp' ); |
765 |
773 |
766 if ( wrap ) { |
774 if ( wrap ) { |
767 dom.events.cancel( event ); // Doesn't cancel all :( |
775 dom.events.cancel( event ); // Doesn't cancel all :( |
800 return false; |
808 return false; |
801 } |
809 } |
802 } |
810 } |
803 }); |
811 }); |
804 |
812 |
805 // After undo/redo FF seems to set the image height very slowly when it is set to 'auto' in the CSS. |
813 /* |
806 // This causes image.getBoundingClientRect() to return wrong values and the resize handles are shown in wrong places. |
814 * After undo/redo FF seems to set the image height very slowly when it is set to 'auto' in the CSS. |
807 // Collapse the selection to remove the resize handles. |
815 * This causes image.getBoundingClientRect() to return wrong values and the resize handles are shown in wrong places. |
|
816 * Collapse the selection to remove the resize handles. |
|
817 */ |
808 if ( tinymce.Env.gecko ) { |
818 if ( tinymce.Env.gecko ) { |
809 editor.on( 'undo redo', function() { |
819 editor.on( 'undo redo', function() { |
810 if ( editor.selection.getNode().nodeName === 'IMG' ) { |
820 if ( editor.selection.getNode().nodeName === 'IMG' ) { |
811 editor.selection.collapse(); |
821 editor.selection.collapse(); |
812 } |
822 } |