From 17a90fff8bdc721738707fd5d4926cb71242ef08 Mon Sep 17 00:00:00 2001 From: Andrew Ozz Date: Sat, 12 Apr 2014 00:45:48 +0000 Subject: [PATCH] TinyMCE wpViews: - Prevent undo steps from being added when the body of a wpview changes. - Add unbind() to handle cleanup on DOM rebuilding in TinyMCE. - Ensure that MediaElement's cleanup routine is run on every player in all instances of the editor. - Initialize the players after some delay to ensure CSS is loaded. Props gcorne and wonderboymusic, fixes #27389 git-svn-id: https://develop.svn.wordpress.org/trunk@28084 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/js/mce-view.js | 34 +++++++++++++++--- src/wp-includes/js/media-audiovideo.js | 8 ++--- .../js/tinymce/plugins/wpview/plugin.js | 36 ++++++++++++------- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/wp-includes/js/mce-view.js b/src/wp-includes/js/mce-view.js index d3af61ee53..d632d829e7 100644 --- a/src/wp-includes/js/mce-view.js +++ b/src/wp-includes/js/mce-view.js @@ -41,12 +41,15 @@ window.wp = window.wp || {}; doc = editor.getDoc(); $( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).each(function (i, elem) { var node = $( elem ); - node.html( html ); + // 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', elem ); }); } }, this ); - } + }, + unbind: function() {} } ); // take advantage of the Backbone extend method @@ -92,6 +95,17 @@ window.wp = window.wp || {}; delete views[ type ]; }, + /** + * wp.mce.views.unbind( editor ) + * + * The editor DOM is being rebuilt, run cleanup. + */ + unbind: function() { + _.each( instances, function( instance ) { + instance.unbind(); + } ); + }, + /** * toViews( content ) * Scans a `content` string for each view's pattern, replacing any @@ -339,6 +353,7 @@ window.wp = window.wp || {}; * @mixin */ wp.mce.media = { + loaded: false, /** * @global wp.shortcode * @@ -410,6 +425,7 @@ window.wp = window.wp || {}; */ wp.mce.media.View = wp.mce.View.extend({ initialize: function( options ) { + this.players = []; this.shortcode = options.shortcode; _.bindAll( this, 'setPlayer' ); $(this).on( 'ready', this.setPlayer ); @@ -460,8 +476,9 @@ window.wp = window.wp || {}; media = wp.media.view.MediaDetails.prepareSrc( media.get(0) ); setTimeout( function() { - self.player = new MediaElementPlayer( media, this.mejsSettings ); - }, 75 ); + wp.mce.media.loaded = true; + self.players.push( new MediaElementPlayer( media, self.mejsSettings ) ); + }, wp.mce.media.loaded ? 10 : 500 ); }, /** @@ -475,6 +492,15 @@ window.wp = window.wp || {}; wp.media[ this.shortcode.tag ].defaults ); return this.template({ model: attrs }); + }, + + unbind: function() { + var self = this; + this.pauseAllPlayers(); + _.each( this.players, function (player) { + self.removePlayer( player ); + } ); + this.players = []; } }); _.extend( wp.mce.media.View.prototype, wp.media.mixin ); diff --git a/src/wp-includes/js/media-audiovideo.js b/src/wp-includes/js/media-audiovideo.js index 3f659da968..da91a9cf63 100644 --- a/src/wp-includes/js/media-audiovideo.js +++ b/src/wp-includes/js/media-audiovideo.js @@ -128,8 +128,8 @@ * MediaElement tries to pull the audio/video tag out of * its container and re-add it to the DOM. */ - removePlayer: function() { - var t = this.player, featureIndex, feature; + removePlayer: function(t) { + var featureIndex, feature; // invoke features cleanup for ( featureIndex in t.options.features ) { @@ -165,7 +165,7 @@ unsetPlayer : function() { if ( this.player ) { wp.media.mixin.pauseAllPlayers(); - wp.media.mixin.removePlayer.apply( this ); + wp.media.mixin.removePlayer( this.player ); this.player = false; } } @@ -914,4 +914,4 @@ $( init ); -}(jQuery, _, Backbone)); \ No newline at end of file +}(jQuery, _, Backbone)); diff --git a/src/wp-includes/js/tinymce/plugins/wpview/plugin.js b/src/wp-includes/js/tinymce/plugins/wpview/plugin.js index 812341cf6b..f357fffc80 100644 --- a/src/wp-includes/js/tinymce/plugins/wpview/plugin.js +++ b/src/wp-includes/js/tinymce/plugins/wpview/plugin.js @@ -80,11 +80,12 @@ tinymce.PluginManager.add( 'wpview', function( editor ) { 'contenteditable': 'true' }, getViewText( viewNode ) ); - viewNode.appendChild( clipboard ); + // Prepend inside the wrapper + viewNode.insertBefore( clipboard, viewNode.firstChild ); // Both of the following are necessary to prevent manipulating the selection/focus - editor.dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop ); - editor.dom.bind( selected, 'beforedeactivate focusin focusout', _stop ); + dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop ); + dom.bind( selected, 'beforedeactivate focusin focusout', _stop ); // Make sure that the editor is focused. // It is possible that the editor is not focused when the mouse event fires @@ -140,8 +141,14 @@ tinymce.PluginManager.add( 'wpview', function( editor ) { return; } + // Remove the content of view wrappers from HTML string + function emptyViews( content ) { + return content.replace(/(]+wpview-wrap[^>]+>)[\s\S]+?data-wpview-end[^>]*><\/ins><\/div>/g, '$1' ); + } + + // Prevent adding undo levels on changes inside a view wrapper editor.on( 'BeforeAddUndo', function( event ) { - if ( selected && ! toRemove ) { + if ( event.lastLevel && emptyViews( event.level.content ) === emptyViews( event.lastLevel.content ) ) { event.preventDefault(); } }); @@ -149,12 +156,16 @@ tinymce.PluginManager.add( 'wpview', function( editor ) { // When the editor's content changes, scan the new content for // matching view patterns, and transform the matches into // view wrappers. - editor.on( 'BeforeSetContent', function( e ) { - if ( ! e.content ) { + editor.on( 'BeforeSetContent', function( event ) { + if ( ! event.content ) { return; } - e.content = wp.mce.views.toViews( e.content ); + if ( ! event.initial ) { + wp.mce.views.unbind( editor ); + } + + event.content = wp.mce.views.toViews( event.content ); }); // When the editor's content has been updated and the DOM has been @@ -162,11 +173,7 @@ tinymce.PluginManager.add( 'wpview', function( editor ) { editor.on( 'SetContent', function( event ) { var body, padNode; - // don't (re-)render views if the format of the content is raw - // to avoid adding additional undo levels on undo/redo - if ( event.format !== 'raw' ) { - wp.mce.views.render(); - } + wp.mce.views.render(); // Add padding

if the noneditable node is last if ( event.load || ! event.set ) { @@ -175,7 +182,10 @@ tinymce.PluginManager.add( 'wpview', function( editor ) { if ( isView( body.lastChild ) ) { padNode = createPadNode(); body.appendChild( padNode ); - editor.selection.setCursorLocation( padNode, 0 ); + + if ( ! event.initial ) { + editor.selection.setCursorLocation( padNode, 0 ); + } } } });