diff --git a/wp-includes/js/mce-view.js b/wp-includes/js/mce-view.js index fc6284fd3e..f5c485770a 100644 --- a/wp-includes/js/mce-view.js +++ b/wp-includes/js/mce-view.js @@ -64,6 +64,8 @@ window.wp = window.wp || {}; * Returns the settings of a view type. * * @param {String} type The view type. + * + * @return {Function} The view constructor. */ get: function( type ) { return views[ type ]; @@ -142,6 +144,8 @@ window.wp = window.wp || {}; * @param {String} type The view type. * @param {String} text The textual representation of the view. * @param {Object} options Options. + * + * @return {wp.mce.View} The view instance. */ createInstance: function( type, text, options ) { var View = this.get( type ), @@ -163,10 +167,27 @@ window.wp = window.wp || {}; /** * Get a view instance. * - * @param {String} text The textual representation of the view. + * @param {(String|HTMLElement)} object The textual representation of the view or the view node. + * + * @return {wp.mce.View} The view instance or undefined. */ - getInstance: function( text ) { - return instances[ encodeURIComponent( text ) ]; + getInstance: function( object ) { + if ( typeof object === 'string' ) { + return instances[ encodeURIComponent( object ) ]; + } + + return instances[ $( object ).data( 'wpview-text' ) ]; + }, + + /** + * Given a view node, get the view's text. + * + * @param {HTMLElement} node The view node. + * + * @return {String} The textual representation of the view. + */ + getText: function( node ) { + return decodeURIComponent( $( node ).data( 'wpview-text' ) || '' ); }, /** @@ -188,8 +209,7 @@ window.wp = window.wp || {}; * @param {HTMLElement} node The view node to update. */ update: function( text, editor, node ) { - var oldText = decodeURIComponent( $( node ).data( 'wpview-text' ) ), - instance = this.getInstance( oldText ); + var instance = this.getInstance( node ); if ( instance ) { instance.update( text, editor, node ); @@ -203,14 +223,27 @@ window.wp = window.wp || {}; * @param {HTMLElement} node The view node to edit. */ edit: function( editor, node ) { - var text = decodeURIComponent( $( node ).data( 'wpview-text' ) ), - instance = this.getInstance( text ); + var instance = this.getInstance( node ); if ( instance && instance.edit ) { - instance.edit( text, function( text ) { + instance.edit( instance.text, function( text ) { instance.update( text, editor, node ); } ); } + }, + + /** + * Remove a given view node from the DOM. + * + * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in. + * @param {HTMLElement} node The view node to remove. + */ + remove: function( editor, node ) { + var instance = this.getInstance( node ); + + if ( instance ) { + instance.remove( editor, node ); + } } }; @@ -218,7 +251,7 @@ window.wp = window.wp || {}; * A Backbone-like View constructor intended for use when rendering a TinyMCE View. * The main difference is that the TinyMCE View is not tied to a particular DOM node. * - * @param {Object} Options. + * @param {Object} options Options. */ wp.mce.View = function( options ) { _.extend( this, options ); @@ -276,44 +309,23 @@ window.wp = window.wp || {}; if ( this.getContent() ) { this.setContent( this.getContent(), function( editor, node ) { - $( node ).data( 'rendered', true ); - this.bindNodes.apply( this, arguments ); + $( node ).data( 'rendered', true ).trigger( 'wp-mce-view-bind' ); }, force ? null : false ); } else { this.setLoader(); } }, - /** - * Binds a given rendered view node. - * Runs after a view node's content is added to the DOM. - * - * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in. - * @param {HTMLElement} node The view node. - * @param {HTMLElement} contentNode The view's content node. - */ - bindNodes: function( /* editor, node, contentNode */ ) {}, - /** * Unbinds all view nodes tied to this view instance. * Runs before their content is removed from the DOM. */ unbind: function() { - this.getNodes( function() { - this.unbindNodes.apply( this, arguments ); + this.getNodes( function( editor, node ) { + $( node ).trigger( 'wp-mce-view-unbind' ); }, true ); }, - /** - * Unbinds a given view node. - * Runs before the view node's content is removed from the DOM. - * - * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in. - * @param {HTMLElement} node The view node. - * @param {HTMLElement} contentNode The view's content node. - */ - unbindNodes: function( /* editor, node, contentNode */ ) {}, - /** * Gets all the TinyMCE editor instances that support views. * @@ -378,7 +390,7 @@ window.wp = window.wp || {}; */ replaceMarkers: function() { this.getMarkers( function( editor, node ) { - if ( $( node ).text() !== this.text ) { + if ( $( node ).html() !== this.text ) { editor.dom.setAttrib( node, 'data-wpview-marker', null ); return; } @@ -455,7 +467,7 @@ window.wp = window.wp || {}; var dom = editor.dom, styles = '', bodyClasses = editor.getBody().className || '', - iframe, iframeDoc, i, resize; + iframe, iframeDoc, observer, i, resize; content.innerHTML = ''; head = head || ''; @@ -547,10 +559,11 @@ window.wp = window.wp || {}; }; if ( MutationObserver ) { - new MutationObserver( _.debounce( function() { + observer = new MutationObserver( _.debounce( function() { resize(); - }, 100 ) ) - .observe( iframeDoc.body, { + }, 100 ) ); + + observer.observe( iframeDoc.body, { attributes: true, childList: true, subtree: true @@ -561,11 +574,18 @@ window.wp = window.wp || {}; } } - if ( importStyles ) { - editor.on( 'wp-body-class-change', function() { - iframeDoc.body.className = editor.getBody().className; - } ); + function classChange() { + iframeDoc.body.className = editor.getBody().className; } + + if ( importStyles ) { + editor.on( 'wp-body-class-change', classChange ); + } + + $( node ).one( 'wp-mce-view-unbind', function() { + observer.disconnect(); + editor.off( 'wp-body-class-change', classChange ); + } ); }, 50 ); callback && callback.apply( this, arguments ); @@ -631,6 +651,17 @@ window.wp = window.wp || {}; $( node ).data( 'rendered', false ); editor.dom.setAttrib( node, 'data-wpview-text', encodeURIComponent( text ) ); wp.mce.views.createInstance( this.type, text, this.match( text ).options ).render(); + }, + + /** + * Remove a given view node from the DOM. + * + * @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in. + * @param {HTMLElement} node The view node to remove. + */ + remove: function( editor, node ) { + $( node ).trigger( 'wp-mce-view-unbind' ); + editor.dom.remove( node ); } } ); } )( window, window.wp, window.jQuery ); @@ -662,6 +693,8 @@ window.wp = window.wp || {}; frame.on( 'close', function() { frame.detach(); } ); + + frame.open(); } }; diff --git a/wp-includes/js/mce-view.min.js b/wp-includes/js/mce-view.min.js index 02734e7eef..7290044463 100644 --- a/wp-includes/js/mce-view.min.js +++ b/wp-includes/js/mce-view.min.js @@ -1 +1 @@ -window.wp=window.wp||{},function(a,b,c){"use strict";var d={},e={};b.mce=b.mce||{},b.mce.views={register:function(a,c){d[a]=b.mce.View.extend(_.extend(c,{type:a}))},unregister:function(a){delete d[a]},get:function(a){return d[a]},unbind:function(){_.each(e,function(a){a.unbind()})},setMarkers:function(a){var b,c=[{content:a}],e=this;return _.each(d,function(a,d){b=c.slice(),c=[],_.each(b,function(b){var f,g=b.content;if(b.processed)return void c.push(b);for(;g&&(f=a.prototype.match(g));)f.index&&c.push({content:g.substring(0,f.index)}),e.createInstance(d,f.content,f.options),c.push({content:'
'+f.content+"
",processed:!0}),g=g.slice(f.index+f.content.length);g&&c.push({content:g})})}),_.pluck(c,"content").join("")},createInstance:function(a,b,c){var d=this.get(a),f=encodeURIComponent(b),g=this.getInstance(f);return g?g:(c=_.extend(c||{},{text:b,encodedText:f}),e[f]=new d(c))},getInstance:function(a){return e[encodeURIComponent(a)]},render:function(a){_.each(e,function(b){b.render(a)})},update:function(a,b,d){var e=decodeURIComponent(c(d).data("wpview-text")),f=this.getInstance(e);f&&f.update(a,b,d)},edit:function(a,b){var d=decodeURIComponent(c(b).data("wpview-text")),e=this.getInstance(d);e&&e.edit&&e.edit(d,function(c){e.update(c,a,b)})}},b.mce.View=function(a){_.extend(this,a),this.initialize()},b.mce.View.extend=Backbone.View.extend,_.extend(b.mce.View.prototype,{content:null,loader:!0,initialize:function(){},getContent:function(){return this.content},render:function(a){(this.loader||this.getContent())&&(a&&this.unbind(),this.replaceMarkers(),this.getContent()?this.setContent(this.getContent(),function(a,b){c(b).data("rendered",!0),this.bindNodes.apply(this,arguments)},a?null:!1):this.setLoader())},bindNodes:function(){},unbind:function(){this.getNodes(function(){this.unbindNodes.apply(this,arguments)},!0)},unbindNodes:function(){},getEditors:function(a){_.each(tinymce.editors,function(b){b.plugins.wpview&&a.call(this,b)},this)},getNodes:function(a,b){this.getEditors(function(d){var e=this;c(d.getBody()).find('[data-wpview-text="'+e.encodedText+'"]').filter(function(){var a;return null==b?!0:(a=c(this).data("rendered")===!0,b?a:!a)}).each(function(){a.call(e,d,this,c(this).find(".wpview-content").get(0))})})},getMarkers:function(a){this.getEditors(function(b){var d=this;c(b.getBody()).find('[data-wpview-marker="'+this.encodedText+'"]').each(function(){a.call(d,b,this)})})},replaceMarkers:function(){this.getMarkers(function(a,b){return c(b).text()!==this.text?void a.dom.setAttrib(b,"data-wpview-marker",null):void a.dom.replace(a.dom.createFragment('