58 } |
59 } |
59 } ); |
60 } ); |
60 } ); |
61 } ); |
61 |
62 |
62 editor.once( 'preinit', function() { |
63 editor.once( 'preinit', function() { |
63 toolbar = editor.wp._createToolbar( [ |
64 if ( editor.wp && editor.wp._createToolbar ) { |
64 'wp_img_alignleft', |
65 toolbar = editor.wp._createToolbar( [ |
65 'wp_img_aligncenter', |
66 'wp_img_alignleft', |
66 'wp_img_alignright', |
67 'wp_img_aligncenter', |
67 'wp_img_alignnone', |
68 'wp_img_alignright', |
68 'wp_img_edit', |
69 'wp_img_alignnone', |
69 'wp_img_remove' |
70 'wp_img_edit', |
70 ] ); |
71 'wp_img_remove' |
|
72 ] ); |
|
73 } |
71 } ); |
74 } ); |
72 |
75 |
73 editor.on( 'wptoolbar', function( event ) { |
76 editor.on( 'wptoolbar', function( event ) { |
74 if ( event.element.nodeName === 'IMG' && ! isPlaceholder( event.element ) ) { |
77 if ( event.element.nodeName === 'IMG' && ! isPlaceholder( event.element ) ) { |
75 event.toolbar = toolbar; |
78 event.toolbar = toolbar; |
76 } |
79 } |
77 } ); |
80 } ); |
78 |
81 |
79 // Safari on iOS fails to select image nodes in contentEditoble mode on touch/click. |
82 function isNonEditable( node ) { |
|
83 var parent = editor.$( node ).parents( '[contenteditable]' ); |
|
84 return parent && parent.attr( 'contenteditable' ) === 'false'; |
|
85 } |
|
86 |
|
87 // Safari on iOS fails to select images in contentEditoble mode on touch. |
80 // Select them again. |
88 // Select them again. |
81 if ( iOS ) { |
89 if ( iOS ) { |
82 editor.on( 'click', function( event ) { |
90 editor.on( 'init', function() { |
83 if ( event.target.nodeName === 'IMG' ) { |
91 editor.on( 'touchstart', function( event ) { |
84 var node = event.target; |
92 if ( event.target.nodeName === 'IMG' && ! isNonEditable( event.target ) ) { |
85 |
93 touchOnImage = true; |
86 window.setTimeout( function() { |
94 } |
87 editor.selection.select( node ); |
95 }); |
88 editor.nodeChanged(); |
96 |
89 }, 200 ); |
97 editor.dom.bind( editor.getDoc(), 'touchmove', function() { |
90 } else { |
98 touchOnImage = false; |
91 toolbar.hide(); |
99 }); |
92 } |
100 |
93 } ); |
101 editor.on( 'touchend', function( event ) { |
|
102 if ( touchOnImage && event.target.nodeName === 'IMG' && ! isNonEditable( event.target ) ) { |
|
103 var node = event.target; |
|
104 |
|
105 touchOnImage = false; |
|
106 |
|
107 window.setTimeout( function() { |
|
108 editor.selection.select( node ); |
|
109 editor.nodeChanged(); |
|
110 }, 100 ); |
|
111 } else if ( toolbar ) { |
|
112 toolbar.hide(); |
|
113 } |
|
114 }); |
|
115 }); |
94 } |
116 } |
95 |
117 |
96 function parseShortcode( content ) { |
118 function parseShortcode( content ) { |
97 return content.replace( /(?:<p>)?\[(?:wp_)?caption([^\]]+)\]([\s\S]+?)\[\/(?:wp_)?caption\](?:<\/p>)?/g, function( a, b, c ) { |
119 return content.replace( /(?:<p>)?\[(?:wp_)?caption([^\]]+)\]([\s\S]+?)\[\/(?:wp_)?caption\](?:<\/p>)?/g, function( a, b, c ) { |
98 var id, align, classes, caption, img, width, |
120 var id, align, classes, caption, img, width; |
99 trim = tinymce.trim; |
|
100 |
121 |
101 id = b.match( /id=['"]([^'"]*)['"] ?/ ); |
122 id = b.match( /id=['"]([^'"]*)['"] ?/ ); |
102 if ( id ) { |
123 if ( id ) { |
103 b = b.replace( id[0], '' ); |
124 b = b.replace( id[0], '' ); |
104 } |
125 } |
155 '<dt class="wp-caption-dt">'+ img +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl></div>'; |
176 '<dt class="wp-caption-dt">'+ img +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl></div>'; |
156 }); |
177 }); |
157 } |
178 } |
158 |
179 |
159 function getShortcode( content ) { |
180 function getShortcode( content ) { |
160 return content.replace( /<div (?:id="attachment_|class="mceTemp)[^>]*>([\s\S]+?)<\/div>/g, function( a, b ) { |
181 return content.replace( /(?:<div [^>]+mceTemp[^>]+>)?\s*(<dl [^>]+wp-caption[^>]+>[\s\S]+?<\/dl>)\s*(?:<\/div>)?/g, function( all, dl ) { |
161 var out = ''; |
182 var out = ''; |
162 |
183 |
163 if ( b.indexOf('<img ') === -1 ) { |
184 if ( dl.indexOf('<img ') === -1 || dl.indexOf('</p>') !== -1 ) { |
164 // Broken caption. The user managed to drag the image out? |
185 // Broken caption. The user managed to drag the image out or type in the wrapper div? |
165 // Try to return the caption text as a paragraph. |
186 // Remove the <dl>, <dd> and <dt> and return the remaining text. |
166 out = b.match( /<dd [^>]+>([\s\S]+?)<\/dd>/i ); |
187 return dl.replace( /<d[ldt]( [^>]+)?>/g, '' ).replace( /<\/d[ldt]>/g, '' ); |
167 |
188 } |
168 if ( out && out[1] ) { |
189 |
169 return '<p>' + out[1] + '</p>'; |
190 out = dl.replace( /\s*<dl ([^>]+)>\s*<dt [^>]+>([\s\S]+?)<\/dt>\s*<dd [^>]+>([\s\S]*?)<\/dd>\s*<\/dl>\s*/gi, function( a, b, c, caption ) { |
170 } |
|
171 |
|
172 return ''; |
|
173 } |
|
174 |
|
175 out = b.replace( /\s*<dl ([^>]+)>\s*<dt [^>]+>([\s\S]+?)<\/dt>\s*<dd [^>]+>([\s\S]*?)<\/dd>\s*<\/dl>\s*/gi, function( a, b, c, caption ) { |
|
176 var id, classes, align, width; |
191 var id, classes, align, width; |
177 |
192 |
178 width = c.match( /width="([0-9]*)"/ ); |
193 width = c.match( /width="([0-9]*)"/ ); |
179 width = ( width && width[1] ) ? width[1] : ''; |
194 width = ( width && width[1] ) ? width[1] : ''; |
180 |
195 |
555 editor.on( 'init', function() { |
584 editor.on( 'init', function() { |
556 var dom = editor.dom, |
585 var dom = editor.dom, |
557 captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions'; |
586 captionClass = editor.getParam( 'wpeditimage_html5_captions' ) ? 'html5-captions' : 'html4-captions'; |
558 |
587 |
559 dom.addClass( editor.getBody(), captionClass ); |
588 dom.addClass( editor.getBody(), captionClass ); |
560 |
|
561 // Add caption field to the default image dialog |
|
562 editor.on( 'wpLoadImageForm', function( event ) { |
|
563 if ( editor.getParam( 'wpeditimage_disable_captions' ) ) { |
|
564 return; |
|
565 } |
|
566 |
|
567 var captionField = { |
|
568 type: 'textbox', |
|
569 flex: 1, |
|
570 name: 'caption', |
|
571 minHeight: 60, |
|
572 multiline: true, |
|
573 scroll: true, |
|
574 label: 'Image caption' |
|
575 }; |
|
576 |
|
577 event.data.splice( event.data.length - 1, 0, captionField ); |
|
578 }); |
|
579 |
|
580 // Fix caption parent width for images added from URL |
|
581 editor.on( 'wpNewImageRefresh', function( event ) { |
|
582 var parent, captionWidth; |
|
583 |
|
584 if ( parent = dom.getParent( event.node, 'dl.wp-caption' ) ) { |
|
585 if ( ! parent.style.width ) { |
|
586 captionWidth = parseInt( event.node.clientWidth, 10 ) + 10; |
|
587 captionWidth = captionWidth ? captionWidth + 'px' : '50%'; |
|
588 dom.setStyle( parent, 'width', captionWidth ); |
|
589 } |
|
590 } |
|
591 }); |
|
592 |
|
593 editor.on( 'wpImageFormSubmit', function( event ) { |
|
594 var data = event.imgData.data, |
|
595 imgNode = event.imgData.node, |
|
596 caption = event.imgData.caption, |
|
597 captionId = '', |
|
598 captionAlign = '', |
|
599 captionWidth = '', |
|
600 wrap, parent, node, html, imgId; |
|
601 |
|
602 // Temp image id so we can find the node later |
|
603 data.id = '__wp-temp-img-id'; |
|
604 // Cancel the original callback |
|
605 event.imgData.cancel = true; |
|
606 |
|
607 if ( ! data.style ) { |
|
608 data.style = null; |
|
609 } |
|
610 |
|
611 if ( ! data.src ) { |
|
612 // Delete the image and the caption |
|
613 if ( imgNode ) { |
|
614 if ( wrap = dom.getParent( imgNode, 'div.mceTemp' ) ) { |
|
615 dom.remove( wrap ); |
|
616 } else if ( imgNode.parentNode.nodeName === 'A' ) { |
|
617 dom.remove( imgNode.parentNode ); |
|
618 } else { |
|
619 dom.remove( imgNode ); |
|
620 } |
|
621 |
|
622 editor.nodeChanged(); |
|
623 } |
|
624 return; |
|
625 } |
|
626 |
|
627 if ( caption ) { |
|
628 caption = caption.replace( /\r\n|\r/g, '\n' ).replace( /<\/?[a-zA-Z0-9]+( [^<>]+)?>/g, function( a ) { |
|
629 // No line breaks inside HTML tags |
|
630 return a.replace( /[\r\n\t]+/, ' ' ); |
|
631 }); |
|
632 |
|
633 // Convert remaining line breaks to <br> |
|
634 caption = caption.replace( /(<br[^>]*>)\s*\n\s*/g, '$1' ).replace( /\s*\n\s*/g, '<br />' ); |
|
635 caption = verifyHTML( caption ); |
|
636 } |
|
637 |
|
638 if ( ! imgNode ) { |
|
639 // New image inserted |
|
640 html = dom.createHTML( 'img', data ); |
|
641 |
|
642 if ( caption ) { |
|
643 node = editor.selection.getNode(); |
|
644 |
|
645 if ( data.width ) { |
|
646 captionWidth = parseInt( data.width, 10 ); |
|
647 |
|
648 if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) { |
|
649 captionWidth += 10; |
|
650 } |
|
651 |
|
652 captionWidth = ' style="width: ' + captionWidth + 'px"'; |
|
653 } |
|
654 |
|
655 html = '<dl class="wp-caption alignnone"' + captionWidth + '>' + |
|
656 '<dt class="wp-caption-dt">'+ html +'</dt><dd class="wp-caption-dd">'+ caption +'</dd></dl>'; |
|
657 |
|
658 if ( node.nodeName === 'P' ) { |
|
659 parent = node; |
|
660 } else { |
|
661 parent = dom.getParent( node, 'p' ); |
|
662 } |
|
663 |
|
664 if ( parent && parent.nodeName === 'P' ) { |
|
665 wrap = dom.create( 'div', { 'class': 'mceTemp' }, html ); |
|
666 parent.parentNode.insertBefore( wrap, parent ); |
|
667 editor.selection.select( wrap ); |
|
668 editor.nodeChanged(); |
|
669 |
|
670 if ( dom.isEmpty( parent ) ) { |
|
671 dom.remove( parent ); |
|
672 } |
|
673 } else { |
|
674 editor.selection.setContent( '<div class="mceTemp">' + html + '</div>' ); |
|
675 } |
|
676 } else { |
|
677 editor.selection.setContent( html ); |
|
678 } |
|
679 } else { |
|
680 // Edit existing image |
|
681 |
|
682 // Store the original image id if any |
|
683 imgId = imgNode.id || null; |
|
684 // Update the image node |
|
685 dom.setAttribs( imgNode, data ); |
|
686 wrap = dom.getParent( imgNode, 'dl.wp-caption' ); |
|
687 |
|
688 if ( caption ) { |
|
689 if ( wrap ) { |
|
690 if ( parent = dom.select( 'dd.wp-caption-dd', wrap )[0] ) { |
|
691 parent.innerHTML = caption; |
|
692 } |
|
693 } else { |
|
694 if ( imgNode.className ) { |
|
695 captionId = imgNode.className.match( /wp-image-([0-9]+)/ ); |
|
696 captionAlign = imgNode.className.match( /align(left|right|center|none)/ ); |
|
697 } |
|
698 |
|
699 if ( captionAlign ) { |
|
700 captionAlign = captionAlign[0]; |
|
701 imgNode.className = imgNode.className.replace( /align(left|right|center|none)/g, '' ); |
|
702 } else { |
|
703 captionAlign = 'alignnone'; |
|
704 } |
|
705 |
|
706 captionAlign = ' class="wp-caption ' + captionAlign + '"'; |
|
707 |
|
708 if ( captionId ) { |
|
709 captionId = ' id="attachment_' + captionId[1] + '"'; |
|
710 } |
|
711 |
|
712 captionWidth = data.width || imgNode.clientWidth; |
|
713 |
|
714 if ( captionWidth ) { |
|
715 captionWidth = parseInt( captionWidth, 10 ); |
|
716 |
|
717 if ( ! editor.getParam( 'wpeditimage_html5_captions' ) ) { |
|
718 captionWidth += 10; |
|
719 } |
|
720 |
|
721 captionWidth = ' style="width: '+ captionWidth +'px"'; |
|
722 } |
|
723 |
|
724 if ( imgNode.parentNode && imgNode.parentNode.nodeName === 'A' ) { |
|
725 node = imgNode.parentNode; |
|
726 } else { |
|
727 node = imgNode; |
|
728 } |
|
729 |
|
730 html = '<dl ' + captionId + captionAlign + captionWidth + '>' + |
|
731 '<dt class="wp-caption-dt"></dt><dd class="wp-caption-dd">'+ caption +'</dd></dl>'; |
|
732 |
|
733 wrap = dom.create( 'div', { 'class': 'mceTemp' }, html ); |
|
734 |
|
735 if ( parent = dom.getParent( node, 'p' ) ) { |
|
736 parent.parentNode.insertBefore( wrap, parent ); |
|
737 } else { |
|
738 node.parentNode.insertBefore( wrap, node ); |
|
739 } |
|
740 |
|
741 editor.$( wrap ).find( 'dt.wp-caption-dt' ).append( node ); |
|
742 |
|
743 if ( parent && dom.isEmpty( parent ) ) { |
|
744 dom.remove( parent ); |
|
745 } |
|
746 } |
|
747 } else { |
|
748 if ( wrap ) { |
|
749 // Remove the caption wrapper and place the image in new paragraph |
|
750 if ( imgNode.parentNode.nodeName === 'A' ) { |
|
751 html = dom.getOuterHTML( imgNode.parentNode ); |
|
752 } else { |
|
753 html = dom.getOuterHTML( imgNode ); |
|
754 } |
|
755 |
|
756 parent = dom.create( 'p', {}, html ); |
|
757 dom.insertAfter( parent, wrap.parentNode ); |
|
758 editor.selection.select( parent ); |
|
759 editor.nodeChanged(); |
|
760 dom.remove( wrap.parentNode ); |
|
761 } |
|
762 } |
|
763 } |
|
764 |
|
765 imgNode = dom.get('__wp-temp-img-id'); |
|
766 dom.setAttrib( imgNode, 'id', imgId ); |
|
767 event.imgData.node = imgNode; |
|
768 }); |
|
769 |
|
770 editor.on( 'wpLoadImageData', function( event ) { |
|
771 var parent, |
|
772 data = event.imgData.data, |
|
773 imgNode = event.imgData.node; |
|
774 |
|
775 if ( parent = dom.getParent( imgNode, 'dl.wp-caption' ) ) { |
|
776 parent = dom.select( 'dd.wp-caption-dd', parent )[0]; |
|
777 |
|
778 if ( parent ) { |
|
779 data.caption = editor.serializer.serialize( parent ) |
|
780 .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' ); |
|
781 } |
|
782 } |
|
783 }); |
|
784 |
|
785 dom.bind( editor.getDoc(), 'dragstart', function( event ) { |
|
786 var node = editor.selection.getNode(); |
|
787 |
|
788 // Prevent dragging images out of the caption elements |
|
789 if ( node.nodeName === 'IMG' && dom.getParent( node, '.wp-caption' ) ) { |
|
790 event.preventDefault(); |
|
791 } |
|
792 }); |
|
793 |
589 |
794 // Prevent IE11 from making dl.wp-caption resizable |
590 // Prevent IE11 from making dl.wp-caption resizable |
795 if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) { |
591 if ( tinymce.Env.ie && tinymce.Env.ie > 10 ) { |
796 // The 'mscontrolselect' event is supported only in IE11+ |
592 // The 'mscontrolselect' event is supported only in IE11+ |
797 dom.bind( editor.getBody(), 'mscontrolselect', function( event ) { |
593 dom.bind( editor.getBody(), 'mscontrolselect', function( event ) { |
830 dom.setStyle( parent, 'width', width + 'px' ); |
626 dom.setStyle( parent, 'width', width + 'px' ); |
831 } |
627 } |
832 } |
628 } |
833 }); |
629 }); |
834 } |
630 } |
835 }); |
631 }); |
|
632 |
|
633 editor.on( 'pastePostProcess', function( event ) { |
|
634 // Pasting in a caption node. |
|
635 if ( editor.dom.getParent( editor.selection.getNode(), 'dd.wp-caption-dd' ) ) { |
|
636 // Remove "non-block" elements that should not be in captions. |
|
637 editor.$( 'img, audio, video, object, embed, iframe, script, style', event.node ).remove(); |
|
638 |
|
639 editor.$( '*', event.node ).each( function( i, node ) { |
|
640 if ( editor.dom.isBlock( node ) ) { |
|
641 // Insert <br> where the blocks used to be. Makes it look better after pasting in the caption. |
|
642 if ( tinymce.trim( node.textContent || node.innerText ) ) { |
|
643 editor.dom.insertAfter( editor.dom.create( 'br' ), node ); |
|
644 editor.dom.remove( node, true ); |
|
645 } else { |
|
646 editor.dom.remove( node ); |
|
647 } |
|
648 } |
|
649 }); |
|
650 |
|
651 // Trim <br> tags. |
|
652 editor.$( 'br', event.node ).each( function( i, node ) { |
|
653 if ( ! node.nextSibling || node.nextSibling.nodeName === 'BR' || |
|
654 ! node.previousSibling || node.previousSibling.nodeName === 'BR' ) { |
|
655 |
|
656 editor.dom.remove( node ); |
|
657 } |
|
658 } ); |
|
659 |
|
660 // Pasted HTML is cleaned up for inserting in the caption. |
|
661 pasteInCaption = true; |
|
662 } |
|
663 }); |
836 |
664 |
837 editor.on( 'BeforeExecCommand', function( event ) { |
665 editor.on( 'BeforeExecCommand', function( event ) { |
838 var node, p, DL, align, replacement, |
666 var node, p, DL, align, replacement, captionParent, |
839 cmd = event.command, |
667 cmd = event.command, |
840 dom = editor.dom; |
668 dom = editor.dom; |
841 |
669 |
842 if ( cmd === 'mceInsertContent' ) { |
670 if ( cmd === 'mceInsertContent' || cmd === 'Indent' || cmd === 'Outdent' ) { |
843 // When inserting content, if the caret is inside a caption create new paragraph under |
671 node = editor.selection.getNode(); |
844 // and move the caret there |
672 captionParent = dom.getParent( node, 'div.mceTemp' ); |
845 if ( node = dom.getParent( editor.selection.getNode(), 'div.mceTemp' ) ) { |
673 |
846 p = dom.create( 'p' ); |
674 if ( captionParent ) { |
847 dom.insertAfter( p, node ); |
675 if ( cmd === 'mceInsertContent' ) { |
848 editor.selection.setCursorLocation( p, 0 ); |
676 if ( pasteInCaption ) { |
849 editor.nodeChanged(); |
677 pasteInCaption = false; |
|
678 // We are in the caption element, and in 'paste' context, |
|
679 // and the pasted HTML was cleaned up on 'pastePostProcess' above. |
|
680 // Let it be pasted in the caption. |
|
681 return; |
|
682 } |
|
683 |
|
684 // The paste is somewhere else in the caption DL element. |
|
685 // Prevent pasting in there as it will break the caption. |
|
686 // Make new paragraph under the caption DL and move the caret there. |
|
687 p = dom.create( 'p' ); |
|
688 dom.insertAfter( p, captionParent ); |
|
689 editor.selection.setCursorLocation( p, 0 ); |
|
690 |
|
691 // If the image is selected and the user pastes "over" it, |
|
692 // replace both the image and the caption elements with the pasted content. |
|
693 // This matches the behavior when pasting over non-caption images. |
|
694 if ( node.nodeName === 'IMG' ) { |
|
695 editor.$( captionParent ).remove(); |
|
696 } |
|
697 |
|
698 editor.nodeChanged(); |
|
699 } else { |
|
700 // Clicking Indent or Outdent while an image with a caption is selected breaks the caption. |
|
701 // See #38313. |
|
702 event.preventDefault(); |
|
703 event.stopImmediatePropagation(); |
|
704 return false; |
|
705 } |
850 } |
706 } |
851 } else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' || cmd === 'wpAlignNone' ) { |
707 } else if ( cmd === 'JustifyLeft' || cmd === 'JustifyRight' || cmd === 'JustifyCenter' || cmd === 'wpAlignNone' ) { |
852 node = editor.selection.getNode(); |
708 node = editor.selection.getNode(); |
853 align = 'align' + cmd.slice( 7 ).toLowerCase(); |
709 align = 'align' + cmd.slice( 7 ).toLowerCase(); |
854 DL = editor.dom.getParent( node, '.wp-caption' ); |
710 DL = editor.dom.getParent( node, '.wp-caption' ); |