WordPress.org

Make WordPress Core

Ticket #26959: 26959-04.patch

File 26959-04.patch, 30.5 KB (added by gcorne, 8 years ago)
  • src/wp-includes/class-wp-editor.php

    diff --git src/wp-includes/class-wp-editor.php src/wp-includes/class-wp-editor.php
    index a829945..16a4bc5 100644
    final class _WP_Editors { 
    235235                                                'fullscreen',
    236236                                                'wordpress',
    237237                                                'wpeditimage',
    238                                                 'wpgallery',
    239238                                                'wplink',
    240239                                                'wpdialogs',
     240                                                'wpview'
    241241                                        ) ) );
    242242
    243243                                        if ( ( $key = array_search( 'spellchecker', $plugins ) ) !== false ) {
    final class _WP_Editors { 
    497497                if ( self::$has_medialib ) {
    498498                        add_thickbox();
    499499                        wp_enqueue_script('media-upload');
     500
     501                        if ( self::$has_tinymce )
     502                                wp_enqueue_script('mce-view');
    500503                }
     504
    501505        }
    502506
    503507        public static function wp_mce_translation() {
  • src/wp-includes/css/editor.css

    diff --git src/wp-includes/css/editor.css src/wp-includes/css/editor.css
    index aa21bc6..b9abb77 100644
    i.mce-i-wp_page:before { 
    11321132        color: #2ea2cc;
    11331133}
    11341134*/
     1135
     1136
     1137
    11351138/* Distraction Free Writing mode
    11361139 * =Overlay Styles
    11371140-------------------------------------------------------------- */
  • src/wp-includes/js/mce-view.js

    diff --git src/wp-includes/js/mce-view.js src/wp-includes/js/mce-view.js
    index 912c4c7..0b804ef 100644
    window.wp = window.wp || {}; 
    33
    44(function($){
    55        var views = {},
    6                 instances = {};
     6                instances = {},
     7                media = wp.media;
    78
    89        // Create the `wp.mce` object if necessary.
    910        wp.mce = wp.mce || {};
    window.wp = window.wp || {}; 
    1920                        // The default properties used for objects with the `pattern` key in
    2021                        // `wp.mce.view.add()`.
    2122                        pattern: {
    22                                 view: Backbone.View,
     23                                view: wp.Backbone.View,
    2324                                text: function( instance ) {
    2425                                        return instance.options.original;
    2526                                },
    2627
    2728                                toView: function( content ) {
    28                                         if ( ! this.pattern )
     29                                        if ( ! this.pattern ) {
    2930                                                return;
     31                                        }
    3032
    3133                                        this.pattern.lastIndex = 0;
    3234                                        var match = this.pattern.exec( content );
    3335
    34                                         if ( ! match )
     36                                        if ( ! match ) {
    3537                                                return;
     38                                        }
    3639
    3740                                        return {
    3841                                                index:   match.index,
    window.wp = window.wp || {}; 
    4851                        // The default properties used for objects with the `shortcode` key in
    4952                        // `wp.mce.view.add()`.
    5053                        shortcode: {
    51                                 view: Backbone.View,
     54                                view: wp.Backbone.View,
    5255                                text: function( instance ) {
    5356                                        return instance.options.shortcode.string();
    5457                                },
    window.wp = window.wp || {}; 
    5659                                toView: function( content ) {
    5760                                        var match = wp.shortcode.next( this.shortcode, content );
    5861
    59                                         if ( ! match )
     62                                        if ( ! match ) {
    6063                                                return;
     64                                        }
    6165
    6266                                        return {
    6367                                                index:   match.index,
    window.wp = window.wp || {}; 
    98102                        var parent, remove, base, properties;
    99103
    100104                        // Fetch the parent view or the default options.
    101                         if ( options.extend )
     105                        if ( options.extend ) {
    102106                                parent = wp.mce.view.get( options.extend );
    103                         else if ( options.shortcode )
     107                        } else if ( options.shortcode ) {
    104108                                parent = wp.mce.view.defaults.shortcode;
    105                         else
     109                        } else {
    106110                                parent = wp.mce.view.defaults.pattern;
     111                        }
    107112
    108113                        // Extend the `options` object with the parent's properties.
    109114                        _.defaults( options, parent );
    window.wp = window.wp || {}; 
    118123                                        this.$el.parent().remove();
    119124
    120125                                        // Trigger the inherited `remove` method.
    121                                         if ( remove )
     126                                        if ( remove ) {
    122127                                                remove.apply( this, arguments );
     128                                        }
    123129
    124130                                        return this;
    125131                                }
    window.wp = window.wp || {}; 
    138144
    139145                        // If there's a `remove` method on the `base` view that wasn't
    140146                        // created by this method, inherit it.
    141                         if ( ! remove && ! base._mceview )
     147                        if ( ! remove && ! base._mceview ) {
    142148                                remove = base.prototype.remove;
     149                        }
    143150
    144151                        // Automatically create the new `Backbone.View` constructor.
    145152                        options.view = base.extend( properties, {
    window.wp = window.wp || {}; 
    168175                // every match.
    169176                //
    170177                // To render the views, call `wp.mce.view.render( scope )`.
     178                // TODO: needs unit tests!
    171179                toViews: function( content ) {
    172180                        var pieces = [ { content: content } ],
    173181                                current;
    window.wp = window.wp || {}; 
    190198                                        // and slicing the string as we go.
    191199                                        while ( remaining && (result = view.toView( remaining )) ) {
    192200                                                // Any text before the match becomes an unprocessed piece.
    193                                                 if ( result.index )
     201                                                if ( result.index ) {
    194202                                                        pieces.push({ content: remaining.substring( 0, result.index ) });
     203                                                }
    195204
    196205                                                // Add the processed piece for the match.
    197206                                                pieces.push({
    window.wp = window.wp || {}; 
    205214
    206215                                        // There are no additional matches. If any content remains,
    207216                                        // add it as an unprocessed piece.
    208                                         if ( remaining )
     217                                        if ( remaining ) {
    209218                                                pieces.push({ content: remaining });
     219                                        }
    210220                                });
    211221                        });
    212222
    window.wp = window.wp || {}; 
    217227                        var view = wp.mce.view.get( viewType ),
    218228                                instance, id;
    219229
    220                         if ( ! view )
     230                        if ( ! view ) {
    221231                                return '';
    222 
     232                        }
    223233                        // Create a new view instance.
    224234                        instance = new view.view( _.extend( options || {}, {
    225235                                viewType: viewType
    window.wp = window.wp || {}; 
    239249                                tag: 'span' === instance.tagName ? 'span' : 'div',
    240250
    241251                                attrs: {
    242                                         'class':           'wp-view-wrap wp-view-type-' + viewType,
     252                                        'class': 'wp-view-wrap wp-view-type-' + viewType,
    243253                                        'data-wp-view':    id,
    244254                                        'contenteditable': false
    245                                 }
     255                                },
     256
     257                                content: '\u00a0'
    246258                        });
    247259                },
    248260
    window.wp = window.wp || {}; 
    257269                                var wrapper = $(this),
    258270                                        view = wp.mce.view.instance( this );
    259271
    260                                 if ( ! view )
     272                                if ( ! view ) {
    261273                                        return;
     274                                }
    262275
    263276                                // Link the real wrapper to the view.
    264277                                view.$wrapper = wrapper;
    window.wp = window.wp || {}; 
    268281                                view.$el.detach();
    269282
    270283                                // Empty the wrapper, attach the view element to the wrapper,
     284                                // add a hidden element with the shortcode,
    271285                                // and add an ending marker to the wrapper to help regexes
    272286                                // scan the HTML string.
    273                                 wrapper.empty().append( view.el ).append('<span data-wp-view-end class="wp-view-end"></span>');
     287                                wrapper.empty().append( view.el )
     288                                        .append('<span data-wp-view-end class="wp-view-end"></span>');
    274289                        });
    275290                },
    276291
    window.wp = window.wp || {}; 
    278293                // Scans an HTML `content` string and replaces any view instances with
    279294                // their respective text representations.
    280295                toText: function( content ) {
    281                         return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) {
     296
     297                        return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/mg, function( match, id ) {
    282298                                var instance = instances[ id ],
    283299                                        view;
    284300
    285                                 if ( instance )
     301                                if ( instance ) {
    286302                                        view = wp.mce.view.get( instance.options.viewType );
    287 
     303                                }
    288304                                return instance && view ? view.text( instance ) : '';
    289305                        });
    290306                },
    window.wp = window.wp || {}; 
    293309                removeInternalAttrs: function( attrs ) {
    294310                        var result = {};
    295311                        _.each( attrs, function( value, attr ) {
    296                                 if ( -1 === attr.indexOf('data-mce') )
     312                                if ( -1 === attr.indexOf('data-mce') ) {
    297313                                        result[ attr ] = value;
     314                                }
    298315                        });
    299316                        return result;
    300317                },
    window.wp = window.wp || {}; 
    311328                instance: function( node ) {
    312329                        var id = $( node ).data('wp-view');
    313330
    314                         if ( id )
     331                        if ( id ) {
    315332                                return instances[ id ];
     333                        }
    316334                },
    317335
    318336                // ### Select a view.
    window.wp = window.wp || {}; 
    320338                // Accepts a MCE view wrapper `node` (i.e. a node with the
    321339                // `wp-view-wrap` class).
    322340                select: function( node ) {
    323                         var $node = $(node);
     341                        var $node = $(node),
     342                                $shortcode,
     343                                view;
    324344
    325345                        // Bail if node is already selected.
    326                         if ( $node.hasClass('selected') )
     346                        if ( $node.hasClass('selected') ) {
    327347                                return;
     348                        }
    328349
    329350                        $node.addClass('selected');
     351
     352                        view = wp.mce.view.instance( node );
     353
     354                        $shortcode = $( '<div />' )
     355                                .addClass( 'wp-view-shortcode' )
     356                                .prop( 'contenteditable', 'true' )
     357                                .data( 'mce-bogus', '1' )
     358                                .text( view.options.shortcode.string() );
     359                       
     360                        $node.prepend( $shortcode );
     361
    330362                        $( node.firstChild ).trigger('select');
    331363                },
    332364
    window.wp = window.wp || {}; 
    338370                        var $node = $(node);
    339371
    340372                        // Bail if node is already selected.
    341                         if ( ! $node.hasClass('selected') )
     373                        if ( ! $node.hasClass('selected') ) {
    342374                                return;
     375                        }
    343376
    344377                        $node.removeClass('selected');
     378
     379                        $node.find( '.wp-view-shortcode' ).remove();
    345380                        $( node.firstChild ).trigger('deselect');
    346381                }
    347382        };
    348383
    349 }(jQuery));
    350  No newline at end of file
     384        wp.mce.view.add( 'gallery', {
     385                shortcode: 'gallery',
     386
     387                gallery: (function() {
     388                        var galleries = {};
     389
     390                        return {
     391                                attachments: function( shortcode, parent ) {
     392                                        var shortcodeString = shortcode.string(),
     393                                                result = galleries[ shortcodeString ],
     394                                                attrs, args, query, others;
     395
     396                                        delete galleries[ shortcodeString ];
     397
     398                                        if ( result ) {
     399                                                return result;
     400                                        }
     401
     402                                        attrs = shortcode.attrs.named;
     403                                        args  = _.pick( attrs, 'orderby', 'order' );
     404
     405                                        args.type    = 'image';
     406                                        args.perPage = -1;
     407
     408                                        // Map the `ids` param to the correct query args.
     409                                        if ( attrs.ids ) {
     410                                                args.post__in = attrs.ids.split(',');
     411                                                args.orderby  = 'post__in';
     412                                        } else if ( attrs.include ) {
     413                                                args.post__in = attrs.include.split(',');
     414                                        }
     415
     416                                        if ( attrs.exclude ) {
     417                                                args.post__not_in = attrs.exclude.split(',');
     418                                        }
     419
     420                                        if ( ! args.post__in ) {
     421                                                args.parent = attrs.id || parent;
     422                                        }
     423
     424                                        // Collect the attributes that were not included in `args`.
     425                                        others = {};
     426                                        _.filter( attrs, function( value, key ) {
     427                                                if ( _.isUndefined( args[ key ] ) ) {
     428                                                        others[ key ] = value;
     429                                                }
     430                                        });
     431
     432                                        query = media.query( args );
     433                                        query.gallery = new Backbone.Model( others );
     434                                        return query;
     435                                },
     436
     437                                shortcode: function( attachments ) {
     438                                        var props = attachments.props.toJSON(),
     439                                                attrs = _.pick( props, 'include', 'exclude', 'orderby', 'order' ),
     440                                                shortcode, clone;
     441
     442                                        if ( attachments.gallery ) {
     443                                                _.extend( attrs, attachments.gallery.toJSON() );
     444                                        }
     445
     446                                        attrs.ids = attachments.pluck('id');
     447
     448                                        // If the `ids` attribute is set and `orderby` attribute
     449                                        // is the default value, clear it for cleaner output.
     450                                        if ( attrs.ids && 'post__in' === attrs.orderby ) {
     451                                                delete attrs.orderby;
     452                                        }
     453
     454                                        shortcode = new wp.shortcode({
     455                                                tag:    'gallery',
     456                                                attrs:  attrs,
     457                                                type:   'single'
     458                                        });
     459
     460                                        // Use a cloned version of the gallery.
     461                                        clone = new wp.media.model.Attachments( attachments.models, {
     462                                                props: props
     463                                        });
     464                                        clone.gallery = attachments.gallery;
     465                                        galleries[ shortcode.string() ] = clone;
     466
     467                                        return shortcode;
     468                                }
     469                        };
     470                }()),
     471
     472                view: {
     473                        className: 'editor-gallery',
     474                        template:  media.template('editor-gallery'),
     475
     476                        // The fallback post ID to use as a parent for galleries that don't
     477                        // specify the `ids` or `include` parameters.
     478                        //
     479                        // Uses the hidden input on the edit posts page by default.
     480                        parent: $('#post_ID').val(),
     481
     482                        events: {
     483                                'click .remove': 'remove',
     484                                'click .edit':  'edit'
     485                        },
     486
     487                        initialize: function() {
     488                                this.update();
     489                        },
     490
     491                        update: function() {
     492                                var     view = wp.mce.view.get('gallery');
     493
     494                                this.attachments = view.gallery.attachments( this.options.shortcode, this.parent );
     495                                this.attachments.more().done( _.bind( this.render, this ) );
     496                        },
     497
     498
     499                        render: function() {
     500                                var attrs = this.options.shortcode.attrs.named,
     501                                        options;
     502
     503                                if ( ! this.attachments.length ) {
     504                                        return;
     505                                }
     506
     507                                options = {
     508                                        attachments: this.attachments.toJSON(),
     509                                        columns: attrs.columns ? parseInt( attrs.columns, 10 ) : 3
     510                                };
     511
     512                                this.$el.html( this.template( options ) );
     513                        },
     514
     515                        edit: function() {
     516                                var selection;
     517
     518                                if ( ! wp.media.view || this.frame ) {
     519                                        return;
     520                                }
     521
     522                                selection = new wp.media.model.Selection( this.attachments.models, {
     523                                        props:    this.attachments.props.toJSON(),
     524                                        multiple: true
     525                                });
     526                                selection.gallery = this.attachments.gallery;
     527
     528                                this.frame = wp.media({
     529                                        frame:     'post',
     530                                        state:     'gallery-edit',
     531                                        editing:   true,
     532                                        multiple:  true,
     533                                        selection: selection
     534                                });
     535
     536                                // Create a single-use frame. If the frame is closed,
     537                                // then detach it from the DOM and remove the reference.
     538                                this.frame.on( 'close', function() {
     539                                        if ( this.frame ) {
     540                                                this.frame.detach();
     541                                        }
     542                                        delete this.frame;
     543                                }, this );
     544
     545                                // Update the `shortcode` and `attachments`.
     546                                this.frame.state('gallery-edit').on( 'update', function( selection ) {
     547                                        var     view = wp.mce.view.get('gallery');
     548
     549                                        this.options.shortcode = view.gallery.shortcode( selection );
     550                                        this.update();
     551                                }, this );
     552
     553                                this.frame.open();
     554                        }
     555                }
     556        });
     557}(jQuery));
  • src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js

    diff --git src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js
    index f625097..797b7f3 100644
    tinymce.PluginManager.add( 'wpeditimage', function( editor ) { 
    379379                        return true;
    380380                }
    381381
     382                if ( dom.getParent( node, '.wp-view-wrap' ) ) {
     383                        return true;
     384                }
     385
    382386                return false;
    383387        }
    384388
  • src/wp-includes/js/tinymce/plugins/wpview/plugin.js

    diff --git src/wp-includes/js/tinymce/plugins/wpview/plugin.js src/wp-includes/js/tinymce/plugins/wpview/plugin.js
    index 0c56ecb..6610205 100644
     
    22/**
    33 * WordPress View plugin.
    44 */
    5 
    6 (function() {
    7         var VK = tinymce.VK,
     5tinymce.PluginManager.add( 'wpview', function( editor ) {
     6        var VK = tinymce.util.VK,
    87                TreeWalker = tinymce.dom.TreeWalker,
     8                toRemove = false,
    99                selected;
    1010
    11         tinymce.create('tinymce.plugins.wpView', {
    12                 init : function( editor ) {
    13                         var wpView = this;
     11        function getParentView( node ) {
     12                while ( node ) {
     13                        if ( isView( node ) ) {
     14                                return node;
     15                        }
     16
     17                        node = node.parentNode;
     18                }
     19        }
     20
     21        function isView( node ) {
     22                return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className );
     23        }
     24
     25        function select( view ) {
     26                var elem;
     27                if ( view === selected ) {
     28                        return;
     29                }
     30
     31                deselect();
     32                selected = view;
     33
     34                wp.mce.view.select( selected );
     35
     36                elem = editor.dom.select( '.wp-view-shortcode', view )[0];
     37
     38                // the following are both necessary to avoid tinymce from manipulating the selection/focus
     39                editor.dom.bind(elem, 'beforedeactivate focusin focusout', function(e) {
     40                        e.stopPropagation();
     41                });
     42                editor.dom.bind(selected, 'beforedeactivate focusin focusout click mouseup', function(e) {
     43                        e.stopPropagation();
     44                });
     45
     46                // select a the hidden div
     47                editor.selection.select( elem, true );
     48                elem.focus();
     49        }
     50
     51        function deselect() {
     52                var elem;
     53
     54                if ( selected ) {
     55                        elem = editor.dom.select( '.wp-view-shortcode', selected )[0];
     56                        editor.dom.unbind(elem, 'beforedeactivate focusin focusout');
     57                        editor.dom.unbind(selected, 'beforedeactivate focusin focusout click mouseup');
     58
     59                        editor.selection.select( selected.nextSibling );
     60                        editor.selection.collapse();
     61                        wp.mce.view.deselect( selected );
     62                }
     63
     64                selected = null;
     65        }
     66
     67        function refreshEmptyContentNode() {
     68                var body = editor.getBody(),
     69                        node,
     70                        editableNode;
     71
     72                // Gecko adds an editable node if there are no other editable elements
     73                editableNode = editor.dom.select( '[_moz_editor_bogus_node="TRUE"]' );
     74
     75                if ( body.childNodes.length === ( 1 + editableNode.length ) ) {
    1476
    15                         // Check if the `wp.mce` API exists.
    16                         if ( typeof wp === 'undefined' || ! wp.mce ) {
     77                        node = body.childNodes[ body.childNodes.length - 1 ];
     78
     79                        if ( node && isView( node ) ) {
     80                                editor.dom.add( body, 'p', {}, '<br data-mce-bogus="1">' );
     81                        }
     82
     83                }
     84        }
     85
     86        // Check if the `wp.mce` API exists.
     87        if ( typeof wp === 'undefined' || ! wp.mce ) {
     88                return;
     89        }
     90
     91        editor.on( 'PreInit', function() {
     92                // Add elements so we can set `contenteditable` to false.
     93                // TODO: since we are serializing, is this needed?
     94                editor.schema.addValidElements('div[*],span[*]');
     95        });
     96
     97        editor.on( 'BeforeAddUndo', function( event ) {
     98                if ( selected && ! toRemove ) {
     99                        event.preventDefault();
     100                }
     101        });
     102
     103        // When the editor's content changes, scan the new content for
     104        // matching view patterns, and transform the matches into
     105        // view wrappers. Since the editor's DOM is outdated at this point,
     106        // we'll wait to render the views.
     107        editor.on( 'BeforeSetContent', function( e ) {
     108                if ( ! e.content ) {
     109                        return;
     110                }
     111
     112                e.content = wp.mce.view.toViews( e.content );
     113        });
     114
     115        // When the editor's content has been updated and the DOM has been
     116        // processed, render the views in the document.
     117        editor.on( 'SetContent', function() {
     118                wp.mce.view.render( editor.getDoc() );
     119                refreshEmptyContentNode();
     120        });
     121
     122        // Provide our own handler for selecting a view that is picked up before TinyMCE
     123        // Ideally, TinyMCE would provide a way to relinquish control over a block that is marked contenteditable=false perhaps through some sort of data attribute
     124        editor.on( 'mousedown', function( event ) {
     125                var view = getParentView( event.target );
     126
     127                if ( event.metaKey || event.ctrlKey ) {
     128                        return;
     129                }
     130
     131                // Update the selected view.
     132                if ( view ) {
     133                        select( view );
     134
     135                        // maybe we can trigger the mousedown so that a view can listen to it.
     136                        // Prevent the selection from propagating to other plugins.
     137                        return false;
     138
     139                } else {
     140                        deselect();
     141                }
     142        } );
     143
     144        // Detect mouse down events that are adjacent to a view when a view is the first view or the last view
     145        //
     146        editor.on( 'click', function( event ) {
     147                var body = editor.getBody(),
     148                        doc = editor.getDoc(),
     149                        scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0,
     150                        x, y, firstView, lastView, emptyNode;
     151
     152                if ( event.metaKey || event.ctrlKey ) {
     153                        return;
     154                }
     155
     156                if ( event.target.nodeName === 'HTML' && ( isView( body.firstChild ) || isView( body.lastChild ) ) ) {
     157                        firstView = body.firstChild;
     158                        lastView = body.lastChild;
     159
     160                        x = event.clientX;
     161                        y = event.clientY;
     162
     163                        emptyNode = editor.dom.create( 'p', {}, '<br data-mce-bogus="1">' );
     164
     165                        // detect events above or to the left of the first view
     166                        if ( isView( firstView ) && ( ( x < firstView.offsetLeft && y < ( firstView.offsetHeight - scrollTop ) ) ||
     167                                                y < firstView.offsetTop ) ) {
     168
     169                                body.insertBefore( emptyNode, firstView );
     170                                editor.selection.select( emptyNode.firstChild );
     171                                editor.selection.collapse( true );
     172                                editor.selection.scrollIntoView( emptyNode );
     173
     174                                return false;
     175                        }
     176                        // detect events to the right and below the last view
     177                        else if ( isView( lastView ) && ( x > ( lastView.offsetLeft + lastView.offsetWidth ) ||
     178                                ( ( scrollTop + y ) - ( lastView.offsetTop + lastView.offsetHeight ) ) > 0 ) ) {
     179
     180                                body.appendChild( emptyNode );
     181
     182                                editor.selection.select( body.lastChild );
     183                                editor.selection.collapse( true );
     184                                editor.selection.scrollIntoView( body.lastChild );
     185
     186                                return false;
     187                        }
     188                }
     189        } );
     190
     191        editor.on( 'init', function() {
     192                var selection = editor.selection;
     193                // When a view is selected, ensure content that is being pasted
     194                // or inserted is added to a text node (instead of the view).
     195                editor.on( 'BeforeSetContent', function() {
     196                        var walker, target,
     197                                view = getParentView( selection.getNode() );
     198
     199                        // If the selection is not within a view, bail.
     200                        if ( ! view ) {
    17201                                return;
    18202                        }
    19203
    20                         editor.on( 'PreInit', function() {
    21                                 // Add elements so we can set `contenteditable` to false.
    22                                 editor.schema.addValidElements('div[*],span[*]');
    23                         });
    24 
    25                         // When the editor's content changes, scan the new content for
    26                         // matching view patterns, and transform the matches into
    27                         // view wrappers. Since the editor's DOM is outdated at this point,
    28                         // we'll wait to render the views.
    29                         editor.on( 'BeforeSetContent', function( e ) {
    30                                 if ( ! e.content ) {
    31                                         return;
    32                                 }
    33 
    34                                 e.content = wp.mce.view.toViews( e.content );
    35                         });
    36 
    37                         // When the editor's content has been updated and the DOM has been
    38                         // processed, render the views in the document.
    39                         editor.on( 'SetContent', function() {
    40                                 wp.mce.view.render( editor.getDoc() );
    41                         });
    42 
    43                         editor.on( 'init', function() {
    44                                 var selection = editor.selection;
    45                                 // When a view is selected, ensure content that is being pasted
    46                                 // or inserted is added to a text node (instead of the view).
    47                                 editor.on( 'BeforeSetContent', function() {
    48                                         var walker, target,
    49                                                 view = wpView.getParentView( selection.getNode() );
    50 
    51                                         // If the selection is not within a view, bail.
    52                                         if ( ! view ) {
    53                                                 return;
    54                                         }
    55 
    56                                         // If there are no additional nodes or the next node is a
    57                                         // view, create a text node after the current view.
    58                                         if ( ! view.nextSibling || wpView.isView( view.nextSibling ) ) {
    59                                                 target = editor.getDoc().createTextNode('');
    60                                                 editor.dom.insertAfter( target, view );
    61 
    62                                         // Otherwise, find the next text node.
    63                                         } else {
    64                                                 walker = new TreeWalker( view.nextSibling, view.nextSibling );
    65                                                 target = walker.next();
    66                                         }
    67 
    68                                         // Select the `target` text node.
    69                                         selection.select( target );
    70                                         selection.collapse( true );
    71                                 });
    72 
    73                                 // When the selection's content changes, scan any new content
    74                                 // for matching views and immediately render them.
    75                                 //
    76                                 // Runs on paste and on inserting nodes/html.
    77                                 editor.on( 'SetContent', function( e ) {
    78                                         if ( ! e.context ) {
    79                                                 return;
    80                                         }
    81 
    82                                         var node = selection.getNode();
    83 
    84                                         if ( ! node.innerHTML ) {
    85                                                 return;
    86                                         }
    87 
    88                                         node.innerHTML = wp.mce.view.toViews( node.innerHTML );
    89                                         wp.mce.view.render( node );
    90                                 });
    91                         });
    92 
    93                         // When the editor's contents are being accessed as a string,
    94                         // transform any views back to their text representations.
    95                         editor.on( 'PostProcess', function( e ) {
    96                                 if ( ( ! e.get && ! e.save ) || ! e.content ) {
    97                                         return;
    98                                 }
    99 
    100                                 e.content = wp.mce.view.toText( e.content );
    101                         });
    102 
    103                         // Triggers when the selection is changed.
    104                         // Add the event handler to the top of the stack.
    105                         editor.on( 'NodeChange', function( e ) {
    106                                 var view = wpView.getParentView( e.element );
    107 
    108                                 // Update the selected view.
    109                                 if ( view ) {
    110                                         wpView.select( view );
    111 
    112                                         // Prevent the selection from propagating to other plugins.
    113                                         return false;
    114 
    115                                 // If we've clicked off of the selected view, deselect it.
    116                                 } else {
    117                                         wpView.deselect();
    118                                 }
    119                         });
    120 
    121                         editor.on( 'keydown', function( event ) {
    122                                 var keyCode = event.keyCode,
    123                                         view, instance;
    124 
    125                                 // If a view isn't selected, let the event go on its merry way.
    126                                 if ( ! selected ) {
    127                                         return;
    128                                 }
    129 
    130                                 // If the caret is not within the selected view, deselect the
    131                                 // view and bail.
    132                                 view = wpView.getParentView( editor.selection.getNode() );
    133                                 if ( view !== selected ) {
    134                                         wpView.deselect();
    135                                         return;
    136                                 }
    137 
    138                                 // If delete or backspace is pressed, delete the view.
    139                                 if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
    140                                         if ( (instance = wp.mce.view.instance( selected )) ) {
    141                                                 instance.remove();
    142                                                 wpView.deselect();
    143                                         }
    144                                 }
    145 
    146                                 // Let keypresses that involve the command or control keys through.
    147                                 // Also, let any of the F# keys through.
    148                                 if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) {
    149                                         return;
    150                                 }
    151 
    152                                 event.preventDefault();
    153                         });
    154                 },
    155 
    156                 getParentView : function( node ) {
    157                         while ( node ) {
    158                                 if ( this.isView( node ) ) {
    159                                         return node;
    160                                 }
    161 
    162                                 node = node.parentNode;
     204                        // If there are no additional nodes or the next node is a
     205                        // view, create a text node after the current view.
     206                        if ( ! view.nextSibling || isView( view.nextSibling ) ) {
     207                                target = editor.getDoc().createTextNode('');
     208                                editor.dom.insertAfter( target, view );
     209
     210                        // Otherwise, find the next text node.
     211                        } else {
     212                                walker = new TreeWalker( view.nextSibling, view.nextSibling );
     213                                target = walker.next();
     214                        }
     215
     216                        // Select the `target` text node.
     217                        selection.select( target );
     218                        selection.collapse( true );
     219                });
     220
     221                // When the selection's content changes, scan any new content
     222                // for matching views and immediately render them.
     223                //
     224                // Runs on paste and on inserting nodes/html.
     225                editor.on( 'SetContent', function( e ) {
     226                        if ( ! e.context ) {
     227                                return;
    163228                        }
    164                 },
    165229
    166                 isView : function( node ) {
    167                         return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className );
    168                 },
     230                        var node = selection.getNode();
    169231
    170                 select : function( view ) {
    171                         if ( view === selected ) {
     232                        if ( ! node.innerHTML ) {
    172233                                return;
    173234                        }
    174235
    175                         this.deselect();
    176                         selected = view;
    177                         wp.mce.view.select( selected );
    178                 },
     236                        node.innerHTML = wp.mce.view.toViews( node.innerHTML );
     237                        wp.mce.view.render( node );
     238                });
     239        });
     240
     241        // When the editor's contents are being accessed as a string,
     242        // transform any views back to their text representations.
     243        editor.on( 'PostProcess', function( e ) {
     244                if ( ( ! e.get && ! e.save ) || ! e.content ) {
     245                        return;
     246                }
     247
     248                e.content = wp.mce.view.toText( e.content );
     249        });
     250
     251        editor.on( 'keydown', function( event ) {
     252                var keyCode = event.keyCode,
     253                        view, instance;
     254
     255                // If a view isn't selected, let the event go on its merry way.
     256                if ( ! selected ) {
     257                        return;
     258                }
    179259
    180                 deselect : function() {
    181                         if ( selected ) {
    182                                 wp.mce.view.deselect( selected );
     260                // Let keypresses that involve the command or control keys through.
     261                // Also, let any of the F# keys through.
     262                if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) {
     263                        if ( ( event.metaKey || event.ctrlKey ) && keyCode === 88 ) {
     264                                toRemove = selected;
    183265                        }
     266                        return;
     267                }
    184268
    185                         selected = null;
     269                // If the caret is not within the selected view, deselect the
     270                // view and bail.
     271                view = getParentView( editor.selection.getNode() );
     272                if ( view !== selected ) {
     273                        deselect();
     274                        return;
    186275                }
     276
     277                // If delete or backspace is pressed, delete the view.
     278                if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
     279                        if ( (instance = wp.mce.view.instance( selected )) ) {
     280                                instance.remove();
     281                                deselect();
     282                        }
     283                }
     284
     285                event.preventDefault();
    187286        });
    188287
    189         // Register plugin
    190         tinymce.PluginManager.add( 'wpview', tinymce.plugins.wpView );
    191 })();
     288        editor.on( 'keyup', function() {
     289                var instance;
     290
     291                if ( toRemove ) {
     292                        instance = wp.mce.view.instance( toRemove );
     293                        instance.remove();
     294                        toRemove = false;
     295                }
     296
     297        });
     298});
  • src/wp-includes/js/tinymce/skins/wordpress/wp-content.css

    diff --git src/wp-includes/js/tinymce/skins/wordpress/wp-content.css src/wp-includes/js/tinymce/skins/wordpress/wp-content.css
    index f0b9a73..3d5b71d 100644
    img::selection { 
    183183        outline: 1px solid #777;
    184184}
    185185
     186
     187/**
     188 * WP Views
     189 */
     190
     191/* delegate the handling of the selection to the wpview tinymce plugin */
     192
     193.wp-view-wrap,
     194.wp-view-wrap * {
     195        -moz-user-select: none;
     196        -webkit-user-select: none;
     197        -ms-user-select: none;
     198        user-select: none;
     199}
     200
     201/* hide the shortcode content, but allow the content to still be selected */
     202.wp-view-wrap .wp-view-shortcode {
     203        position: absolute;
     204        top: 20px;
     205        width: 10px;
     206        z-index: 100;
     207        overflow: hidden;
     208        opacity: 1;
     209        left: -9999px;
     210        display: block;
     211        -moz-user-select: element;
     212        -webkit-user-select: element;
     213        -ms-user-select: element;
     214        user-select: element;
     215
     216}
     217
     218/**
     219 * Gallery preview
     220 */
     221.wp-view-type-gallery {
     222    position: relative;
     223    padding: 16px 0;
     224    margin-bottom: 16px;
     225        cursor: pointer;
     226}
     227
     228 .wp-view-type-gallery:after {
     229    content: '';
     230    display: block;
     231    height: 0;
     232    clear: both;
     233    visibility: hidden;
     234}
     235
     236 .wp-view-type-gallery.selected {
     237        background-color: #efefef;
     238}
     239
     240.wp-view-type-gallery .toolbar {
     241    position: absolute;
     242    top: 0;
     243    left: 0;
     244    background-color: #333;
     245    color: white;
     246    padding: 4px;
     247        display: none;
     248}
     249
     250.wp-view-type-gallery.selected .toolbar {
     251        display: block;
     252}
     253
     254.wp-view-type-gallery .toolbar span {
     255        cursor: pointer;
     256}
     257
     258.gallery img[data-mce-selected]:focus {
     259        outline: none;
     260}
     261
     262.gallery a {
     263        cursor: default;
     264}
     265
     266.gallery {
     267        margin: auto;
     268    line-height: 1;
     269}
     270
     271.gallery .gallery-item {
     272        float: left;
     273        margin: 10px 0 0 0;
     274        text-align: center;
     275}
     276
     277.gallery .gallery-caption,
     278.gallery .gallery-icon {
     279        margin: 0;
     280}
     281
     282.gallery-columns-1 .gallery-item {
     283        width: 100%;
     284}
     285
     286.gallery-columns-2 .gallery-item {
     287        width: 50%;
     288}
     289
     290.gallery-columns-3 .gallery-item {
     291        width: 33.333%;
     292}
     293
     294.gallery-columns-4 .gallery-item {
     295        width: 25%;
     296}
     297
     298.gallery-columns-5 .gallery-item {
     299        width: 20%;
     300}
     301
     302.gallery-columns-6 .gallery-item {
     303        width: 16%;
     304}
     305
     306.gallery-columns-7 .gallery-item {
     307        width: 14%;
     308}
     309
     310.gallery-columns-8 .gallery-item {
     311        width: 12%;
     312}
     313
     314.gallery-columns-9 .gallery-item {
     315        width: 11%;
     316}
     317
     318.gallery img {
     319        border: 2px solid #cfcfcf;
     320}
     321
    186322img.wp-oembed {
    187323        border: 1px dashed #888;
    188324        background: #f7f5f2 url("images/embedded.png") no-repeat scroll center center;
  • src/wp-includes/media-template.php

    diff --git src/wp-includes/media-template.php src/wp-includes/media-template.php
    index 28f4a1a..679a831 100644
    function wp_print_media_templates() { 
    599599        </script>
    600600        <?php
    601601
     602                //TODO: do we want to deal with the fact that the elements used for gallery items are filterable and can be overriden via shortcode attributes
     603                // do we want to deal with the difference between display and edit context at all? (e.g. wptexturize() being applied to the caption.
     604        ?>
     605
     606        <script type="text/html" id="tmpl-editor-gallery">
     607                <div class="toolbar">
     608                        <div class="dashicons dashicons-format-gallery edit"></div>
     609                        <div class="dashicons dashicons-no-alt remove"></div>
     610                </div>
     611                <div class="gallery gallery-columns-{{{ data.columns }}}">
     612                        <# _.each( data.attachments, function( attachment, index ) { #>
     613                                <dl class="gallery-item">
     614                                        <dt class="gallery-icon">
     615                                                <?php // TODO: need to figure out the best way to make sure that we have thumbnails ?>
     616                                                <img src="{{{ attachment.sizes.thumbnail.url }}}" />
     617                                        </dt>
     618                                        <dd class="wp-caption-text gallery-caption">
     619                                                {{ attachment.caption }}
     620                                        </dd>
     621                                </dl>
     622                                <?php // this is kind silly, but copied from the gallery shortcode. Maybe it should be removed ?>
     623                                <# if ( index % data.columns === data.columns - 1 ) { #>
     624                                        <br style="clear: both;">
     625                                <# } #>
     626
     627                        <# } ); #>
     628                </div>
     629        </script>
     630        <?php
     631
    602632        /**
    603633         * Prints the media manager custom media templates.
    604634         *