/* global tinymce */
-window.wp = window.wp || {};
-
/*
* The TinyMCE view API.
*
* |- registered view
* | |- ...
*/
-( function( window, wp, $ ) {
+( function( window, wp, shortcode, $ ) {
'use strict';
var views = {},
// Add the processed piece for the match.
pieces.push( {
- content: '<p data-wpview-marker="' + instance.encodedText + '">' + text + '</p>',
+ content: instance.ignore ? text : '<p data-wpview-marker="' + instance.encodedText + '">' + text + '</p>',
processed: true
} );
/**
* Create a view instance.
*
- * @param {String} type The view type.
- * @param {String} text The textual representation of the view.
- * @param {Object} options Options.
+ * @param {String} type The view type.
+ * @param {String} text The textual representation of the view.
+ * @param {Object} options Options.
+ * @param {Boolean} force Recreate the instance. Optional.
*
* @return {wp.mce.View} The view instance.
*/
- createInstance: function( type, text, options ) {
+ createInstance: function( type, text, options, force ) {
var View = this.get( type ),
encodedText,
instance;
text = tinymce.DOM.decode( text );
- instance = this.getInstance( text );
- if ( instance ) {
- return instance;
+ if ( ! force ) {
+ instance = this.getInstance( text );
+
+ if ( instance ) {
+ return instance;
+ }
}
encodedText = encodeURIComponent( text );
* @param {String} text The new text.
* @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
* @param {HTMLElement} node The view node to update.
+ * @param {Boolean} force Recreate the instance. Optional.
*/
- update: function( text, editor, node ) {
+ update: function( text, editor, node, force ) {
var instance = this.getInstance( node );
if ( instance ) {
- instance.update( text, editor, node );
+ instance.update( text, editor, node, force );
}
},
var instance = this.getInstance( node );
if ( instance && instance.edit ) {
- instance.edit( instance.text, function( text ) {
- instance.update( text, editor, node );
+ instance.edit( instance.text, function( text, force ) {
+ instance.update( text, editor, node, force );
} );
}
},
/**
* Renders all view nodes tied to this view instance that are not yet rendered.
*
- * @param {String} content The content to render. Optional.
- * @param {Boolean} force Rerender all view nodes tied to this view instance.
+ * @param {String} content The content to render. Optional.
+ * @param {Boolean} force Rerender all view nodes tied to this view instance. Optional.
*/
render: function( content, force ) {
if ( content != null ) {
this.replaceMarkers();
if ( content ) {
- this.setContent( content, function( editor, node, contentNode ) {
+ this.setContent( content, function( editor, node ) {
$( node ).data( 'rendered', true );
- this.bindNode.call( this, editor, node, contentNode );
+ this.bindNode.call( this, editor, node );
}, force ? null : false );
} else {
this.setLoader();
* Runs before their content is removed from the DOM.
*/
unbind: function() {
- this.getNodes( function( editor, node, contentNode ) {
- this.unbindNode.call( this, editor, node, contentNode );
- $( node ).trigger( 'wp-mce-view-unbind' );
+ this.getNodes( function( editor, node ) {
+ this.unbindNode.call( this, editor, node );
}, true );
},
return rendered ? data : ! data;
} )
.each( function() {
- callback.call( self, editor, this, $( this ).find( '.wpview-content' ).get( 0 ) );
+ callback.call( self, editor, this, this /* back compat */ );
} );
} );
},
*/
replaceMarkers: function() {
this.getMarkers( function( editor, node ) {
+ var $viewNode;
+
if ( ! this.loader && $( node ).text() !== this.text ) {
editor.dom.setAttrib( node, 'data-wpview-marker', null );
return;
}
- editor.dom.replace(
- editor.dom.createFragment(
- '<div class="wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '">' +
- '<p class="wpview-selection-before">\u00a0</p>' +
- '<div class="wpview-body" contenteditable="false">' +
- '<div class="wpview-content wpview-type-' + this.type + '"></div>' +
- '</div>' +
- '<p class="wpview-selection-after">\u00a0</p>' +
- '</div>'
- ),
- node
+ $viewNode = editor.$(
+ '<div class="wpview wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '" contenteditable="false"></div>'
);
+
+ editor.$( node ).replaceWith( $viewNode );
} );
},
} else if ( _.isString( content ) && content.indexOf( '<script' ) !== -1 ) {
this.setIframes( '', content, callback, rendered );
} else {
- this.getNodes( function( editor, node, contentNode ) {
+ this.getNodes( function( editor, node ) {
content = content.body || content;
if ( content.indexOf( '<iframe' ) !== -1 ) {
- content += '<div class="wpview-overlay"></div>';
+ content += '<span class="mce-shim"></span>';
}
- contentNode.innerHTML = '';
- contentNode.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content );
+ editor.undoManager.transact( function() {
+ node.innerHTML = '';
+ node.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content );
+ editor.dom.add( node, 'span', { 'class': 'wpview-end' } );
+ } );
- callback && callback.call( this, editor, node, contentNode );
+ callback && callback.call( this, editor, node );
}, rendered );
}
},
* @param {Boolean} rendered Only set for (un)rendered nodes. Optional.
*/
setIframes: function( head, body, callback, rendered ) {
- var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
- self = this;
+ var self = this;
- this.getNodes( function( editor, node, contentNode ) {
+ this.getNodes( function( editor, node ) {
var dom = editor.dom,
styles = '',
bodyClasses = editor.getBody().className || '',
}
} );
+ if ( self.iframeHeight ) {
+ dom.add( node, 'span', {
+ 'data-mce-bogus': 1,
+ style: {
+ display: 'block',
+ width: '100%',
+ height: self.iframeHeight
+ }
+ }, '\u200B' );
+ }
+
// Seems the browsers need a bit of time to insert/set the view nodes,
// or the iframe will fail especially when switching Text => Visual.
setTimeout( function() {
- var iframe, iframeDoc, observer, i;
-
- contentNode.innerHTML = '';
+ var iframe, iframeWin, iframeDoc, MutationObserver, observer, i, block;
+
+ editor.undoManager.transact( function() {
+ node.innerHTML = '';
+
+ iframe = dom.add( node, 'iframe', {
+ /* jshint scripturl: true */
+ src: tinymce.Env.ie ? 'javascript:""' : '',
+ frameBorder: '0',
+ allowTransparency: 'true',
+ scrolling: 'no',
+ 'class': 'wpview-sandbox',
+ style: {
+ width: '100%',
+ display: 'block'
+ },
+ height: self.iframeHeight
+ } );
- iframe = dom.add( contentNode, 'iframe', {
- /* jshint scripturl: true */
- src: tinymce.Env.ie ? 'javascript:""' : '',
- frameBorder: '0',
- allowTransparency: 'true',
- scrolling: 'no',
- 'class': 'wpview-sandbox',
- style: {
- width: '100%',
- display: 'block'
- }
+ dom.add( node, 'span', { 'class': 'mce-shim' } );
+ dom.add( node, 'span', { 'class': 'wpview-end' } );
} );
- dom.add( contentNode, 'div', { 'class': 'wpview-overlay' } );
-
- iframeDoc = iframe.contentWindow.document;
+ // Bail if the iframe node is not attached to the DOM.
+ // Happens when the view is dragged in the editor.
+ // There is a browser restriction when iframes are moved in the DOM. They get emptied.
+ // The iframe will be rerendered after dropping the view node at the new location.
+ if ( ! iframe.contentWindow ) {
+ return;
+ }
+ iframeWin = iframe.contentWindow;
+ iframeDoc = iframeWin.document;
iframeDoc.open();
iframeDoc.write(
iframeDoc.close();
function resize() {
- var $iframe, iframeDocHeight;
+ var $iframe;
+
+ if ( block ) {
+ return;
+ }
// Make sure the iframe still exists.
if ( iframe.contentWindow ) {
$iframe = $( iframe );
- iframeDocHeight = $( iframeDoc.body ).height();
+ self.iframeHeight = $( iframeDoc.body ).height();
- if ( $iframe.height() !== iframeDocHeight ) {
- $iframe.height( iframeDocHeight );
+ if ( $iframe.height() !== self.iframeHeight ) {
+ $iframe.height( self.iframeHeight );
editor.nodeChanged();
}
}
}
- $( iframe.contentWindow ).on( 'load', resize );
+ if ( self.iframeHeight ) {
+ block = true;
+
+ setTimeout( function() {
+ block = false;
+ resize();
+ }, 3000 );
+ }
+
+ $( iframeWin ).on( 'load', resize );
+
+ MutationObserver = iframeWin.MutationObserver || iframeWin.WebKitMutationObserver || iframeWin.MozMutationObserver;
if ( MutationObserver ) {
observer = new MutationObserver( _.debounce( resize, 100 ) );
childList: true,
subtree: true
} );
-
- $( node ).one( 'wp-mce-view-unbind', function() {
- observer.disconnect();
- } );
} else {
for ( i = 1; i < 6; i++ ) {
setTimeout( resize, i * 700 );
}
}
- function classChange() {
- iframeDoc.body.className = editor.getBody().className;
- }
-
- editor.on( 'wp-body-class-change', classChange );
-
- $( node ).one( 'wp-mce-view-unbind', function() {
- editor.off( 'wp-body-class-change', classChange );
- } );
-
- callback && callback.call( self, editor, node, contentNode );
+ callback && callback.call( self, editor, node );
}, 50 );
}, rendered );
},
* Sets an error for all view nodes tied to this view instance.
*
* @param {String} message The error message to set.
- * @param {String} dashicon A dashicon ID (optional). {@link https://developer.wordpress.org/resource/dashicons/}
+ * @param {String} dashicon A dashicon ID. Optional. {@link https://developer.wordpress.org/resource/dashicons/}
*/
setError: function( message, dashicon ) {
this.setContent(
* @return {Object}
*/
match: function( content ) {
- var match = wp.shortcode.next( this.type, content );
+ var match = shortcode.next( this.type, content );
if ( match ) {
return {
* @param {String} text The new text.
* @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
* @param {HTMLElement} node The view node to update.
+ * @param {Boolean} force Recreate the instance. Optional.
*/
- update: function( text, editor, node ) {
+ update: function( text, editor, node, force ) {
_.find( views, function( view, type ) {
var match = view.prototype.match( text );
if ( match ) {
$( node ).data( 'rendered', false );
editor.dom.setAttrib( node, 'data-wpview-text', encodeURIComponent( text ) );
- wp.mce.views.createInstance( type, text, match.options ).render();
+ wp.mce.views.createInstance( type, text, match.options, force ).render();
editor.focus();
return true;
* @param {HTMLElement} node The view node to remove.
*/
remove: function( editor, node ) {
- this.unbindNode.call( this, editor, node, $( node ).find( '.wpview-content' ).get( 0 ) );
- $( node ).trigger( 'wp-mce-view-unbind' );
+ this.unbindNode.call( this, editor, node );
editor.dom.remove( node );
editor.focus();
}
} );
-} )( window, window.wp, window.jQuery );
+} )( window, window.wp, window.wp.shortcode, window.jQuery );
/*
* The WordPress core TinyMCE views.
* Views for the gallery, audio, video, playlist and embed shortcodes,
* and a view for embeddable URLs.
*/
-( function( window, views, $ ) {
- var postID = $( '#post_ID' ).val() || 0,
- media, gallery, av, embed;
+( function( window, views, media, $ ) {
+ var base, gallery, av, embed,
+ schema, parser, serializer;
+
+ function verifyHTML( string ) {
+ var settings = {};
+
+ if ( ! window.tinymce ) {
+ return string.replace( /<[^>]+>/g, '' );
+ }
+
+ if ( ! string || ( string.indexOf( '<' ) === -1 && string.indexOf( '>' ) === -1 ) ) {
+ return string;
+ }
+
+ schema = schema || new window.tinymce.html.Schema( settings );
+ parser = parser || new window.tinymce.html.DomParser( settings, schema );
+ serializer = serializer || new window.tinymce.html.Serializer( settings, schema );
+
+ return serializer.serialize( parser.parse( string, { forced_root_block: false } ) );
+ }
- media = {
+ base = {
state: [],
edit: function( text, update ) {
- var media = wp.media[ this.type ],
- frame = media.edit( text );
+ var type = this.type,
+ frame = media[ type ].edit( text );
this.pausePlayers && this.pausePlayers();
_.each( this.state, function( state ) {
frame.state( state ).on( 'update', function( selection ) {
- update( media.shortcode( selection ).string() );
+ update( media[ type ].shortcode( selection ).string(), type === 'gallery' );
} );
} );
}
};
- gallery = _.extend( {}, media, {
+ gallery = _.extend( {}, base, {
state: [ 'gallery-edit' ],
- template: wp.media.template( 'editor-gallery' ),
+ template: media.template( 'editor-gallery' ),
initialize: function() {
- var attachments = wp.media.gallery.attachments( this.shortcode, postID ),
+ var attachments = media.gallery.attachments( this.shortcode, media.view.settings.post.id ),
attrs = this.shortcode.attrs.named,
self = this;
} );
self.render( self.template( {
+ verifyHTML: verifyHTML,
attachments: attachments,
- columns: attrs.columns ? parseInt( attrs.columns, 10 ) : wp.media.galleryDefaults.columns
+ columns: attrs.columns ? parseInt( attrs.columns, 10 ) : media.galleryDefaults.columns
} ) );
} )
.fail( function( jqXHR, textStatus ) {
}
} );
- av = _.extend( {}, media, {
+ av = _.extend( {}, base, {
action: 'parse-media-shortcode',
initialize: function() {
if ( this.url ) {
this.loader = false;
- this.shortcode = wp.media.embed.shortcode( {
+ this.shortcode = media.embed.shortcode( {
url: this.text
} );
}
wp.ajax.post( this.action, {
- post_ID: postID,
+ post_ID: media.view.settings.post.id,
type: this.shortcode.tag,
shortcode: this.shortcode.string()
} )
} )
.fail( function( response ) {
if ( self.url ) {
+ self.ignore = true;
self.removeMarkers();
} else {
self.setError( response.message || response.statusText, 'admin-media' );
action: 'parse-embed',
edit: function( text, update ) {
- var media = wp.media.embed,
- frame = media.edit( text, this.url ),
+ var frame = media.embed.edit( text, this.url ),
self = this;
this.pausePlayers();
if ( self.url ) {
update( data.url );
} else {
- update( media.shortcode( data ).string() );
+ update( media.embed.shortcode( data ).string() );
}
} );
}
}
} ) );
-} )( window, window.wp.mce.views, window.jQuery );
+} )( window, window.wp.mce.views, window.wp.media, window.jQuery );