From 09d4b541fb5637ed1812797257a9e8b792ba384d Mon Sep 17 00:00:00 2001 From: Andrew Ozz Date: Thu, 12 Jun 2014 02:48:17 +0000 Subject: [PATCH] wpView: - Don't wrap single-line URLs in [embed]. Use them directly in generating a view. - If the embedding HTML contains a script, "sandbox" it in an iframe to prevent it from changing the editor DOM. - Automatically add toolbar and overlay when needed. - Try to embed single-line URLs only if they are pasted in an empty paragraph. Props avryl, see #28195 git-svn-id: https://develop.svn.wordpress.org/trunk@28748 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/js/mce-view.js | 306 +++++++++++++----- .../js/tinymce/plugins/wpview/plugin.js | 16 +- src/wp-includes/media-template.php | 26 -- 3 files changed, 235 insertions(+), 113 deletions(-) diff --git a/src/wp-includes/js/mce-view.js b/src/wp-includes/js/mce-view.js index 4d5396ac5b..fb4afe51d2 100644 --- a/src/wp-includes/js/mce-view.js +++ b/src/wp-includes/js/mce-view.js @@ -8,7 +8,9 @@ // Ensure the global `wp` object exists. window.wp = window.wp || {}; -(function($){ +( function( $ ) { + 'use strict'; + var views = {}, instances = {}, media = wp.media, @@ -27,6 +29,7 @@ window.wp = window.wp || {}; */ wp.mce.View = function( options ) { options = options || {}; + this.type = options.type; _.extend( this, _.pick( options, viewOptions ) ); this.initialize.apply( this, arguments ); }; @@ -35,22 +38,67 @@ window.wp = window.wp || {}; initialize: function() {}, getHtml: function() {}, render: function() { - var html = this.getHtml(); - // Search all tinymce editor instances and update the placeholders + this.setContent( + '
' + + ( _.isFunction( views[ this.type ].edit ) ? '
' : '' ) + + '
' + + '
' + + '
' + + this.getHtml() + + '
' + + ( this.overlay ? '
' : '' ) + + // The is used to mark the end of the wrapper div (has to be the last child node). + // Needed when comparing the content as string for preventing extra undo levels. + '', + function( self, editor, node ) { + $( self ).trigger( 'ready', [ editor, node ] ); + } + ); + }, + unbind: function() {}, + setContent: function( html, callback, replace ) { _.each( tinymce.editors, function( editor ) { var self = this; if ( editor.plugins.wpview ) { - $( editor.getDoc() ).find( '[data-wpview-text="' + this.encodedText + '"]' ).each( function ( i, element ) { - var node = $( element ); - // The is used to mark the end of the wrapper div. Needed when comparing - // the content as string for preventing extra undo levels. - node.html( html ).append( '' ); - $( self ).trigger( 'ready', element ); - }); + $( editor.getBody() ) + .find( '[data-wpview-text="' + this.encodedText + '"]' ) + .each( function ( i, element ) { + var contentWrap = $( element ).children( '.wpview-content' ), + wrap = element; + + if ( contentWrap.length ) { + element = contentWrap = contentWrap[0]; + } + + if ( _.isString( html ) ) { + if ( replace ) { + element = editor.dom.replace( editor.dom.createFragment( html ), wrap ); + } else { + editor.dom.setHTML( element, html ); + } + } else { + if ( replace ) { + element = editor.dom.replace( html, wrap ); + } else { + element.appendChild( html ); + } + } + + if ( _.isFunction( callback ) ) { + callback( self, editor, $( element ).children( '.wpview-content' )[0] ); + } + } ); } }, this ); }, - unbind: function() {} + setError: function( message, dashicon ) { + this.setContent( + '
' + + '
' + + '

' + message + '

' + + '
' + ); + } } ); // take advantage of the Backbone extend method @@ -209,6 +257,7 @@ window.wp = window.wp || {}; if ( ! wp.mce.views.getInstance( encodedText ) ) { viewOptions = options; + viewOptions.type = viewType; viewOptions.encodedText = encodedText; instance = new view.View( viewOptions ); instances[ encodedText ] = instance; @@ -244,6 +293,7 @@ window.wp = window.wp || {}; if ( ! instance ) { result = view.toView( text ); viewOptions = result.options; + viewOptions.type = view.type; viewOptions.encodedText = encodedText; instance = new view.View( viewOptions ); instances[ encodedText ] = instance; @@ -331,7 +381,6 @@ window.wp = window.wp || {}; }; return this.template( options ); - } }, @@ -361,6 +410,8 @@ window.wp = window.wp || {}; loaded: false, View: _.extend( {}, wp.media.mixin, { + overlay: true, + initialize: function( options ) { this.players = []; this.shortcode = options.shortcode; @@ -375,32 +426,23 @@ window.wp = window.wp || {}; * * @global MediaElementPlayer * - * @param {Event} e + * @param {Event} event + * @param {Object} editor * @param {HTMLElement} node */ - setPlayer: function(e, node) { - // if the ready event fires on an empty node - if ( ! node ) { - return; - } - + setPlayer: function( event, editor, node ) { var self = this, - media, - firefox = this.ua.is( 'ff' ), - className = '.wp-' + this.shortcode.tag + '-shortcode'; + media; - media = $( node ).find( className ); + media = $( node ).find( '.wp-' + this.shortcode.tag + '-shortcode' ); if ( ! this.isCompatible( media ) ) { media.closest( '.wpview-wrap' ).addClass( 'wont-play' ); - if ( ! media.parent().hasClass( 'wpview-wrap' ) ) { - media.parent().replaceWith( media ); - } media.replaceWith( '

' + media.find( 'source' ).eq(0).prop( 'src' ) + '

' ); return; } else { media.closest( '.wpview-wrap' ).removeClass( 'wont-play' ); - if ( firefox ) { + if ( this.ua.is( 'ff' ) ) { media.prop( 'preload', 'metadata' ); } else { media.prop( 'preload', 'none' ); @@ -508,6 +550,7 @@ window.wp = window.wp || {}; state: ['playlist-edit', 'video-playlist-edit'], View: _.extend( {}, wp.media.mixin, { template: media.template( 'editor-playlist' ), + overlay: true, initialize: function( options ) { this.players = []; @@ -531,7 +574,7 @@ window.wp = window.wp || {}; this.dfd = this.attachments.more().done( _.bind( this.render, this ) ); }, - setPlaylist: function( event, element ) { + setPlaylist: function( event, editor, element ) { if ( ! this.data.tracks ) { return; } @@ -634,60 +677,161 @@ window.wp = window.wp || {}; /** * TinyMCE handler for the embed shortcode */ - wp.mce.views.register( 'embed', { - View: _.extend( {}, wp.media.mixin, { - template: media.template( 'editor-embed' ), - initialize: function( options ) { - this.players = []; - this.content = options.content; - this.parsed = false; - this.shortcode = options.shortcode; - _.bindAll( this, 'setHtml', 'setNode', 'fetch' ); - $( this ).on( 'ready', this.setNode ); - }, - unbind: function() { - var self = this; - _.each( this.players, function ( player ) { - player.pause(); - self.removePlayer( player ); - } ); - this.players = []; - }, - setNode: function ( e, node ) { - this.node = node; - if ( this.parsed ) { - this.parseMediaShortcodes(); - } else { - this.fetch(); - } - }, - fetch: function () { - wp.ajax.send( 'parse-embed', { - data: { - post_ID: $( '#post_ID' ).val(), - content: this.shortcode.string() - } - } ).done( this.setHtml ); - }, - setHtml: function ( content ) { - this.parsed = content; - $( this.node ).html( this.getHtml() ); - this.parseMediaShortcodes(); - }, - parseMediaShortcodes: function () { - var self = this; - $( '.wp-audio-shortcode, .wp-video-shortcode', this.node ).each( function ( i, element ) { - self.players.push( new MediaElementPlayer( element, self.mejsSettings ) ); - } ); - }, - getHtml: function() { - if ( ! this.parsed ) { - return ''; - } - return this.template({ content: this.parsed }); + wp.mce.embedView = _.extend( {}, wp.media.mixin, { + overlay: true, + initialize: function( options ) { + this.players = []; + this.content = options.content; + this.fetching = false; + this.parsed = false; + this.original = options.url || options.shortcode.string(); + + if ( options.url ) { + this.shortcode = '[embed]' + options.url + '[/embed]'; + } else { + this.shortcode = options.shortcode.string(); } - } ), - edit: function() {} + + _.bindAll( this, 'setHtml', 'setNode', 'fetch' ); + $( this ).on( 'ready', this.setNode ); + }, + unbind: function() { + var self = this; + _.each( this.players, function ( player ) { + player.pause(); + self.removePlayer( player ); + } ); + this.players = []; + }, + setNode: function () { + if ( this.parsed ) { + this.setHtml( this.parsed ); + this.parseMediaShortcodes(); + } else if ( ! this.fetching ) { + this.fetch(); + } + }, + fetch: function () { + var self = this; + + this.fetching = true; + + wp.ajax.send( 'parse-embed', { + data: { + post_ID: $( '#post_ID' ).val(), + content: this.shortcode + } + } ) + .done( function( content ) { + self.fetching = false; + + if ( content.substring( 0, ( '' + + '' + + '' + + '' + + '' + + '' + + content + + '' + + '' + ); + iframeDoc.close(); + + resize = function() { + $( iframe ).height( $( iframeDoc ).height() ); + }; + + if ( MutationObserver ) { + new MutationObserver( _.debounce( function() { + resize(); + }, 100 ) ) + .observe( iframeDoc.body, { + attributes: true, + childList: true, + subtree: true + } ); + } else { + for ( i = 1; i < 6; i++ ) { + setTimeout( resize, i * 700 ); + } + } + } else { + this.setContent( content ); + } + + this.parseMediaShortcodes(); + }, + parseMediaShortcodes: function () { + var self = this; + $( '.wp-audio-shortcode, .wp-video-shortcode', this.node ).each( function ( i, element ) { + self.players.push( new MediaElementPlayer( element, self.mejsSettings ) ); + } ); + }, + getHtml: function() { + return ''; + } + } ); + + wp.mce.views.register( 'embed', { + View: wp.mce.embedView + } ); + + wp.mce.views.register( 'embedURL', { + toView: function( content ) { + var re = /(?:^|

)(https?:\/\/[^\s"]+?)(?:<\/p>\s*|$)/gi, + match = re.exec( tinymce.trim( content ) ); + + if ( ! match ) { + return; + } + + return { + index: match.index, + content: match[0], + options: { + url: match[1] + } + }; + }, + View: wp.mce.embedView } ); }(jQuery)); diff --git a/src/wp-includes/js/tinymce/plugins/wpview/plugin.js b/src/wp-includes/js/tinymce/plugins/wpview/plugin.js index fba3bfc012..be11d0663c 100644 --- a/src/wp-includes/js/tinymce/plugins/wpview/plugin.js +++ b/src/wp-includes/js/tinymce/plugins/wpview/plugin.js @@ -158,6 +158,8 @@ tinymce.PluginManager.add( 'wpview', function( editor ) { // matching view patterns, and transform the matches into // view wrappers. editor.on( 'BeforeSetContent', function( event ) { + var node; + if ( ! event.content ) { return; } @@ -166,15 +168,17 @@ tinymce.PluginManager.add( 'wpview', function( editor ) { wp.mce.views.unbind( editor ); } + node = editor.selection.getNode(); + + // When a url is pasted, only try to embed it when pasted in an empty paragrapgh. + if ( event.content.match( /^\s*(https?:\/\/[^\s"]+)\s*$/i ) && + ( node.nodeName !== 'P' || node.parentNode !== editor.getBody() || ! editor.dom.isEmpty( node ) ) ) { + return; + } + event.content = wp.mce.views.toViews( event.content ); }); - editor.on( 'PastePreProcess', function( event ) { - if ( event.content.match( /^\s*(https?:\/\/[^\s"]+)\s*$/im ) ) { - event.content = '[embed]' + event.content + '[/embed]'; - } - } ); - // When the editor's content has been updated and the DOM has been // processed, render the views in the document. editor.on( 'SetContent', function( event ) { diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index 7ef4f041e3..1fdcb7f0a9 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -994,9 +994,6 @@ function wp_print_media_templates() { - -