136 pieces.push( { content: remaining } ); |
136 pieces.push( { content: remaining } ); |
137 } |
137 } |
138 } ); |
138 } ); |
139 } ); |
139 } ); |
140 |
140 |
141 return _.pluck( pieces, 'content' ).join( '' ); |
141 content = _.pluck( pieces, 'content' ).join( '' ); |
|
142 return content.replace( /<p>\s*<p data-wpview-marker=/g, '<p data-wpview-marker=' ).replace( /<\/p>\s*<\/p>/g, '</p>' ); |
142 }, |
143 }, |
143 |
144 |
144 /** |
145 /** |
145 * Create a view instance. |
146 * Create a view instance. |
146 * |
147 * |
147 * @param {String} type The view type. |
148 * @param {String} type The view type. |
148 * @param {String} text The textual representation of the view. |
149 * @param {String} text The textual representation of the view. |
149 * @param {Object} options Options. |
150 * @param {Object} options Options. |
|
151 * @param {Boolean} force Recreate the instance. Optional. |
150 * |
152 * |
151 * @return {wp.mce.View} The view instance. |
153 * @return {wp.mce.View} The view instance. |
152 */ |
154 */ |
153 createInstance: function( type, text, options ) { |
155 createInstance: function( type, text, options, force ) { |
154 var View = this.get( type ), |
156 var View = this.get( type ), |
155 encodedText, |
157 encodedText, |
156 instance; |
158 instance; |
157 |
159 |
158 text = tinymce.DOM.decode( text ), |
160 if ( text.indexOf( '[' ) !== -1 && text.indexOf( ']' ) !== -1 ) { |
159 encodedText = encodeURIComponent( text ), |
161 // Looks like a shortcode? Remove any line breaks from inside of shortcodes |
160 instance = this.getInstance( encodedText ); |
162 // or autop will replace them with <p> and <br> later and the string won't match. |
161 |
163 text = text.replace( /\[[^\]]+\]/g, function( match ) { |
162 if ( instance ) { |
164 return match.replace( /[\r\n]/g, '' ); |
163 return instance; |
165 }); |
164 } |
166 } |
|
167 |
|
168 if ( ! force ) { |
|
169 instance = this.getInstance( text ); |
|
170 |
|
171 if ( instance ) { |
|
172 return instance; |
|
173 } |
|
174 } |
|
175 |
|
176 encodedText = encodeURIComponent( text ); |
165 |
177 |
166 options = _.extend( options || {}, { |
178 options = _.extend( options || {}, { |
167 text: text, |
179 text: text, |
168 encodedText: encodedText |
180 encodedText: encodedText |
169 } ); |
181 } ); |
451 * @param {*} content The content to set. |
466 * @param {*} content The content to set. |
452 * @param {Function} callback A callback. Optional. |
467 * @param {Function} callback A callback. Optional. |
453 * @param {Boolean} rendered Only set for (un)rendered nodes. Optional. |
468 * @param {Boolean} rendered Only set for (un)rendered nodes. Optional. |
454 */ |
469 */ |
455 setContent: function( content, callback, rendered ) { |
470 setContent: function( content, callback, rendered ) { |
456 if ( _.isObject( content ) && content.body.indexOf( '<script' ) !== -1 ) { |
471 if ( _.isObject( content ) && ( content.sandbox || content.head || content.body.indexOf( '<script' ) !== -1 ) ) { |
457 this.setIframes( content.head || '', content.body, callback, rendered ); |
472 this.setIframes( content.head || '', content.body, callback, rendered ); |
458 } else if ( _.isString( content ) && content.indexOf( '<script' ) !== -1 ) { |
473 } else if ( _.isString( content ) && content.indexOf( '<script' ) !== -1 ) { |
459 this.setIframes( '', content, callback, rendered ); |
474 this.setIframes( '', content, callback, rendered ); |
460 } else { |
475 } else { |
461 this.getNodes( function( editor, node, contentNode ) { |
476 this.getNodes( function( editor, node ) { |
462 content = content.body || content; |
477 content = content.body || content; |
463 |
478 |
464 if ( content.indexOf( '<iframe' ) !== -1 ) { |
479 if ( content.indexOf( '<iframe' ) !== -1 ) { |
465 content += '<div class="wpview-overlay"></div>'; |
480 content += '<span class="mce-shim"></span>'; |
466 } |
481 } |
467 |
482 |
468 contentNode.innerHTML = ''; |
483 editor.undoManager.transact( function() { |
469 contentNode.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content ); |
484 node.innerHTML = ''; |
470 |
485 node.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content ); |
471 callback && callback.call( this, editor, node, contentNode ); |
486 editor.dom.add( node, 'span', { 'class': 'wpview-end' } ); |
|
487 } ); |
|
488 |
|
489 callback && callback.call( this, editor, node ); |
472 }, rendered ); |
490 }, rendered ); |
473 } |
491 } |
474 }, |
492 }, |
475 |
493 |
476 /** |
494 /** |
480 * @param {String} body HTML string to be added to the body of the document. |
498 * @param {String} body HTML string to be added to the body of the document. |
481 * @param {Function} callback A callback. Optional. |
499 * @param {Function} callback A callback. Optional. |
482 * @param {Boolean} rendered Only set for (un)rendered nodes. Optional. |
500 * @param {Boolean} rendered Only set for (un)rendered nodes. Optional. |
483 */ |
501 */ |
484 setIframes: function( head, body, callback, rendered ) { |
502 setIframes: function( head, body, callback, rendered ) { |
485 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, |
503 var self = this; |
486 self = this; |
504 |
487 |
505 if ( body.indexOf( '[' ) !== -1 && body.indexOf( ']' ) !== -1 ) { |
488 this.getNodes( function( editor, node, contentNode ) { |
506 var shortcodesRegExp = new RegExp( '\\[\\/?(?:' + window.mceViewL10n.shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' ); |
|
507 // Escape tags inside shortcode previews. |
|
508 body = body.replace( shortcodesRegExp, function( match ) { |
|
509 return match.replace( /</g, '<' ).replace( />/g, '>' ); |
|
510 } ); |
|
511 } |
|
512 |
|
513 this.getNodes( function( editor, node ) { |
489 var dom = editor.dom, |
514 var dom = editor.dom, |
490 styles = '', |
515 styles = '', |
491 bodyClasses = editor.getBody().className || '', |
516 bodyClasses = editor.getBody().className || '', |
492 editorHead = editor.getDoc().getElementsByTagName( 'head' )[0]; |
517 editorHead = editor.getDoc().getElementsByTagName( 'head' )[0], |
|
518 iframe, iframeWin, iframeDoc, MutationObserver, observer, i, block; |
493 |
519 |
494 tinymce.each( dom.$( 'link[rel="stylesheet"]', editorHead ), function( link ) { |
520 tinymce.each( dom.$( 'link[rel="stylesheet"]', editorHead ), function( link ) { |
495 if ( link.href && link.href.indexOf( 'skins/lightgray/content.min.css' ) === -1 && |
521 if ( link.href && link.href.indexOf( 'skins/lightgray/content.min.css' ) === -1 && |
496 link.href.indexOf( 'skins/wordpress/wp-content.css' ) === -1 ) { |
522 link.href.indexOf( 'skins/wordpress/wp-content.css' ) === -1 ) { |
497 |
523 |
498 styles += dom.getOuterHTML( link ); |
524 styles += dom.getOuterHTML( link ); |
499 } |
525 } |
500 } ); |
526 } ); |
501 |
527 |
502 // Seems the browsers need a bit of time to insert/set the view nodes, |
528 if ( self.iframeHeight ) { |
503 // or the iframe will fail especially when switching Text => Visual. |
529 dom.add( node, 'span', { |
504 setTimeout( function() { |
530 'data-mce-bogus': 1, |
505 var iframe, iframeDoc, observer, i; |
531 style: { |
506 |
532 display: 'block', |
507 contentNode.innerHTML = ''; |
533 width: '100%', |
508 |
534 height: self.iframeHeight |
509 iframe = dom.add( contentNode, 'iframe', { |
535 } |
|
536 }, '\u200B' ); |
|
537 } |
|
538 |
|
539 editor.undoManager.transact( function() { |
|
540 node.innerHTML = ''; |
|
541 |
|
542 iframe = dom.add( node, 'iframe', { |
510 /* jshint scripturl: true */ |
543 /* jshint scripturl: true */ |
511 src: tinymce.Env.ie ? 'javascript:""' : '', |
544 src: tinymce.Env.ie ? 'javascript:""' : '', |
512 frameBorder: '0', |
545 frameBorder: '0', |
513 allowTransparency: 'true', |
546 allowTransparency: 'true', |
514 scrolling: 'no', |
547 scrolling: 'no', |
515 'class': 'wpview-sandbox', |
548 'class': 'wpview-sandbox', |
516 style: { |
549 style: { |
517 width: '100%', |
550 width: '100%', |
518 display: 'block' |
551 display: 'block' |
|
552 }, |
|
553 height: self.iframeHeight |
|
554 } ); |
|
555 |
|
556 dom.add( node, 'span', { 'class': 'mce-shim' } ); |
|
557 dom.add( node, 'span', { 'class': 'wpview-end' } ); |
|
558 } ); |
|
559 |
|
560 // Bail if the iframe node is not attached to the DOM. |
|
561 // Happens when the view is dragged in the editor. |
|
562 // There is a browser restriction when iframes are moved in the DOM. They get emptied. |
|
563 // The iframe will be rerendered after dropping the view node at the new location. |
|
564 if ( ! iframe.contentWindow ) { |
|
565 return; |
|
566 } |
|
567 |
|
568 iframeWin = iframe.contentWindow; |
|
569 iframeDoc = iframeWin.document; |
|
570 iframeDoc.open(); |
|
571 |
|
572 iframeDoc.write( |
|
573 '<!DOCTYPE html>' + |
|
574 '<html>' + |
|
575 '<head>' + |
|
576 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' + |
|
577 head + |
|
578 styles + |
|
579 '<style>' + |
|
580 'html {' + |
|
581 'background: transparent;' + |
|
582 'padding: 0;' + |
|
583 'margin: 0;' + |
|
584 '}' + |
|
585 'body#wpview-iframe-sandbox {' + |
|
586 'background: transparent;' + |
|
587 'padding: 1px 0 !important;' + |
|
588 'margin: -1px 0 0 !important;' + |
|
589 '}' + |
|
590 'body#wpview-iframe-sandbox:before,' + |
|
591 'body#wpview-iframe-sandbox:after {' + |
|
592 'display: none;' + |
|
593 'content: "";' + |
|
594 '}' + |
|
595 'iframe {' + |
|
596 'max-width: 100%;' + |
|
597 '}' + |
|
598 '</style>' + |
|
599 '</head>' + |
|
600 '<body id="wpview-iframe-sandbox" class="' + bodyClasses + '">' + |
|
601 body + |
|
602 '</body>' + |
|
603 '</html>' |
|
604 ); |
|
605 |
|
606 iframeDoc.close(); |
|
607 |
|
608 function resize() { |
|
609 var $iframe; |
|
610 |
|
611 if ( block ) { |
|
612 return; |
|
613 } |
|
614 |
|
615 // Make sure the iframe still exists. |
|
616 if ( iframe.contentWindow ) { |
|
617 $iframe = $( iframe ); |
|
618 self.iframeHeight = $( iframeDoc.body ).height(); |
|
619 |
|
620 if ( $iframe.height() !== self.iframeHeight ) { |
|
621 $iframe.height( self.iframeHeight ); |
|
622 editor.nodeChanged(); |
519 } |
623 } |
|
624 } |
|
625 } |
|
626 |
|
627 if ( self.iframeHeight ) { |
|
628 block = true; |
|
629 |
|
630 setTimeout( function() { |
|
631 block = false; |
|
632 resize(); |
|
633 }, 3000 ); |
|
634 } |
|
635 |
|
636 function reload() { |
|
637 if ( ! editor.isHidden() ) { |
|
638 $( node ).data( 'rendered', null ); |
|
639 |
|
640 setTimeout( function() { |
|
641 wp.mce.views.render(); |
|
642 } ); |
|
643 } |
|
644 } |
|
645 |
|
646 function addObserver() { |
|
647 observer = new MutationObserver( _.debounce( resize, 100 ) ); |
|
648 |
|
649 observer.observe( iframeDoc.body, { |
|
650 attributes: true, |
|
651 childList: true, |
|
652 subtree: true |
520 } ); |
653 } ); |
521 |
654 } |
522 dom.add( contentNode, 'div', { 'class': 'wpview-overlay' } ); |
655 |
523 |
656 $( iframeWin ).on( 'load', resize ).on( 'unload', reload ); |
524 iframeDoc = iframe.contentWindow.document; |
657 |
525 |
658 MutationObserver = iframeWin.MutationObserver || iframeWin.WebKitMutationObserver || iframeWin.MozMutationObserver; |
526 iframeDoc.open(); |
659 |
527 |
660 if ( MutationObserver ) { |
528 iframeDoc.write( |
661 if ( ! iframeDoc.body ) { |
529 '<!DOCTYPE html>' + |
662 iframeDoc.addEventListener( 'DOMContentLoaded', addObserver, false ); |
530 '<html>' + |
|
531 '<head>' + |
|
532 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' + |
|
533 head + |
|
534 styles + |
|
535 '<style>' + |
|
536 'html {' + |
|
537 'background: transparent;' + |
|
538 'padding: 0;' + |
|
539 'margin: 0;' + |
|
540 '}' + |
|
541 'body#wpview-iframe-sandbox {' + |
|
542 'background: transparent;' + |
|
543 'padding: 1px 0 !important;' + |
|
544 'margin: -1px 0 0 !important;' + |
|
545 '}' + |
|
546 'body#wpview-iframe-sandbox:before,' + |
|
547 'body#wpview-iframe-sandbox:after {' + |
|
548 'display: none;' + |
|
549 'content: "";' + |
|
550 '}' + |
|
551 '</style>' + |
|
552 '</head>' + |
|
553 '<body id="wpview-iframe-sandbox" class="' + bodyClasses + '">' + |
|
554 body + |
|
555 '</body>' + |
|
556 '</html>' |
|
557 ); |
|
558 |
|
559 iframeDoc.close(); |
|
560 |
|
561 function resize() { |
|
562 var $iframe, iframeDocHeight; |
|
563 |
|
564 // Make sure the iframe still exists. |
|
565 if ( iframe.contentWindow ) { |
|
566 $iframe = $( iframe ); |
|
567 iframeDocHeight = $( iframeDoc.body ).height(); |
|
568 |
|
569 if ( $iframe.height() !== iframeDocHeight ) { |
|
570 $iframe.height( iframeDocHeight ); |
|
571 editor.nodeChanged(); |
|
572 } |
|
573 } |
|
574 } |
|
575 |
|
576 $( iframe.contentWindow ).on( 'load', resize ); |
|
577 |
|
578 if ( MutationObserver ) { |
|
579 observer = new MutationObserver( _.debounce( resize, 100 ) ); |
|
580 |
|
581 observer.observe( iframeDoc.body, { |
|
582 attributes: true, |
|
583 childList: true, |
|
584 subtree: true |
|
585 } ); |
|
586 |
|
587 $( node ).one( 'wp-mce-view-unbind', function() { |
|
588 observer.disconnect(); |
|
589 } ); |
|
590 } else { |
663 } else { |
591 for ( i = 1; i < 6; i++ ) { |
664 addObserver(); |
592 setTimeout( resize, i * 700 ); |
665 } |
593 } |
666 } else { |
594 } |
667 for ( i = 1; i < 6; i++ ) { |
595 |
668 setTimeout( resize, i * 700 ); |
596 function classChange() { |
669 } |
597 iframeDoc.body.className = editor.getBody().className; |
670 } |
598 } |
671 |
599 |
672 callback && callback.call( self, editor, node ); |
600 editor.on( 'wp-body-class-change', classChange ); |
|
601 |
|
602 $( node ).one( 'wp-mce-view-unbind', function() { |
|
603 editor.off( 'wp-body-class-change', classChange ); |
|
604 } ); |
|
605 |
|
606 callback && callback.call( self, editor, node, contentNode ); |
|
607 }, 50 ); |
|
608 }, rendered ); |
673 }, rendered ); |
609 }, |
674 }, |
610 |
675 |
611 /** |
676 /** |
612 * Sets a loader for all view nodes tied to this view instance. |
677 * Sets a loader for all view nodes tied to this view instance. |
613 */ |
678 */ |
614 setLoader: function() { |
679 setLoader: function( dashicon ) { |
615 this.setContent( |
680 this.setContent( |
616 '<div class="loading-placeholder">' + |
681 '<div class="loading-placeholder">' + |
617 '<div class="dashicons dashicons-admin-media"></div>' + |
682 '<div class="dashicons dashicons-' + ( dashicon || 'admin-media' ) + '"></div>' + |
618 '<div class="wpview-loading"><ins></ins></div>' + |
683 '<div class="wpview-loading"><ins></ins></div>' + |
619 '</div>' |
684 '</div>' |
620 ); |
685 ); |
621 }, |
686 }, |
622 |
687 |
623 /** |
688 /** |
624 * Sets an error for all view nodes tied to this view instance. |
689 * Sets an error for all view nodes tied to this view instance. |
625 * |
690 * |
626 * @param {String} message The error message to set. |
691 * @param {String} message The error message to set. |
627 * @param {String} dashicon A dashicon ID (optional). {@link https://developer.wordpress.org/resource/dashicons/} |
692 * @param {String} dashicon A dashicon ID. Optional. {@link https://developer.wordpress.org/resource/dashicons/} |
628 */ |
693 */ |
629 setError: function( message, dashicon ) { |
694 setError: function( message, dashicon ) { |
630 this.setContent( |
695 this.setContent( |
631 '<div class="wpview-error">' + |
696 '<div class="wpview-error">' + |
632 '<div class="dashicons dashicons-' + ( dashicon || 'no' ) + '"></div>' + |
697 '<div class="dashicons dashicons-' + ( dashicon || 'no' ) + '"></div>' + |
683 * |
752 * |
684 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in. |
753 * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in. |
685 * @param {HTMLElement} node The view node to remove. |
754 * @param {HTMLElement} node The view node to remove. |
686 */ |
755 */ |
687 remove: function( editor, node ) { |
756 remove: function( editor, node ) { |
688 this.unbindNode.call( this, editor, node, $( node ).find( '.wpview-content' ).get( 0 ) ); |
757 this.unbindNode.call( this, editor, node ); |
689 $( node ).trigger( 'wp-mce-view-unbind' ); |
|
690 editor.dom.remove( node ); |
758 editor.dom.remove( node ); |
691 editor.focus(); |
759 editor.focus(); |
692 } |
760 } |
693 } ); |
761 } ); |
694 } )( window, window.wp, window.jQuery ); |
762 } )( window, window.wp, window.wp.shortcode, window.jQuery ); |
695 |
763 |
696 /* |
764 /* |
697 * The WordPress core TinyMCE views. |
765 * The WordPress core TinyMCE views. |
698 * Views for the gallery, audio, video, playlist and embed shortcodes, |
766 * Views for the gallery, audio, video, playlist and embed shortcodes, |
699 * and a view for embeddable URLs. |
767 * and a view for embeddable URLs. |
700 */ |
768 */ |
701 ( function( window, views, $ ) { |
769 ( function( window, views, media, $ ) { |
702 var postID = $( '#post_ID' ).val() || 0, |
770 var base, gallery, av, embed, |
703 media, gallery, av, embed; |
771 schema, parser, serializer; |
704 |
772 |
705 media = { |
773 function verifyHTML( string ) { |
|
774 var settings = {}; |
|
775 |
|
776 if ( ! window.tinymce ) { |
|
777 return string.replace( /<[^>]+>/g, '' ); |
|
778 } |
|
779 |
|
780 if ( ! string || ( string.indexOf( '<' ) === -1 && string.indexOf( '>' ) === -1 ) ) { |
|
781 return string; |
|
782 } |
|
783 |
|
784 schema = schema || new window.tinymce.html.Schema( settings ); |
|
785 parser = parser || new window.tinymce.html.DomParser( settings, schema ); |
|
786 serializer = serializer || new window.tinymce.html.Serializer( settings, schema ); |
|
787 |
|
788 return serializer.serialize( parser.parse( string, { forced_root_block: false } ) ); |
|
789 } |
|
790 |
|
791 base = { |
706 state: [], |
792 state: [], |
707 |
793 |
708 edit: function( text, update ) { |
794 edit: function( text, update ) { |
709 var media = wp.media[ this.type ], |
795 var type = this.type, |
710 frame = media.edit( text ); |
796 frame = media[ type ].edit( text ); |
711 |
797 |
712 this.pausePlayers && this.pausePlayers(); |
798 this.pausePlayers && this.pausePlayers(); |
713 |
799 |
714 _.each( this.state, function( state ) { |
800 _.each( this.state, function( state ) { |
715 frame.state( state ).on( 'update', function( selection ) { |
801 frame.state( state ).on( 'update', function( selection ) { |
716 update( media.shortcode( selection ).string() ); |
802 update( media[ type ].shortcode( selection ).string(), type === 'gallery' ); |
717 } ); |
803 } ); |
718 } ); |
804 } ); |
719 |
805 |
720 frame.on( 'close', function() { |
806 frame.on( 'close', function() { |
721 frame.detach(); |
807 frame.detach(); |
749 } |
835 } |
750 } |
836 } |
751 } ); |
837 } ); |
752 |
838 |
753 self.render( self.template( { |
839 self.render( self.template( { |
|
840 verifyHTML: verifyHTML, |
754 attachments: attachments, |
841 attachments: attachments, |
755 columns: attrs.columns ? parseInt( attrs.columns, 10 ) : wp.media.galleryDefaults.columns |
842 columns: attrs.columns ? parseInt( attrs.columns, 10 ) : media.galleryDefaults.columns |
756 } ) ); |
843 } ) ); |
757 } ) |
844 } ) |
758 .fail( function( jqXHR, textStatus ) { |
845 .fail( function( jqXHR, textStatus ) { |
759 self.setError( textStatus ); |
846 self.setError( textStatus ); |
760 } ); |
847 } ); |
761 } |
848 } |
762 } ); |
849 } ); |
763 |
850 |
764 av = _.extend( {}, media, { |
851 av = _.extend( {}, base, { |
765 action: 'parse-media-shortcode', |
852 action: 'parse-media-shortcode', |
766 |
853 |
767 initialize: function() { |
854 initialize: function() { |
768 var self = this; |
855 var self = this, maxwidth = null; |
769 |
856 |
770 if ( this.url ) { |
857 if ( this.url ) { |
771 this.loader = false; |
858 this.loader = false; |
772 this.shortcode = wp.media.embed.shortcode( { |
859 this.shortcode = media.embed.shortcode( { |
773 url: this.text |
860 url: this.text |
774 } ); |
861 } ); |
775 } |
862 } |
776 |
863 |
|
864 // Obtain the target width for the embed. |
|
865 if ( self.editor ) { |
|
866 maxwidth = self.editor.getBody().clientWidth; |
|
867 } |
|
868 |
777 wp.ajax.post( this.action, { |
869 wp.ajax.post( this.action, { |
778 post_ID: postID, |
870 post_ID: media.view.settings.post.id, |
779 type: this.shortcode.tag, |
871 type: this.shortcode.tag, |
780 shortcode: this.shortcode.string() |
872 shortcode: this.shortcode.string(), |
|
873 maxwidth: maxwidth |
781 } ) |
874 } ) |
782 .done( function( response ) { |
875 .done( function( response ) { |
783 self.render( response ); |
876 self.render( response ); |
784 } ) |
877 } ) |
785 .fail( function( response ) { |
878 .fail( function( response ) { |
786 if ( self.url ) { |
879 if ( self.url ) { |
|
880 self.ignore = true; |
787 self.removeMarkers(); |
881 self.removeMarkers(); |
788 } else { |
882 } else { |
789 self.setError( response.message || response.statusText, 'admin-media' ); |
883 self.setError( response.message || response.statusText, 'admin-media' ); |
790 } |
884 } |
791 } ); |
885 } ); |