diff -r 490d5cc509ed -r cf61fcea0001 wp/wp-includes/js/mce-view.js --- a/wp/wp-includes/js/mce-view.js Tue Jun 09 11:14:17 2015 +0000 +++ b/wp/wp-includes/js/mce-view.js Mon Oct 14 17:39:30 2019 +0200 @@ -1,7 +1,5 @@ /* global tinymce */ -window.wp = window.wp || {}; - /* * The TinyMCE view API. * @@ -24,7 +22,7 @@ * |- registered view * | |- ... */ -( function( window, wp, $ ) { +( function( window, wp, shortcode, $ ) { 'use strict'; var views = {}, @@ -87,14 +85,14 @@ * and creates a new instance for every match. * * @param {String} content The string to scan. + * @param {tinymce.Editor} editor The editor. * * @return {String} The string with markers. */ - setMarkers: function( content ) { + setMarkers: function( content, editor ) { var pieces = [ { content: content } ], self = this, - instance, - current; + instance, current; _.each( views, function( view, type ) { current = pieces.slice(); @@ -102,7 +100,7 @@ _.each( current, function( piece ) { var remaining = piece.content, - result; + result, text; // Ignore processed pieces, but retain their location. if ( piece.processed ) { @@ -118,11 +116,13 @@ pieces.push( { content: remaining.substring( 0, result.index ) } ); } + result.options.editor = editor; instance = self.createInstance( type, result.content, result.options ); + text = instance.loader ? '.' : instance.text; // Add the processed piece for the match. pieces.push( { - content: '

' + instance.text + '

', + content: instance.ignore ? text : '

' + text + '

', processed: true } ); @@ -138,31 +138,43 @@ } ); } ); - return _.pluck( pieces, 'content' ).join( '' ); + content = _.pluck( pieces, 'content' ).join( '' ); + return content.replace( /

\s*

' ); }, /** * 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 ), - encodedText = encodeURIComponent( text ), - instance = this.getInstance( encodedText ); + if ( text.indexOf( '[' ) !== -1 && text.indexOf( ']' ) !== -1 ) { + // Looks like a shortcode? Remove any line breaks from inside of shortcodes + // or autop will replace them with

and
later and the string won't match. + text = text.replace( /\[[^\]]+\]/g, function( match ) { + return match.replace( /[\r\n]/g, '' ); + }); + } - if ( instance ) { - return instance; + if ( ! force ) { + instance = this.getInstance( text ); + + if ( instance ) { + return instance; + } } + encodedText = encodeURIComponent( text ); + options = _.extend( options || {}, { text: text, encodedText: encodedText @@ -204,7 +216,7 @@ */ render: function( force ) { _.each( instances, function( instance ) { - instance.render( force ); + instance.render( null, force ); } ); }, @@ -214,12 +226,13 @@ * @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 ); } }, @@ -233,8 +246,8 @@ 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 ); } ); } }, @@ -267,7 +280,7 @@ wp.mce.View.extend = Backbone.View.extend; - _.extend( wp.mce.View.prototype, { + _.extend( wp.mce.View.prototype, /** @lends wp.mce.View.prototype */{ /** * The content. @@ -289,7 +302,7 @@ initialize: function() {}, /** - * Retuns the content to render in the view node. + * Returns the content to render in the view node. * * @return {*} */ @@ -300,8 +313,8 @@ /** * 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 ) { @@ -322,9 +335,9 @@ 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(); @@ -346,9 +359,8 @@ * 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 ); }, @@ -389,7 +401,7 @@ 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 */ ); } ); } ); }, @@ -416,23 +428,26 @@ */ replaceMarkers: function() { this.getMarkers( function( editor, node ) { - if ( $( node ).text() !== this.text ) { + var selected = node === editor.selection.getNode(); + var $viewNode; + + if ( ! this.loader && $( node ).text() !== tinymce.DOM.decode( this.text ) ) { editor.dom.setAttrib( node, 'data-wpview-marker', null ); return; } - editor.dom.replace( - editor.dom.createFragment( - '

' + - '

\u00a0

' + - '
' + - '
' + - '
' + - '

\u00a0

' + - '
' - ), - node + $viewNode = editor.$( + '
' ); + + editor.$( node ).replaceWith( $viewNode ); + + if ( selected ) { + setTimeout( function() { + editor.selection.select( $viewNode[0] ); + editor.selection.collapse(); + } ); + } } ); }, @@ -453,22 +468,25 @@ * @param {Boolean} rendered Only set for (un)rendered nodes. Optional. */ setContent: function( content, callback, rendered ) { - if ( _.isObject( content ) && content.body.indexOf( '/g, '>' ); + } ); + } + + this.getNodes( function( editor, node ) { var dom = editor.dom, styles = '', bodyClasses = editor.getBody().className || '', - editorHead = editor.getDoc().getElementsByTagName( 'head' )[0]; + editorHead = editor.getDoc().getElementsByTagName( 'head' )[0], + iframe, iframeWin, iframeDoc, MutationObserver, observer, i, block; tinymce.each( dom.$( 'link[rel="stylesheet"]', editorHead ), function( link ) { if ( link.href && link.href.indexOf( 'skins/lightgray/content.min.css' ) === -1 && @@ -499,14 +525,21 @@ } } ); - // 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; + if ( self.iframeHeight ) { + dom.add( node, 'span', { + 'data-mce-bogus': 1, + style: { + display: 'block', + width: '100%', + height: self.iframeHeight + } + }, '\u200B' ); + } - contentNode.innerHTML = ''; + editor.undoManager.transact( function() { + node.innerHTML = ''; - iframe = dom.add( contentNode, 'iframe', { + iframe = dom.add( node, 'iframe', { /* jshint scripturl: true */ src: tinymce.Env.ie ? 'javascript:""' : '', frameBorder: '0', @@ -516,105 +549,137 @@ style: { width: '100%', display: 'block' - } + }, + height: self.iframeHeight } ); - dom.add( contentNode, 'div', { 'class': 'wpview-overlay' } ); + dom.add( node, 'span', { 'class': 'mce-shim' } ); + dom.add( node, 'span', { 'class': 'wpview-end' } ); + } ); - 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; + } - iframeDoc.open(); + iframeWin = iframe.contentWindow; + iframeDoc = iframeWin.document; + iframeDoc.open(); - iframeDoc.write( - '' + - '' + - '' + - '' + - head + - styles + - '' + - '' + - '' + - body + - '' + - '' - ); + iframeDoc.write( + '' + + '' + + '' + + '' + + head + + styles + + '' + + '' + + '' + + body + + '' + + '' + ); - iframeDoc.close(); + iframeDoc.close(); - function resize() { - var $iframe, iframeDocHeight; + function resize() { + var $iframe; + + if ( block ) { + return; + } - // Make sure the iframe still exists. - if ( iframe.contentWindow ) { - $iframe = $( iframe ); - iframeDocHeight = $( iframeDoc.body ).height(); + // Make sure the iframe still exists. + if ( iframe.contentWindow ) { + $iframe = $( iframe ); + self.iframeHeight = $( iframeDoc.body ).height(); - if ( $iframe.height() !== iframeDocHeight ) { - $iframe.height( iframeDocHeight ); - editor.nodeChanged(); - } + if ( $iframe.height() !== self.iframeHeight ) { + $iframe.height( self.iframeHeight ); + editor.nodeChanged(); } } + } - $( iframe.contentWindow ).on( 'load', resize ); + if ( self.iframeHeight ) { + block = true; - if ( MutationObserver ) { - observer = new MutationObserver( _.debounce( resize, 100 ) ); + setTimeout( function() { + block = false; + resize(); + }, 3000 ); + } + + function reload() { + if ( ! editor.isHidden() ) { + $( node ).data( 'rendered', null ); - observer.observe( iframeDoc.body, { - attributes: true, - childList: true, - subtree: true + setTimeout( function() { + wp.mce.views.render(); } ); + } + } - $( node ).one( 'wp-mce-view-unbind', function() { - observer.disconnect(); - } ); + function addObserver() { + observer = new MutationObserver( _.debounce( resize, 100 ) ); + + observer.observe( iframeDoc.body, { + attributes: true, + childList: true, + subtree: true + } ); + } + + $( iframeWin ).on( 'load', resize ).on( 'unload', reload ); + + MutationObserver = iframeWin.MutationObserver || iframeWin.WebKitMutationObserver || iframeWin.MozMutationObserver; + + if ( MutationObserver ) { + if ( ! iframeDoc.body ) { + iframeDoc.addEventListener( 'DOMContentLoaded', addObserver, false ); } else { - for ( i = 1; i < 6; i++ ) { - setTimeout( resize, i * 700 ); - } + addObserver(); } - - function classChange() { - iframeDoc.body.className = editor.getBody().className; + } else { + for ( i = 1; i < 6; i++ ) { + setTimeout( resize, i * 700 ); } + } - 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 ); - }, 50 ); + callback && callback.call( self, editor, node ); }, rendered ); }, /** * Sets a loader for all view nodes tied to this view instance. */ - setLoader: function() { + setLoader: function( dashicon ) { this.setContent( '
' + - '
' + + '
' + '
' + '
' ); @@ -624,7 +689,7 @@ * 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( @@ -643,7 +708,7 @@ * @return {Object} */ match: function( content ) { - var match = wp.shortcode.next( this.type, content ); + var match = shortcode.next( this.type, content ); if ( match ) { return { @@ -662,15 +727,19 @@ * @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.selection.select( node ); + editor.nodeChanged(); editor.focus(); return true; @@ -685,35 +754,52 @@ * @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, '' ); + } - media = { + 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 } ) ); + } + + 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' ); } ); } ); @@ -725,12 +811,12 @@ } }; - 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; @@ -751,8 +837,9 @@ } ); 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 ) { @@ -761,29 +848,36 @@ } } ); - av = _.extend( {}, media, { + av = _.extend( {}, base, { action: 'parse-media-shortcode', initialize: function() { - var self = this; + var self = this, maxwidth = null; if ( this.url ) { this.loader = false; - this.shortcode = wp.media.embed.shortcode( { + this.shortcode = media.embed.shortcode( { url: this.text } ); } + // Obtain the target width for the embed. + if ( self.editor ) { + maxwidth = self.editor.getBody().clientWidth; + } + wp.ajax.post( this.action, { - post_ID: postID, + post_ID: media.view.settings.post.id, type: this.shortcode.tag, - shortcode: this.shortcode.string() + shortcode: this.shortcode.string(), + maxwidth: maxwidth } ) .done( function( response ) { self.render( response ); } ) .fail( function( response ) { if ( self.url ) { + self.ignore = true; self.removeMarkers(); } else { self.setError( response.message || response.statusText, 'admin-media' ); @@ -816,8 +910,7 @@ 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(); @@ -834,7 +927,7 @@ if ( self.url ) { update( data.url ); } else { - update( media.shortcode( data ).string() ); + update( media.embed.shortcode( data ).string() ); } } ); @@ -878,4 +971,4 @@ } } } ) ); -} )( window, window.wp.mce.views, window.jQuery ); +} )( window, window.wp.mce.views, window.wp.media, window.jQuery );