diff --git a/wp-includes/class-wp-editor.php b/wp-includes/class-wp-editor.php index af2a071c6c..d209c588b7 100644 --- a/wp-includes/class-wp-editor.php +++ b/wp-includes/class-wp-editor.php @@ -242,6 +242,7 @@ final class _WP_Editors { 'wpgallery', 'wplink', 'wpdialogs', + 'wpview', ) ) ); if ( ( $key = array_search( 'spellchecker', $plugins ) ) !== false ) { @@ -501,6 +502,9 @@ final class _WP_Editors { if ( self::$has_medialib ) { add_thickbox(); wp_enqueue_script('media-upload'); + + if ( self::$has_tinymce ) + wp_enqueue_script('mce-view'); } } diff --git a/wp-includes/js/mce-view.js b/wp-includes/js/mce-view.js index 912c4c7ce4..0881680b77 100644 --- a/wp-includes/js/mce-view.js +++ b/wp-includes/js/mce-view.js @@ -1,173 +1,95 @@ +/* global tinymce */ + // Ensure the global `wp` object exists. window.wp = window.wp || {}; (function($){ var views = {}, - instances = {}; + instances = {}, + media = wp.media, + viewOptions = ['encodedText']; // Create the `wp.mce` object if necessary. wp.mce = wp.mce || {}; - // wp.mce.view - // ----------- - // A set of utilities that simplifies adding custom UI within a TinyMCE editor. - // At its core, it serves as a series of converters, transforming text to a - // custom UI, and back again. - wp.mce.view = { - // ### defaults - defaults: { - // The default properties used for objects with the `pattern` key in - // `wp.mce.view.add()`. - pattern: { - view: Backbone.View, - text: function( instance ) { - return instance.options.original; - }, + /** + * wp.mce.View + * + * 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. + */ + wp.mce.View = function( options ) { + options || (options = {}); + _.extend(this, _.pick(options, viewOptions)); + this.initialize.apply(this, arguments); + }; - toView: function( content ) { - if ( ! this.pattern ) - return; - - this.pattern.lastIndex = 0; - var match = this.pattern.exec( content ); - - if ( ! match ) - return; - - return { - index: match.index, - content: match[0], - options: { - original: match[0], - results: match - } - }; + _.extend( wp.mce.View.prototype, { + initialize: function() {}, + html: function() {}, + render: function() { + var html = this.getHtml(); + // Search all tinymce editor instances and update the placeholders + _.each( tinymce.editors, function( editor ) { + var doc; + if ( editor.plugins.wpview ) { + doc = editor.getDoc(); + $( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).html( html ); } - }, + }, this ); + } + } ); - // The default properties used for objects with the `shortcode` key in - // `wp.mce.view.add()`. - shortcode: { - view: Backbone.View, - text: function( instance ) { - return instance.options.shortcode.string(); - }, + // take advantage of the Backbone extend method + wp.mce.View.extend = Backbone.View.extend; - toView: function( content ) { - var match = wp.shortcode.next( this.shortcode, content ); + /** + * wp.mce.views + * + * A set of utilities that simplifies adding custom UI within a TinyMCE editor. + * At its core, it serves as a series of converters, transforming text to a + * custom UI, and back again. + */ + wp.mce.views = { - if ( ! match ) - return; - - return { - index: match.index, - content: match.content, - options: { - shortcode: match.shortcode - } - }; - } - } + /** + * wp.mce.views.register( type, view ) + * + * Registers a new TinyMCE view. + * + * @param type + * @param constructor + * + */ + register: function( type, constructor ) { + views[ type ] = constructor; }, - // ### add( id, options ) - // Registers a new TinyMCE view. - // - // Accepts a unique `id` and an `options` object. - // - // `options` accepts the following properties: - // - // * `pattern` is the regular expression used to scan the content and - // detect matching views. - // - // * `view` is a `Backbone.View` constructor. If a plain object is - // provided, it will automatically extend the parent constructor - // (usually `Backbone.View`). Views are instantiated when the `pattern` - // is successfully matched. The instance's `options` object is provided - // with the `original` matched value, the match `results` including - // capture groups, and the `viewType`, which is the constructor's `id`. - // - // * `extend` an existing view by passing in its `id`. The current - // view will inherit all properties from the parent view, and if - // `view` is set to a plain object, it will extend the parent `view` - // constructor. - // - // * `text` is a method that accepts an instance of the `view` - // constructor and transforms it into a text representation. - add: function( id, options ) { - var parent, remove, base, properties; - - // Fetch the parent view or the default options. - if ( options.extend ) - parent = wp.mce.view.get( options.extend ); - else if ( options.shortcode ) - parent = wp.mce.view.defaults.shortcode; - else - parent = wp.mce.view.defaults.pattern; - - // Extend the `options` object with the parent's properties. - _.defaults( options, parent ); - options.id = id; - - // Create properties used to enhance the view for use in TinyMCE. - properties = { - // Ensure the wrapper element and references to the view are - // removed. Otherwise, removed views could randomly restore. - remove: function() { - delete instances[ this.el.id ]; - this.$el.parent().remove(); - - // Trigger the inherited `remove` method. - if ( remove ) - remove.apply( this, arguments ); - - return this; - } - }; - - // If the `view` provided was an object, use the parent's - // `view` constructor as a base. If a `view` constructor - // was provided, treat that as the base. - if ( _.isFunction( options.view ) ) { - base = options.view; - } else { - base = parent.view; - remove = options.view.remove; - _.defaults( properties, options.view ); - } - - // If there's a `remove` method on the `base` view that wasn't - // created by this method, inherit it. - if ( ! remove && ! base._mceview ) - remove = base.prototype.remove; - - // Automatically create the new `Backbone.View` constructor. - options.view = base.extend( properties, { - // Flag that the new view has been created by `wp.mce.view`. - _mceview: true - }); - - views[ id ] = options; + /** + * wp.mce.views.get( id ) + * + * Returns a TinyMCE view constructor. + */ + get: function( type ) { + return views[ type ]; }, - // ### get( id ) - // Returns a TinyMCE view options object. - get: function( id ) { - return views[ id ]; + /** + * wp.mce.views.unregister( type ) + * + * Unregisters a TinyMCE view. + */ + unregister: function( type ) { + delete views[ type ]; }, - // ### remove( id ) - // Unregisters a TinyMCE view. - remove: function( id ) { - delete views[ id ]; - }, - - // ### toViews( content ) - // Scans a `content` string for each view's pattern, replacing any - // matches with wrapper elements, and creates a new view instance for - // every match. - // - // To render the views, call `wp.mce.view.render( scope )`. + /** + * toViews( content ) + * Scans a `content` string for each view's pattern, replacing any + * matches with wrapper elements, and creates a new instance for + * every match, which triggers the related data to be fetched. + * + */ toViews: function( content ) { var pieces = [ { content: content } ], current; @@ -190,12 +112,13 @@ window.wp = window.wp || {}; // and slicing the string as we go. while ( remaining && (result = view.toView( remaining )) ) { // Any text before the match becomes an unprocessed piece. - if ( result.index ) + if ( result.index ) { pieces.push({ content: remaining.substring( 0, result.index ) }); + } // Add the processed piece for the match. pieces.push({ - content: wp.mce.view.toView( viewType, result.options ), + content: wp.mce.views.toView( viewType, result.content, result.options ), processed: true }); @@ -205,145 +128,178 @@ window.wp = window.wp || {}; // There are no additional matches. If any content remains, // add it as an unprocessed piece. - if ( remaining ) + if ( remaining ) { pieces.push({ content: remaining }); + } }); }); return _.pluck( pieces, 'content' ).join(''); }, - toView: function( viewType, options ) { - var view = wp.mce.view.get( viewType ), - instance, id; + /** + * Create a placeholder for a particular view type + * + * @param viewType + * @param text + * @param options + * + */ + toView: function( viewType, text, options ) { + var view = wp.mce.views.get( viewType ), + encodedText = window.encodeURIComponent( text ), + instance, viewOptions; - if ( ! view ) - return ''; - // Create a new view instance. - instance = new view.view( _.extend( options || {}, { - viewType: viewType - }) ); + if ( ! view ) { + return text; + } - // Use the view's `id` if it already exists. Otherwise, - // create a new `id`. - id = instance.el.id = instance.el.id || _.uniqueId('__wpmce-'); - instances[ id ] = instance; - - // Create a dummy `$wrapper` property to allow `$wrapper` to be - // called in the view's `render` method without a conditional. - instance.$wrapper = $(); + if ( ! wp.mce.views.getInstance( encodedText ) ) { + viewOptions = options; + viewOptions.encodedText = encodedText; + instance = new view.View( viewOptions ); + instances[ encodedText ] = instance; + } return wp.html.string({ - // If the view is a span, wrap it in a span. - tag: 'span' === instance.tagName ? 'span' : 'div', + tag: 'div', attrs: { - 'class': 'wp-view-wrap wp-view-type-' + viewType, - 'data-wp-view': id, - 'contenteditable': false - } + 'class': 'wpview-wrap wpview-type-' + viewType, + 'data-wpview-text': encodedText, + 'data-wpview-type': viewType, + 'contenteditable': 'false' + }, + + content: '\u00a0' }); }, - // ### render( scope ) - // Renders any view instances inside a DOM node `scope`. - // - // View instances are detected by the presence of wrapper elements. - // To generate wrapper elements, pass your content through - // `wp.mce.view.toViews( content )`. - render: function( scope ) { - $( '.wp-view-wrap', scope ).each( function() { - var wrapper = $(this), - view = wp.mce.view.instance( this ); + /** + * Refresh views after an update is made + * + * @param view {object} being refreshed + * @param text {string} textual representation of the view + */ + refreshView: function( view, text ) { + var encodedText = window.encodeURIComponent( text ), + viewOptions, + result, instance; - if ( ! view ) - return; + instance = wp.mce.views.getInstance( encodedText ); - // Link the real wrapper to the view. - view.$wrapper = wrapper; - // Render the view. - view.render(); - // Detach the view element to ensure events are not unbound. - view.$el.detach(); + if ( ! instance ) { + result = view.toView( text ); + viewOptions = result.options; + viewOptions.encodedText = encodedText; + instance = new view.View( viewOptions ); + instances[ encodedText ] = instance; + } - // Empty the wrapper, attach the view element to the wrapper, - // and add an ending marker to the wrapper to help regexes - // scan the HTML string. - wrapper.empty().append( view.el ).append(''); - }); + wp.mce.views.render(); }, - // ### toText( content ) - // Scans an HTML `content` string and replaces any view instances with - // their respective text representations. - toText: function( content ) { - return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) { - var instance = instances[ id ], - view; - - if ( instance ) - view = wp.mce.view.get( instance.options.viewType ); - - return instance && view ? view.text( instance ) : ''; - }); + getInstance: function( encodedText ) { + return instances[ encodedText ]; }, - // ### Remove internal TinyMCE attributes. - removeInternalAttrs: function( attrs ) { - var result = {}; - _.each( attrs, function( value, attr ) { - if ( -1 === attr.indexOf('data-mce') ) - result[ attr ] = value; - }); - return result; + /** + * render( scope ) + * + * Renders any view instances inside a DOM node `scope`. + * + * View instances are detected by the presence of wrapper elements. + * To generate wrapper elements, pass your content through + * `wp.mce.view.toViews( content )`. + */ + render: function() { + _.each( instances, function( instance ) { + instance.render(); + } ); }, - // ### Parse an attribute string and removes internal TinyMCE attributes. - attrs: function( content ) { - return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) ); - }, + edit: function( node ) { + var viewType = $( node ).data('wpview-type'), + view = wp.mce.views.get( viewType ); - // ### instance( scope ) - // - // Accepts a MCE view wrapper `node` (i.e. a node with the - // `wp-view-wrap` class). - instance: function( node ) { - var id = $( node ).data('wp-view'); - - if ( id ) - return instances[ id ]; - }, - - // ### Select a view. - // - // Accepts a MCE view wrapper `node` (i.e. a node with the - // `wp-view-wrap` class). - select: function( node ) { - var $node = $(node); - - // Bail if node is already selected. - if ( $node.hasClass('selected') ) - return; - - $node.addClass('selected'); - $( node.firstChild ).trigger('select'); - }, - - // ### Deselect a view. - // - // Accepts a MCE view wrapper `node` (i.e. a node with the - // `wp-view-wrap` class). - deselect: function( node ) { - var $node = $(node); - - // Bail if node is already selected. - if ( ! $node.hasClass('selected') ) - return; - - $node.removeClass('selected'); - $( node.firstChild ).trigger('deselect'); + if ( view ) { + view.edit( node ); + } } }; -}(jQuery)); \ No newline at end of file + wp.mce.gallery = { + shortcode: 'gallery', + toView: function( content ) { + var match = wp.shortcode.next( this.shortcode, content ); + + if ( ! match ) { + return; + } + + return { + index: match.index, + content: match.content, + options: { + shortcode: match.shortcode + } + }; + }, + View: wp.mce.View.extend({ + className: 'editor-gallery', + template: media.template('editor-gallery'), + + // The fallback post ID to use as a parent for galleries that don't + // specify the `ids` or `include` parameters. + // + // Uses the hidden input on the edit posts page by default. + postID: $('#post_ID').val(), + + initialize: function( options ) { + this.shortcode = options.shortcode; + this.fetch(); + }, + + fetch: function() { + this.attachments = wp.media.gallery.attachments( this.shortcode, this.postID ); + this.attachments.more().done( _.bind( this.render, this ) ); + }, + + getHtml: function() { + var attrs = this.shortcode.attrs.named, + options; + + if ( ! this.attachments.length ) { + return; + } + + options = { + attachments: this.attachments.toJSON(), + columns: attrs.columns ? parseInt( attrs.columns, 10 ) : 3 + }; + + return this.template( options ); + + } + }), + + edit: function( node ) { + var gallery = wp.media.gallery, + self = this, + frame, data; + + data = window.decodeURIComponent( $( node ).data('wpview-text') ); + frame = gallery.edit( data ); + + frame.state('gallery-edit').on( 'update', function( selection ) { + var shortcode = gallery.shortcode( selection ).string(); + $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) ); + wp.mce.views.refreshView( self, shortcode ); + frame.detach(); + }); + } + + }; + wp.mce.views.register( 'gallery', wp.mce.gallery ); +}(jQuery)); diff --git a/wp-includes/js/mce-view.min.js b/wp-includes/js/mce-view.min.js index 20bd2d6b88..12d09cc560 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){var b={},c={};wp.mce=wp.mce||{},wp.mce.view={defaults:{pattern:{view:Backbone.View,text:function(a){return a.options.original},toView:function(a){if(this.pattern){this.pattern.lastIndex=0;var b=this.pattern.exec(a);if(b)return{index:b.index,content:b[0],options:{original:b[0],results:b}}}}},shortcode:{view:Backbone.View,text:function(a){return a.options.shortcode.string()},toView:function(a){var b=wp.shortcode.next(this.shortcode,a);if(b)return{index:b.index,content:b.content,options:{shortcode:b.shortcode}}}}},add:function(a,d){var e,f,g,h;e=d.extend?wp.mce.view.get(d.extend):d.shortcode?wp.mce.view.defaults.shortcode:wp.mce.view.defaults.pattern,_.defaults(d,e),d.id=a,h={remove:function(){return delete c[this.el.id],this.$el.parent().remove(),f&&f.apply(this,arguments),this}},_.isFunction(d.view)?g=d.view:(g=e.view,f=d.view.remove,_.defaults(h,d.view)),f||g._mceview||(f=g.prototype.remove),d.view=g.extend(h,{_mceview:!0}),b[a]=d},get:function(a){return b[a]},remove:function(a){delete b[a]},toViews:function(a){var c,d=[{content:a}];return _.each(b,function(a,b){c=d.slice(),d=[],_.each(c,function(c){var e,f=c.content;if(c.processed)return void d.push(c);for(;f&&(e=a.toView(f));)e.index&&d.push({content:f.substring(0,e.index)}),d.push({content:wp.mce.view.toView(b,e.options),processed:!0}),f=f.slice(e.index+e.content.length);f&&d.push({content:f})})}),_.pluck(d,"content").join("")},toView:function(b,d){var e,f,g=wp.mce.view.get(b);return g?(e=new g.view(_.extend(d||{},{viewType:b})),f=e.el.id=e.el.id||_.uniqueId("__wpmce-"),c[f]=e,e.$wrapper=a(),wp.html.string({tag:"span"===e.tagName?"span":"div",attrs:{"class":"wp-view-wrap wp-view-type-"+b,"data-wp-view":f,contenteditable:!1}})):""},render:function(b){a(".wp-view-wrap",b).each(function(){var b=a(this),c=wp.mce.view.instance(this);c&&(c.$wrapper=b,c.render(),c.$el.detach(),b.empty().append(c.el).append(''))})},toText:function(a){return a.replace(/<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g,function(a,b){var d,e=c[b];return e&&(d=wp.mce.view.get(e.options.viewType)),e&&d?d.text(e):""})},removeInternalAttrs:function(a){var b={};return _.each(a,function(a,c){-1===c.indexOf("data-mce")&&(b[c]=a)}),b},attrs:function(a){return wp.mce.view.removeInternalAttrs(wp.html.attrs(a))},instance:function(b){var d=a(b).data("wp-view");return d?c[d]:void 0},select:function(b){var c=a(b);c.hasClass("selected")||(c.addClass("selected"),a(b.firstChild).trigger("select"))},deselect:function(b){var c=a(b);c.hasClass("selected")&&(c.removeClass("selected"),a(b.firstChild).trigger("deselect"))}}}(jQuery); \ No newline at end of file +window.wp=window.wp||{},function(a){var b={},c={},d=wp.media,e=["encodedText"];wp.mce=wp.mce||{},wp.mce.View=function(a){a||(a={}),_.extend(this,_.pick(a,e)),this.initialize.apply(this,arguments)},_.extend(wp.mce.View.prototype,{initialize:function(){},html:function(){},render:function(){var b=this.getHtml();_.each(tinymce.editors,function(c){var d;c.plugins.wpview&&(d=c.getDoc(),a(d).find('[data-wpview-text="'+this.encodedText+'"]').html(b))},this)}}),wp.mce.View.extend=Backbone.View.extend,wp.mce.views={register:function(a,c){b[a]=c},get:function(a){return b[a]},unregister:function(a){delete b[a]},toViews:function(a){var c,d=[{content:a}];return _.each(b,function(a,b){c=d.slice(),d=[],_.each(c,function(c){var e,f=c.content;if(c.processed)return void d.push(c);for(;f&&(e=a.toView(f));)e.index&&d.push({content:f.substring(0,e.index)}),d.push({content:wp.mce.views.toView(b,e.content,e.options),processed:!0}),f=f.slice(e.index+e.content.length);f&&d.push({content:f})})}),_.pluck(d,"content").join("")},toView:function(a,b,d){var e,f,g=wp.mce.views.get(a),h=window.encodeURIComponent(b);return g?(wp.mce.views.getInstance(h)||(f=d,f.encodedText=h,e=new g.View(f),c[h]=e),wp.html.string({tag:"div",attrs:{"class":"wpview-wrap wpview-type-"+a,"data-wpview-text":h,"data-wpview-type":a,contenteditable:"false"},content:" "})):b},refreshView:function(a,b){var d,e,f,g=window.encodeURIComponent(b);f=wp.mce.views.getInstance(g),f||(e=a.toView(b),d=e.options,d.encodedText=g,f=new a.View(d),c[g]=f),wp.mce.views.render()},getInstance:function(a){return c[a]},render:function(){_.each(c,function(a){a.render()})},edit:function(b){var c=a(b).data("wpview-type"),d=wp.mce.views.get(c);d&&d.edit(b)}},wp.mce.gallery={shortcode:"gallery",toView:function(a){var b=wp.shortcode.next(this.shortcode,a);if(b)return{index:b.index,content:b.content,options:{shortcode:b.shortcode}}},View:wp.mce.View.extend({className:"editor-gallery",template:d.template("editor-gallery"),postID:a("#post_ID").val(),initialize:function(a){this.shortcode=a.shortcode,this.fetch()},fetch:function(){this.attachments=wp.media.gallery.attachments(this.shortcode,this.postID),this.attachments.more().done(_.bind(this.render,this))},getHtml:function(){var a,b=this.shortcode.attrs.named;if(this.attachments.length)return a={attachments:this.attachments.toJSON(),columns:b.columns?parseInt(b.columns,10):3},this.template(a)}}),edit:function(b){var c,d,e=wp.media.gallery,f=this;d=window.decodeURIComponent(a(b).data("wpview-text")),c=e.edit(d),c.state("gallery-edit").on("update",function(d){var g=e.shortcode(d).string();a(b).attr("data-wpview-text",window.encodeURIComponent(g)),wp.mce.views.refreshView(f,g),c.detach()})}},wp.mce.views.register("gallery",wp.mce.gallery)}(jQuery); \ No newline at end of file diff --git a/wp-includes/js/tinymce/plugins/wpgallery/plugin.js b/wp-includes/js/tinymce/plugins/wpgallery/plugin.js index def97d6faf..6e645709f3 100644 --- a/wp-includes/js/tinymce/plugins/wpgallery/plugin.js +++ b/wp-includes/js/tinymce/plugins/wpgallery/plugin.js @@ -59,7 +59,7 @@ tinymce.PluginManager.add('wpgallery', function( editor ) { return; } - // Check if the `wp.media.gallery` API exists. + // Check if the `wp.media` API exists. if ( typeof wp === 'undefined' || ! wp.media ) { return; } @@ -166,7 +166,11 @@ tinymce.PluginManager.add('wpgallery', function( editor ) { }); editor.on( 'BeforeSetContent', function( event ) { - event.content = replaceGalleryShortcodes( event.content ); + // 'wpview' handles the gallery shortcode when present + if ( ! editor.plugins.wpview ) { + event.content = replaceGalleryShortcodes( event.content ); + } + event.content = replaceAVShortcodes( event.content ); }); diff --git a/wp-includes/js/tinymce/plugins/wpgallery/plugin.min.js b/wp-includes/js/tinymce/plugins/wpgallery/plugin.min.js index af1c41f51a..087f938ebc 100644 --- a/wp-includes/js/tinymce/plugins/wpgallery/plugin.min.js +++ b/wp-includes/js/tinymce/plugins/wpgallery/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("wpgallery",function(a){function b(a){return a.replace(/\[gallery([^\]]*)\]/g,function(a){return c("wp-gallery",a)})}function c(a,b){return b=window.encodeURIComponent(b),''}function d(a,b,d){var e;return d&&d.indexOf("["+b)>-1?(e=a.length-d.length,c("wp-"+b,a.substring(0,e))+a.substring(e)):c("wp-"+b,a)}function e(a){for(var b=/\[(video-playlist|audio|video|playlist)[^\]]*\]/,c=/\[(video-playlist|audio|video|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/;b.test(a);)a=a.replace(c,d);return a}function f(a){function b(a,b){return b=new RegExp(b+'="([^"]+)"').exec(a),b?window.decodeURIComponent(b[1]):""}return a.replace(/(?:]+)?>)*(]+>)(?:<\/p>)*/g,function(a,c){var d=b(c,"data-wp-media");return d?"

"+d+"

":a})}function g(b){var c,d,e;"IMG"===b.nodeName&&"undefined"!=typeof wp&&wp.media&&(a.dom.hasClass(b,"wp-gallery")&&wp.media.gallery?(c=wp.media.gallery,e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=c.edit(e),d.state("gallery-edit").on("update",function(e){var f=c.shortcode(e).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(f)),d.detach()})):a.dom.hasClass(b,"wp-playlist")&&wp.media.playlist?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media.playlist.edit(e),d.state("playlist-edit").on("update",function(c){var e=wp.media.playlist.shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):a.dom.hasClass(b,"wp-video-playlist")&&wp.media["video-playlist"]?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media["video-playlist"].edit(e),d.state("video-playlist-edit").on("update",function(c){var e=wp.media["video-playlist"].shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):window.console&&window.console.log("Edit AV shortcode "+window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media"))))}a.addCommand("WP_Gallery",function(){g(a.selection.getNode())}),a.on("mouseup",function(b){function c(){d.removeClass(d.select("img.wp-media-selected"),"wp-media-selected")}var d=a.dom,e=b.target;"IMG"===e.nodeName&&d.getAttrib(e,"data-wp-media")?2!==b.button&&(d.hasClass(e,"wp-media-selected")?g(e):(c(),d.addClass(e,"wp-media-selected"))):c()}),a.on("ResolveName",function(b){var c=a.dom,d=b.target;"IMG"===d.nodeName&&c.getAttrib(d,"data-wp-media")&&(c.hasClass(d,"wp-gallery")?b.name="gallery":c.hasClass(d,"wp-video")?b.name="video":c.hasClass(d,"wp-audio")?b.name="audio":c.hasClass(d,"wp-playlist")?b.name="playlist":c.hasClass(d,"wp-video-playlist")&&(b.name="video-playlist"))}),a.on("BeforeSetContent",function(a){a.content=b(a.content),a.content=e(a.content)}),a.on("PostProcess",function(a){a.get&&(a.content=f(a.content))})}); \ No newline at end of file +tinymce.PluginManager.add("wpgallery",function(a){function b(a){return a.replace(/\[gallery([^\]]*)\]/g,function(a){return c("wp-gallery",a)})}function c(a,b){return b=window.encodeURIComponent(b),''}function d(a,b,d){var e;return d&&d.indexOf("["+b)>-1?(e=a.length-d.length,c("wp-"+b,a.substring(0,e))+a.substring(e)):c("wp-"+b,a)}function e(a){for(var b=/\[(video-playlist|audio|video|playlist)[^\]]*\]/,c=/\[(video-playlist|audio|video|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/;b.test(a);)a=a.replace(c,d);return a}function f(a){function b(a,b){return b=new RegExp(b+'="([^"]+)"').exec(a),b?window.decodeURIComponent(b[1]):""}return a.replace(/(?:]+)?>)*(]+>)(?:<\/p>)*/g,function(a,c){var d=b(c,"data-wp-media");return d?"

"+d+"

":a})}function g(b){var c,d,e;"IMG"===b.nodeName&&"undefined"!=typeof wp&&wp.media&&(a.dom.hasClass(b,"wp-gallery")&&wp.media.gallery?(c=wp.media.gallery,e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=c.edit(e),d.state("gallery-edit").on("update",function(e){var f=c.shortcode(e).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(f)),d.detach()})):a.dom.hasClass(b,"wp-playlist")&&wp.media.playlist?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media.playlist.edit(e),d.state("playlist-edit").on("update",function(c){var e=wp.media.playlist.shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):a.dom.hasClass(b,"wp-video-playlist")&&wp.media["video-playlist"]?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media["video-playlist"].edit(e),d.state("video-playlist-edit").on("update",function(c){var e=wp.media["video-playlist"].shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):window.console&&window.console.log("Edit AV shortcode "+window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media"))))}a.addCommand("WP_Gallery",function(){g(a.selection.getNode())}),a.on("mouseup",function(b){function c(){d.removeClass(d.select("img.wp-media-selected"),"wp-media-selected")}var d=a.dom,e=b.target;"IMG"===e.nodeName&&d.getAttrib(e,"data-wp-media")?2!==b.button&&(d.hasClass(e,"wp-media-selected")?g(e):(c(),d.addClass(e,"wp-media-selected"))):c()}),a.on("ResolveName",function(b){var c=a.dom,d=b.target;"IMG"===d.nodeName&&c.getAttrib(d,"data-wp-media")&&(c.hasClass(d,"wp-gallery")?b.name="gallery":c.hasClass(d,"wp-video")?b.name="video":c.hasClass(d,"wp-audio")?b.name="audio":c.hasClass(d,"wp-playlist")?b.name="playlist":c.hasClass(d,"wp-video-playlist")&&(b.name="video-playlist"))}),a.on("BeforeSetContent",function(c){a.plugins.wpview||(c.content=b(c.content)),c.content=e(c.content)}),a.on("PostProcess",function(a){a.get&&(a.content=f(a.content))})}); \ No newline at end of file diff --git a/wp-includes/js/tinymce/plugins/wpview/plugin.js b/wp-includes/js/tinymce/plugins/wpview/plugin.js index 0c56ecba1f..e56e3da334 100644 --- a/wp-includes/js/tinymce/plugins/wpview/plugin.js +++ b/wp-includes/js/tinymce/plugins/wpview/plugin.js @@ -2,190 +2,366 @@ /** * WordPress View plugin. */ - -(function() { - var VK = tinymce.VK, +tinymce.PluginManager.add( 'wpview', function( editor ) { + var selected, + VK = tinymce.util.VK, TreeWalker = tinymce.dom.TreeWalker, - selected; + toRemove = false; - tinymce.create('tinymce.plugins.wpView', { - init : function( editor ) { - var wpView = this; - - // Check if the `wp.mce` API exists. - if ( typeof wp === 'undefined' || ! wp.mce ) { - return; + function getParentView( node ) { + while ( node && node.nodeName !== 'BODY' ) { + if ( isView( node ) ) { + return node; } - editor.on( 'PreInit', function() { - // Add elements so we can set `contenteditable` to false. - editor.schema.addValidElements('div[*],span[*]'); - }); + node = node.parentNode; + } + } - // When the editor's content changes, scan the new content for - // matching view patterns, and transform the matches into - // view wrappers. Since the editor's DOM is outdated at this point, - // we'll wait to render the views. - editor.on( 'BeforeSetContent', function( e ) { - if ( ! e.content ) { - return; - } + function isView( node ) { + return node && /\bwpview-wrap\b/.test( node.className ); + } - e.content = wp.mce.view.toViews( e.content ); - }); + function createPadNode() { + return editor.dom.create( 'p', { 'data-wpview-pad': 1 }, + ( tinymce.Env.ie && tinymce.Env.ie < 11 ) ? '' : '
' ); + } - // When the editor's content has been updated and the DOM has been - // processed, render the views in the document. - editor.on( 'SetContent', function() { - wp.mce.view.render( editor.getDoc() ); - }); + /** + * Get the text/shortcode string for a view. + * + * @param view The view wrapper's HTML id or node + * @returns string The text/shoercode string of the view + */ + function getViewText( view ) { + view = getParentView( typeof view === 'string' ? editor.dom.get( view ) : view ); - editor.on( 'init', function() { - var selection = editor.selection; - // When a view is selected, ensure content that is being pasted - // or inserted is added to a text node (instead of the view). - editor.on( 'BeforeSetContent', function() { - var walker, target, - view = wpView.getParentView( selection.getNode() ); + if ( view ) { + return window.decodeURIComponent( editor.dom.getAttrib( view, 'data-wpview-text' ) || '' ); + } + return ''; + } - // If the selection is not within a view, bail. - if ( ! view ) { - return; - } + /** + * Set the view's original text/shortcode string + * + * @param view The view wrapper's HTML id or node + * @param text The text string to be set + */ + function setViewText( view, text ) { + view = getParentView( typeof view === 'string' ? editor.dom.get( view ) : view ); - // If there are no additional nodes or the next node is a - // view, create a text node after the current view. - if ( ! view.nextSibling || wpView.isView( view.nextSibling ) ) { - target = editor.getDoc().createTextNode(''); - editor.dom.insertAfter( target, view ); + if ( view ) { + editor.dom.setAttrib( view, 'data-wpview-text', window.encodeURIComponent( text || '' ) ); + return true; + } + return false; + } - // Otherwise, find the next text node. - } else { - walker = new TreeWalker( view.nextSibling, view.nextSibling ); - target = walker.next(); - } + function _stop( event ) { + event.stopPropagation(); + } - // Select the `target` text node. - selection.select( target ); - selection.collapse( true ); - }); + function select( viewNode ) { + var clipboard, + dom = editor.dom; - // When the selection's content changes, scan any new content - // for matching views and immediately render them. - // - // Runs on paste and on inserting nodes/html. - editor.on( 'SetContent', function( e ) { - if ( ! e.context ) { - return; - } + // Bail if node is already selected. + if ( viewNode === selected ) { + return; + } - var node = selection.getNode(); + deselect(); + selected = viewNode; + dom.addClass( viewNode, 'selected' ); - if ( ! node.innerHTML ) { - return; - } + clipboard = dom.create( 'div', { + 'class': 'wpview-clipboard', + 'contenteditable': 'true' + }, getViewText( viewNode ) ); - node.innerHTML = wp.mce.view.toViews( node.innerHTML ); - wp.mce.view.render( node ); - }); - }); + viewNode.appendChild( clipboard ); - // When the editor's contents are being accessed as a string, - // transform any views back to their text representations. - editor.on( 'PostProcess', function( e ) { - if ( ( ! e.get && ! e.save ) || ! e.content ) { - return; - } + // 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 ); - e.content = wp.mce.view.toText( e.content ); - }); + // select the hidden div + editor.selection.select( clipboard, true ); + } - // Triggers when the selection is changed. - // Add the event handler to the top of the stack. - editor.on( 'NodeChange', function( e ) { - var view = wpView.getParentView( e.element ); + /** + * Deselect a selected view and remove clipboard + */ + function deselect() { + var clipboard, + dom = editor.dom; - // Update the selected view. - if ( view ) { - wpView.select( view ); + if ( selected ) { + clipboard = editor.dom.select( '.wpview-clipboard', selected )[0]; + dom.unbind( clipboard ); + dom.remove( clipboard ); - // Prevent the selection from propagating to other plugins. - return false; + dom.unbind( selected, 'beforedeactivate focusin focusout click mouseup', _stop ); + dom.removeClass( selected, 'selected' ); - // If we've clicked off of the selected view, deselect it. - } else { - wpView.deselect(); - } - }); + editor.selection.select( selected.nextSibling ); + editor.selection.collapse(); - editor.on( 'keydown', function( event ) { - var keyCode = event.keyCode, - view, instance; + } - // If a view isn't selected, let the event go on its merry way. - if ( ! selected ) { - return; - } + selected = null; + } - // If the caret is not within the selected view, deselect the - // view and bail. - view = wpView.getParentView( editor.selection.getNode() ); - if ( view !== selected ) { - wpView.deselect(); - return; - } + // Check if the `wp.mce` API exists. + if ( typeof wp === 'undefined' || ! wp.mce ) { + return; + } - // If delete or backspace is pressed, delete the view. - if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) { - if ( (instance = wp.mce.view.instance( selected )) ) { - instance.remove(); - wpView.deselect(); - } - } - - // Let keypresses that involve the command or control keys through. - // Also, let any of the F# keys through. - if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) { - return; - } - - event.preventDefault(); - }); - }, - - getParentView : function( node ) { - while ( node ) { - if ( this.isView( node ) ) { - return node; - } - - node = node.parentNode; - } - }, - - isView : function( node ) { - return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className ); - }, - - select : function( view ) { - if ( view === selected ) { - return; - } - - this.deselect(); - selected = view; - wp.mce.view.select( selected ); - }, - - deselect : function() { - if ( selected ) { - wp.mce.view.deselect( selected ); - } - - selected = null; + editor.on( 'BeforeAddUndo', function( event ) { + if ( selected && ! toRemove ) { + event.preventDefault(); } }); - // Register plugin - tinymce.PluginManager.add( 'wpview', tinymce.plugins.wpView ); -})(); + // 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 ) { + return; + } + + e.content = wp.mce.views.toViews( e.content ); + }); + + // 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 ) { + var body, padNode; + + wp.mce.views.render(); + + // Add padding

if the noneditable node is last + if ( event.load || ! event.set ) { + body = editor.getBody(); + + if ( isView( body.lastChild ) ) { + padNode = createPadNode(); + body.appendChild( padNode ); + editor.selection.setCursorLocation( padNode, 0 ); + } + } + + // refreshEmptyContentNode(); + }); + + // Detect mouse down events that are adjacent to a view when a view is the first view or the last view + editor.on( 'click', function( event ) { + var body = editor.getBody(), + doc = editor.getDoc(), + scrollTop = doc.documentElement.scrollTop || body.scrollTop || 0, + x, y, firstNode, lastNode, padNode; + + if ( event.target.nodeName === 'HTML' && ! event.metaKey && ! event.ctrlKey ) { + firstNode = body.firstChild; + lastNode = body.lastChild; + x = event.clientX; + y = event.clientY; + + if ( isView( firstNode ) && ( ( x < firstNode.offsetLeft && y < ( firstNode.offsetHeight - scrollTop ) ) || + y < firstNode.offsetTop ) ) { + // detect events above or to the left of the first view + + padNode = createPadNode(); + body.insertBefore( padNode, firstNode ); + } else if ( isView( lastNode ) && ( x > ( lastNode.offsetLeft + lastNode.offsetWidth ) || + ( ( scrollTop + y ) - ( lastNode.offsetTop + lastNode.offsetHeight ) ) > 0 ) ) { + // detect events to the right and below the last view + + padNode = createPadNode(); + body.appendChild( padNode ); + } + + if ( padNode ) { + editor.selection.setCursorLocation( padNode, 0 ); + } + } + }); + + editor.on( 'init', function() { + var selection = editor.selection; + // When a view is selected, ensure content that is being pasted + // or inserted is added to a text node (instead of the view). + editor.on( 'BeforeSetContent', function() { + var walker, target, + view = getParentView( selection.getNode() ); + + // If the selection is not within a view, bail. + if ( ! view ) { + return; + } + + if ( ! view.nextSibling || isView( view.nextSibling ) ) { + // If there are no additional nodes or the next node is a + // view, create a text node after the current view. + target = editor.getDoc().createTextNode(''); + editor.dom.insertAfter( target, view ); + } else { + // Otherwise, find the next text node. + walker = new TreeWalker( view.nextSibling, view.nextSibling ); + target = walker.next(); + } + + // Select the `target` text node. + selection.select( target ); + selection.collapse( true ); + }); + + // When the selection's content changes, scan any new content + // for matching views. + // + // Runs on paste and on inserting nodes/html. + editor.on( 'SetContent', function( e ) { + if ( ! e.context ) { + return; + } + + var node = selection.getNode(); + + if ( ! node.innerHTML ) { + return; + } + + node.innerHTML = wp.mce.views.toViews( node.innerHTML ); + }); + + editor.dom.bind( editor.getBody(), 'mousedown mouseup click', function( event ) { + var view = getParentView( event.target ); + + // Contain clicks inside the view wrapper + if ( view ) { + event.stopPropagation(); + + if ( event.type === 'click' ) { + if ( ! event.metaKey && ! event.ctrlKey ) { + if ( editor.dom.hasClass( event.target, 'edit' ) ) { + wp.mce.views.edit( view ); + } else if ( editor.dom.hasClass( event.target, 'remove' ) ) { + editor.dom.remove( view ); + } + } + } + select( view ); + // returning false stops the ugly bars from appearing in IE11 and stops the view being selected as a range in FF + // unfortunately, it also inhibits the dragging fo views to a new location + return false; + } else { + if ( event.type === 'click' ) { + deselect(); + } + } + }); + + }); + + editor.on( 'PreProcess', function( event ) { + var dom = editor.dom; + + // Remove empty padding nodes + tinymce.each( dom.select( 'p[data-wpview-pad]', event.node ), function( node ) { + if ( dom.isEmpty( node ) ) { + dom.remove( node ); + } else { + dom.setAttrib( node, 'data-wpview-pad', null ); + } + }); + + // Replace the wpview node with the wpview string/shortcode? + tinymce.each( dom.select( 'div[data-wpview-text]', event.node ), function( node ) { + // Empty the wrap node + if ( 'textContent' in node ) { + node.textContent = ''; + } else { + node.innerText = ''; + } + + // TODO: that makes all views into block tags (as we use

). + // Can use 'PostProcess' and a regex instead. + dom.replace( dom.create( 'p', null, window.decodeURIComponent( dom.getAttrib( node, 'data-wpview-text' ) ) ), node ); + }); + }); + + editor.on( 'keydown', function( event ) { + var keyCode = event.keyCode, + view; + + // If a view isn't selected, let the event go on its merry way. + if ( ! selected ) { + return; + } + + // Let keypresses that involve the command or control keys through. + // Also, let any of the F# keys through. + if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) { + if ( ( event.metaKey || event.ctrlKey ) && keyCode === 88 ) { + toRemove = selected; + } + return; + } + + // If the caret is not within the selected view, deselect the + // view and bail. + view = getParentView( editor.selection.getNode() ); + + if ( view !== selected ) { + deselect(); + return; + } + + // If delete or backspace is pressed, delete the view. + if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) { + editor.dom.remove( selected ); + } + + event.preventDefault(); + }); + + editor.on( 'keyup', function( event ) { + var padNode, + keyCode = event.keyCode, + body = editor.getBody(), + range; + + if ( toRemove ) { + editor.dom.remove( toRemove ); + toRemove = false; + } + + if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) { + // Make sure there is padding if the last element is a view + if ( isView( body.lastChild ) ) { + padNode = createPadNode(); + body.appendChild( padNode ); + + if ( body.childNodes.length === 2 ) { + editor.selection.setCursorLocation( padNode, 0 ); + } + } + + range = editor.selection.getRng(); + + // Allow an initial element in the document to be removed when it is before a view + if ( body.firstChild === range.startContainer && range.collapsed === true && + isView( range.startContainer.nextSibling ) && range.startOffset === 0 ) { + + editor.dom.remove( range.startContainer ); + } + } + }); + + return { + getViewText: getViewText, + setViewText: setViewText + }; +}); diff --git a/wp-includes/js/tinymce/plugins/wpview/plugin.min.js b/wp-includes/js/tinymce/plugins/wpview/plugin.min.js index 61eb4d7a65..bfe7870b46 100644 --- a/wp-includes/js/tinymce/plugins/wpview/plugin.min.js +++ b/wp-includes/js/tinymce/plugins/wpview/plugin.min.js @@ -1 +1 @@ -!function(){var a,b=tinymce.VK,c=tinymce.dom.TreeWalker;tinymce.create("tinymce.plugins.wpView",{init:function(d){var e=this;"undefined"!=typeof wp&&wp.mce&&(d.on("PreInit",function(){d.schema.addValidElements("div[*],span[*]")}),d.on("BeforeSetContent",function(a){a.content&&(a.content=wp.mce.view.toViews(a.content))}),d.on("SetContent",function(){wp.mce.view.render(d.getDoc())}),d.on("init",function(){var a=d.selection;d.on("BeforeSetContent",function(){var b,f,g=e.getParentView(a.getNode());g&&(!g.nextSibling||e.isView(g.nextSibling)?(f=d.getDoc().createTextNode(""),d.dom.insertAfter(f,g)):(b=new c(g.nextSibling,g.nextSibling),f=b.next()),a.select(f),a.collapse(!0))}),d.on("SetContent",function(b){if(b.context){var c=a.getNode();c.innerHTML&&(c.innerHTML=wp.mce.view.toViews(c.innerHTML),wp.mce.view.render(c))}})}),d.on("PostProcess",function(a){(a.get||a.save)&&a.content&&(a.content=wp.mce.view.toText(a.content))}),d.on("NodeChange",function(a){var b=e.getParentView(a.element);return b?(e.select(b),!1):void e.deselect()}),d.on("keydown",function(c){var f,g,h=c.keyCode;if(a){if(f=e.getParentView(d.selection.getNode()),f!==a)return void e.deselect();(h===b.DELETE||h===b.BACKSPACE)&&(g=wp.mce.view.instance(a))&&(g.remove(),e.deselect()),c.metaKey||c.ctrlKey||h>=112&&123>=h||c.preventDefault()}}))},getParentView:function(a){for(;a;){if(this.isView(a))return a;a=a.parentNode}},isView:function(a){return/(?:^|\s)wp-view-wrap(?:\s|$)/.test(a.className)},select:function(b){b!==a&&(this.deselect(),a=b,wp.mce.view.select(a))},deselect:function(){a&&wp.mce.view.deselect(a),a=null}}),tinymce.PluginManager.add("wpview",tinymce.plugins.wpView)}(); \ No newline at end of file +tinymce.PluginManager.add("wpview",function(a){function b(a){for(;a&&"BODY"!==a.nodeName;){if(c(a))return a;a=a.parentNode}}function c(a){return a&&/\bwpview-wrap\b/.test(a.className)}function d(){return a.dom.create("p",{"data-wpview-pad":1},tinymce.Env.ie&&tinymce.Env.ie<11?"":'
')}function e(c){return c=b("string"==typeof c?a.dom.get(c):c),c?window.decodeURIComponent(a.dom.getAttrib(c,"data-wpview-text")||""):""}function f(c,d){return c=b("string"==typeof c?a.dom.get(c):c),c?(a.dom.setAttrib(c,"data-wpview-text",window.encodeURIComponent(d||"")),!0):!1}function g(a){a.stopPropagation()}function h(b){var c,d=a.dom;b!==j&&(i(),j=b,d.addClass(b,"selected"),c=d.create("div",{"class":"wpview-clipboard",contenteditable:"true"},e(b)),b.appendChild(c),a.dom.bind(c,"beforedeactivate focusin focusout",g),a.dom.bind(j,"beforedeactivate focusin focusout",g),a.selection.select(c,!0))}function i(){var b,c=a.dom;j&&(b=a.dom.select(".wpview-clipboard",j)[0],c.unbind(b),c.remove(b),c.unbind(j,"beforedeactivate focusin focusout click mouseup",g),c.removeClass(j,"selected"),a.selection.select(j.nextSibling),a.selection.collapse()),j=null}var j,k=tinymce.util.VK,l=tinymce.dom.TreeWalker,m=!1;if("undefined"!=typeof wp&&wp.mce)return a.on("BeforeAddUndo",function(a){j&&!m&&a.preventDefault()}),a.on("BeforeSetContent",function(a){a.content&&(a.content=wp.mce.views.toViews(a.content))}),a.on("SetContent",function(b){var e,f;wp.mce.views.render(),(b.load||!b.set)&&(e=a.getBody(),c(e.lastChild)&&(f=d(),e.appendChild(f),a.selection.setCursorLocation(f,0)))}),a.on("click",function(b){var e,f,g,h,i,j=a.getBody(),k=a.getDoc(),l=k.documentElement.scrollTop||j.scrollTop||0;"HTML"!==b.target.nodeName||b.metaKey||b.ctrlKey||(g=j.firstChild,h=j.lastChild,e=b.clientX,f=b.clientY,c(g)&&(eh.offsetLeft+h.offsetWidth||l+f-(h.offsetTop+h.offsetHeight)>0)&&(i=d(),j.appendChild(i)),i&&a.selection.setCursorLocation(i,0))}),a.on("init",function(){var d=a.selection;a.on("BeforeSetContent",function(){var e,f,g=b(d.getNode());g&&(!g.nextSibling||c(g.nextSibling)?(f=a.getDoc().createTextNode(""),a.dom.insertAfter(f,g)):(e=new l(g.nextSibling,g.nextSibling),f=e.next()),d.select(f),d.collapse(!0))}),a.on("SetContent",function(a){if(a.context){var b=d.getNode();b.innerHTML&&(b.innerHTML=wp.mce.views.toViews(b.innerHTML))}}),a.dom.bind(a.getBody(),"mousedown mouseup click",function(c){var d=b(c.target);return d?(c.stopPropagation(),"click"===c.type&&(c.metaKey||c.ctrlKey||(a.dom.hasClass(c.target,"edit")?wp.mce.views.edit(d):a.dom.hasClass(c.target,"remove")&&a.dom.remove(d))),h(d),!1):void("click"===c.type&&i())})}),a.on("PreProcess",function(b){var c=a.dom;tinymce.each(c.select("p[data-wpview-pad]",b.node),function(a){c.isEmpty(a)?c.remove(a):c.setAttrib(a,"data-wpview-pad",null)}),tinymce.each(c.select("div[data-wpview-text]",b.node),function(a){"textContent"in a?a.textContent="":a.innerText="",c.replace(c.create("p",null,window.decodeURIComponent(c.getAttrib(a,"data-wpview-text"))),a)})}),a.on("keydown",function(c){var d,e=c.keyCode;if(j){if(c.metaKey||c.ctrlKey||e>=112&&123>=e)return void((c.metaKey||c.ctrlKey)&&88===e&&(m=j));if(d=b(a.selection.getNode()),d!==j)return void i();(e===k.DELETE||e===k.BACKSPACE)&&a.dom.remove(j),c.preventDefault()}}),a.on("keyup",function(b){var e,f,g=b.keyCode,h=a.getBody();m&&(a.dom.remove(m),m=!1),(g===k.DELETE||g===k.BACKSPACE)&&(c(h.lastChild)&&(e=d(),h.appendChild(e),2===h.childNodes.length&&a.selection.setCursorLocation(e,0)),f=a.selection.getRng(),h.firstChild===f.startContainer&&f.collapsed===!0&&c(f.startContainer.nextSibling)&&0===f.startOffset&&a.dom.remove(f.startContainer))}),{getViewText:e,setViewText:f}}); \ No newline at end of file diff --git a/wp-includes/js/tinymce/skins/wordpress/wp-content.css b/wp-includes/js/tinymce/skins/wordpress/wp-content.css index 95f1f6c831..afff0c8599 100644 --- a/wp-includes/js/tinymce/skins/wordpress/wp-content.css +++ b/wp-includes/js/tinymce/skins/wordpress/wp-content.css @@ -198,6 +198,141 @@ img::selection { outline: 0; } + +/** + * WP Views + */ + +/* IE hasLayout. Needed for all IE incl. 11 (ugh, not again!!) */ +.wpview-wrap { + width: 99.99%; + position: relative; +} + +/* delegate the handling of the selection to the wpview tinymce plugin */ +.wpview-wrap, +.wpview-wrap * { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* hide the shortcode content, but allow the content to still be selected */ +.wpview-wrap .wpview-clipboard { + position: absolute; + top: 0; + left: 0; + z-index: -1; + clip: rect(1px, 1px, 1px, 1px); + overflow: hidden; + outline: 0; +} + +/** + * Gallery preview + */ +.wpview-type-gallery { + position: relative; + padding: 0 0 12px; + margin-bottom: 16px; + cursor: pointer; +} + + .wpview-type-gallery:after { + content: ''; + display: block; + height: 0; + clear: both; + visibility: hidden; +} + + .wpview-type-gallery.selected { + background-color: #efefef; +} + +.wpview-type-gallery .toolbar { + position: absolute; + top: 0; + left: 0; + background-color: #333; + color: white; + padding: 4px; + display: none; +} + +.wpview-type-gallery.selected .toolbar { + display: block; +} + +.wpview-type-gallery .toolbar span { + cursor: pointer; +} + +.gallery img[data-mce-selected]:focus { + outline: none; +} + +.gallery a { + cursor: default; +} + +.gallery { + margin: auto; + line-height: 1; +} + +.gallery .gallery-item { + float: left; + margin: 10px 0 0 0; + text-align: center; +} + +.gallery .gallery-caption, +.gallery .gallery-icon { + margin: 0; +} + +.gallery-columns-1 .gallery-item { + width: 99%; +} + +.gallery-columns-2 .gallery-item { + width: 49.5%; +} + +.gallery-columns-3 .gallery-item { + width: 33%; +} + +.gallery-columns-4 .gallery-item { + width: 24.75%; +} + +.gallery-columns-5 .gallery-item { + width: 19.825%; +} + +.gallery-columns-6 .gallery-item { + width: 16%; +} + +.gallery-columns-7 .gallery-item { + width: 14%; +} + +.gallery-columns-8 .gallery-item { + width: 12%; +} + +.gallery-columns-9 .gallery-item { + width: 11%; +} + +.gallery img { + border: 1px solid #cfcfcf; +} + img.wp-oembed { border: 1px dashed #888; background: #f7f5f2 url(images/embedded.png) no-repeat scroll center center; diff --git a/wp-includes/js/tinymce/wp-tinymce.js.gz b/wp-includes/js/tinymce/wp-tinymce.js.gz index 8e0c8388f8..248d6e7160 100644 Binary files a/wp-includes/js/tinymce/wp-tinymce.js.gz and b/wp-includes/js/tinymce/wp-tinymce.js.gz differ diff --git a/wp-includes/media-template.php b/wp-includes/media-template.php index 5304899f3c..97430d0f18 100644 --- a/wp-includes/media-template.php +++ b/wp-includes/media-template.php @@ -648,6 +648,36 @@ function wp_print_media_templates() {
+ + +