WordPress.org

Make WordPress Core

Ticket #26959: 26959-02.patch

File 26959-02.patch, 27.3 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 5a169bb..ef4ffb0 100644
    final class _WP_Editors { 
    231231                                                'fullscreen',
    232232                                                'wordpress',
    233233                                                'wpeditimage',
    234                                                 'wpgallery',
    235234                                                'wplink',
    236235                                                'wpdialogs',
     236                                                'wpview'
    237237                                        ) ) );
    238238
    239239                                        if ( ( $key = array_search( 'spellchecker', $plugins ) ) !== false ) {
    final class _WP_Editors { 
    489489                if ( self::$has_medialib ) {
    490490                        add_thickbox();
    491491                        wp_enqueue_script('media-upload');
     492
     493                        if ( self::$has_tinymce )
     494                                wp_enqueue_script('mce-view');
    492495                }
     496
    493497        }
    494498
    495499        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 b240346..819cdfb 100644
    i.mce-i-wp_page:before { 
    10791079        padding-left: 12px;
    10801080}
    10811081*/
     1082
     1083
     1084
    10821085/* Distraction Free Writing mode
    10831086 * =Overlay Styles
    10841087-------------------------------------------------------------- */
  • 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..753ae66 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 || {}; 
    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 || {}; 
    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 || {}; 
    255267                render: function( scope ) {
    256268                        $( '.wp-view-wrap', scope ).each( function() {
    257269                                var wrapper = $(this),
    258                                         view = wp.mce.view.instance( this );
     270                                        view = wp.mce.view.instance( this ),
     271                                        $shortcode;
    259272
    260                                 if ( ! view )
     273                                if ( ! view ) {
    261274                                        return;
     275                                }
    262276
    263277                                // Link the real wrapper to the view.
    264278                                view.$wrapper = wrapper;
    window.wp = window.wp || {}; 
    267281                                // Detach the view element to ensure events are not unbound.
    268282                                view.$el.detach();
    269283
     284                                $shortcode = $( '<div />' )
     285                                        .addClass( 'wp-view-shortcode' )
     286                                        .prop( 'contenteditable', 'true' )
     287                                        .data( 'mce-bogus', '1' )
     288                                        .text( view.options.shortcode.string() );
     289
    270290                                // Empty the wrapper, attach the view element to the wrapper,
     291                                // add a hidden element with the shortcode,
    271292                                // and add an ending marker to the wrapper to help regexes
    272293                                // scan the HTML string.
    273                                 wrapper.empty().append( view.el ).append('<span data-wp-view-end class="wp-view-end"></span>');
     294                                wrapper.empty().append( view.el )
     295                                        .prepend( $shortcode )
     296                                        .append('<span data-wp-view-end class="wp-view-end"></span>');
    274297                        });
    275298                },
    276299
    window.wp = window.wp || {}; 
    278301                // Scans an HTML `content` string and replaces any view instances with
    279302                // their respective text representations.
    280303                toText: function( content ) {
    281                         return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) {
     304
     305                        return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/mg, function( match, id ) {
    282306                                var instance = instances[ id ],
    283307                                        view;
    284308
    285                                 if ( instance )
     309                                if ( instance ) {
    286310                                        view = wp.mce.view.get( instance.options.viewType );
    287 
     311                                }
    288312                                return instance && view ? view.text( instance ) : '';
    289313                        });
    290314                },
    window.wp = window.wp || {}; 
    293317                removeInternalAttrs: function( attrs ) {
    294318                        var result = {};
    295319                        _.each( attrs, function( value, attr ) {
    296                                 if ( -1 === attr.indexOf('data-mce') )
     320                                if ( -1 === attr.indexOf('data-mce') ) {
    297321                                        result[ attr ] = value;
     322                                }
    298323                        });
    299324                        return result;
    300325                },
    window.wp = window.wp || {}; 
    311336                instance: function( node ) {
    312337                        var id = $( node ).data('wp-view');
    313338
    314                         if ( id )
     339                        if ( id ) {
    315340                                return instances[ id ];
     341                        }
    316342                },
    317343
    318344                // ### Select a view.
    window.wp = window.wp || {}; 
    323349                        var $node = $(node);
    324350
    325351                        // Bail if node is already selected.
    326                         if ( $node.hasClass('selected') )
     352                        if ( $node.hasClass('selected') ) {
    327353                                return;
     354                        }
    328355
    329356                        $node.addClass('selected');
    330357                        $( node.firstChild ).trigger('select');
    window.wp = window.wp || {}; 
    338365                        var $node = $(node);
    339366
    340367                        // Bail if node is already selected.
    341                         if ( ! $node.hasClass('selected') )
     368                        if ( ! $node.hasClass('selected') ) {
    342369                                return;
     370                        }
    343371
    344372                        $node.removeClass('selected');
    345373                        $( node.firstChild ).trigger('deselect');
    346374                }
    347375        };
    348376
    349 }(jQuery));
    350  No newline at end of file
     377        wp.mce.view.add( 'gallery', {
     378                shortcode: 'gallery',
     379
     380                gallery: (function() {
     381                        var galleries = {};
     382
     383                        return {
     384                                attachments: function( shortcode, parent ) {
     385                                        var shortcodeString = shortcode.string(),
     386                                                result = galleries[ shortcodeString ],
     387                                                attrs, args, query, others;
     388
     389                                        delete galleries[ shortcodeString ];
     390
     391                                        if ( result ) {
     392                                                return result;
     393                                        }
     394
     395                                        attrs = shortcode.attrs.named;
     396                                        args  = _.pick( attrs, 'orderby', 'order' );
     397
     398                                        args.type    = 'image';
     399                                        args.perPage = -1;
     400
     401                                        // Map the `ids` param to the correct query args.
     402                                        if ( attrs.ids ) {
     403                                                args.post__in = attrs.ids.split(',');
     404                                                args.orderby  = 'post__in';
     405                                        } else if ( attrs.include ) {
     406                                                args.post__in = attrs.include.split(',');
     407                                        }
     408
     409                                        if ( attrs.exclude ) {
     410                                                args.post__not_in = attrs.exclude.split(',');
     411                                        }
     412
     413                                        if ( ! args.post__in ) {
     414                                                args.parent = attrs.id || parent;
     415                                        }
     416
     417                                        // Collect the attributes that were not included in `args`.
     418                                        others = {};
     419                                        _.filter( attrs, function( value, key ) {
     420                                                if ( _.isUndefined( args[ key ] ) ) {
     421                                                        others[ key ] = value;
     422                                                }
     423                                        });
     424
     425                                        query = media.query( args );
     426                                        query.gallery = new Backbone.Model( others );
     427                                        return query;
     428                                },
     429
     430                                shortcode: function( attachments ) {
     431                                        var props = attachments.props.toJSON(),
     432                                                attrs = _.pick( props, 'include', 'exclude', 'orderby', 'order' ),
     433                                                shortcode, clone;
     434
     435                                        if ( attachments.gallery ) {
     436                                                _.extend( attrs, attachments.gallery.toJSON() );
     437                                        }
     438
     439                                        attrs.ids = attachments.pluck('id');
     440
     441                                        // If the `ids` attribute is set and `orderby` attribute
     442                                        // is the default value, clear it for cleaner output.
     443                                        if ( attrs.ids && 'post__in' === attrs.orderby ) {
     444                                                delete attrs.orderby;
     445                                        }
     446
     447                                        shortcode = new wp.shortcode({
     448                                                tag:    'gallery',
     449                                                attrs:  attrs,
     450                                                type:   'single'
     451                                        });
     452
     453                                        // Use a cloned version of the gallery.
     454                                        clone = new wp.media.model.Attachments( attachments.models, {
     455                                                props: props
     456                                        });
     457                                        clone.gallery = attachments.gallery;
     458                                        galleries[ shortcode.string() ] = clone;
     459
     460                                        return shortcode;
     461                                }
     462                        };
     463                }()),
     464
     465                view: {
     466                        className: 'editor-gallery',
     467                        template:  media.template('editor-gallery'),
     468
     469                        // The fallback post ID to use as a parent for galleries that don't
     470                        // specify the `ids` or `include` parameters.
     471                        //
     472                        // Uses the hidden input on the edit posts page by default.
     473                        parent: $('#post_ID').val(),
     474
     475                        events: {
     476                                'click .remove': 'remove',
     477                                'click .edit':  'edit'
     478                        },
     479
     480                        initialize: function() {
     481                                this.update();
     482                        },
     483
     484                        update: function() {
     485                                var     view = wp.mce.view.get('gallery');
     486
     487                                this.attachments = view.gallery.attachments( this.options.shortcode, this.parent );
     488                                this.attachments.more().done( _.bind( this.render, this ) );
     489                        },
     490
     491
     492                        render: function() {
     493                                var attrs = this.options.shortcode.attrs.named,
     494                                        options;
     495
     496                                if ( ! this.attachments.length ) {
     497                                        return;
     498                                }
     499
     500                                options = {
     501                                        attachments: this.attachments.toJSON(),
     502                                        columns: attrs.columns ? parseInt( attrs.columns, 10 ) : 3
     503                                };
     504
     505                                this.$el.html( this.template( options ) );
     506                        },
     507
     508                        edit: function() {
     509                                var selection;
     510
     511                                if ( ! wp.media.view || this.frame ) {
     512                                        return;
     513                                }
     514
     515                                selection = new wp.media.model.Selection( this.attachments.models, {
     516                                        props:    this.attachments.props.toJSON(),
     517                                        multiple: true
     518                                });
     519                                selection.gallery = this.attachments.gallery;
     520
     521                                this.frame = wp.media({
     522                                        frame:     'post',
     523                                        state:     'gallery-edit',
     524                                        editing:   true,
     525                                        multiple:  true,
     526                                        selection: selection
     527                                });
     528
     529                                // Create a single-use frame. If the frame is closed,
     530                                // then detach it from the DOM and remove the reference.
     531                                this.frame.on( 'close', function() {
     532                                        if ( this.frame ) {
     533                                                this.frame.detach();
     534                                        }
     535                                        delete this.frame;
     536                                }, this );
     537
     538                                // Update the `shortcode` and `attachments`.
     539                                this.frame.state('gallery-edit').on( 'update', function( selection ) {
     540                                        var     view = wp.mce.view.get('gallery');
     541
     542                                        this.options.shortcode = view.gallery.shortcode( selection );
     543                                        this.update();
     544                                }, this );
     545
     546                                this.frame.open();
     547                        }
     548                }
     549        });
     550}(jQuery));
  • 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..a466abe 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                removeSelected = 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                elem = editor.dom.select( '.wp-view-shortcode', view )[0];
     35
     36                // the following are both necessary to avoid tinymce from manipulating the selection/focus
     37                editor.dom.bind(elem, 'beforedeactivate focusin focusout', function(e) {
     38                        e.stopPropagation();
     39                });
     40                editor.dom.bind(selected, 'beforedeactivate focusin focusout click ouseup', function(e) {
     41                        e.stopPropagation();
     42                });
     43
     44                // select a the hidden div
     45                editor.selection.select( elem, true );
     46                elem.focus();
     47                wp.mce.view.select( selected );
     48        }
     49
     50        function deselect() {
     51                var elem;
     52
     53                if ( selected ) {
     54                        elem = editor.dom.select( '.wp-view-shortcode', selected )[0];
     55                        editor.dom.unbind(elem, 'beforedeactivate focusin focusout');
     56                        editor.dom.unbind(selected, 'beforedeactivate focusin focusout click mouseup');
     57
     58                        editor.selection.select( selected.nextSibling );
     59                        editor.selection.collapse();
     60                        wp.mce.view.deselect( selected );
     61                }
     62
     63                selected = null;
     64        }
     65
     66        // Check if the `wp.mce` API exists.
     67        if ( typeof wp === 'undefined' || ! wp.mce ) {
     68                return;
     69        }
     70
     71        editor.on( 'PreInit', function() {
     72                // Add elements so we can set `contenteditable` to false.
     73                // TODO: since we are serializing, is this needed?
     74                editor.schema.addValidElements('div[*],span[*]');
     75        });
     76
     77        // When the editor's content changes, scan the new content for
     78        // matching view patterns, and transform the matches into
     79        // view wrappers. Since the editor's DOM is outdated at this point,
     80        // we'll wait to render the views.
     81        editor.on( 'BeforeSetContent', function( e ) {
     82                if ( ! e.content ) {
     83                        return;
     84                }
     85
     86                e.content = wp.mce.view.toViews( e.content );
     87        });
     88
     89        // When the editor's content has been updated and the DOM has been
     90        // processed, render the views in the document.
     91        editor.on( 'SetContent', function() {
     92                wp.mce.view.render( editor.getDoc() );
     93        });
     94
     95        // Provide our own handler for selecting a view that is picked up before TinyMCE
     96        // Ideally, TinyMCE would provide a way to relinquish control over a block that is marked contenteditable=false perhaps through some sort of data attribute
     97        editor.on( 'mousedown', function( event ) {
     98                var view = getParentView( event.target );
    1499
    15                         // Check if the `wp.mce` API exists.
    16                         if ( typeof wp === 'undefined' || ! wp.mce ) {
     100                if ( event.metaKey || event.ctrlKey ) {
     101                        return;
     102                }
     103
     104                // Update the selected view.
     105                if ( view ) {
     106                        select( view );
     107
     108                        // maybe we can trigger the mousedown so that a view can listen to it.
     109                        // Prevent the selection from propagating to other plugins.
     110                        return false;
     111
     112                } else {
     113                        deselect();
     114                }
     115        } );
     116
     117        editor.on( 'init', function() {
     118                var selection = editor.selection;
     119                // When a view is selected, ensure content that is being pasted
     120                // or inserted is added to a text node (instead of the view).
     121                editor.on( 'BeforeSetContent', function() {
     122                        var walker, target,
     123                                view = getParentView( selection.getNode() );
     124
     125                        // If the selection is not within a view, bail.
     126                        if ( ! view ) {
    17127                                return;
    18128                        }
    19129
    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;
     130                        // If there are no additional nodes or the next node is a
     131                        // view, create a text node after the current view.
     132                        if ( ! view.nextSibling || isView( view.nextSibling ) ) {
     133                                target = editor.getDoc().createTextNode('');
     134                                editor.dom.insertAfter( target, view );
     135
     136                        // Otherwise, find the next text node.
     137                        } else {
     138                                walker = new TreeWalker( view.nextSibling, view.nextSibling );
     139                                target = walker.next();
    163140                        }
    164                 },
    165141
    166                 isView : function( node ) {
    167                         return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className );
    168                 },
     142                        // Select the `target` text node.
     143                        selection.select( target );
     144                        selection.collapse( true );
     145                });
     146
     147                // When the selection's content changes, scan any new content
     148                // for matching views and immediately render them.
     149                //
     150                // Runs on paste and on inserting nodes/html.
     151                editor.on( 'SetContent', function( e ) {
     152                        if ( ! e.context ) {
     153                                return;
     154                        }
     155
     156                        var node = selection.getNode();
    169157
    170                 select : function( view ) {
    171                         if ( view === selected ) {
     158                        if ( ! node.innerHTML ) {
    172159                                return;
    173160                        }
    174161
    175                         this.deselect();
    176                         selected = view;
    177                         wp.mce.view.select( selected );
    178                 },
     162                        node.innerHTML = wp.mce.view.toViews( node.innerHTML );
     163                        wp.mce.view.render( node );
     164                });
     165        });
     166
     167        // When the editor's contents are being accessed as a string,
     168        // transform any views back to their text representations.
     169        editor.on( 'PostProcess', function( e ) {
     170                if ( ( ! e.get && ! e.save ) || ! e.content ) {
     171                        return;
     172                }
     173
     174                e.content = wp.mce.view.toText( e.content );
     175        });
     176
     177        editor.on( 'keydown', function( event ) {
     178                var keyCode = event.keyCode,
     179                        view, instance;
     180
     181                // If a view isn't selected, let the event go on its merry way.
     182                if ( ! selected ) {
     183                        return;
     184                }
    179185
    180                 deselect : function() {
    181                         if ( selected ) {
    182                                 wp.mce.view.deselect( selected );
     186                // Let keypresses that involve the command or control keys through.
     187                // Also, let any of the F# keys through.
     188                if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) {
     189                        if ( ( event.metaKey || event.ctrlKey ) && keyCode === 88 ) {
     190                                removeSelected = true;
    183191                        }
     192                        return;
     193                }
    184194
    185                         selected = null;
     195                // If the caret is not within the selected view, deselect the
     196                // view and bail.
     197                view = getParentView( editor.selection.getNode() );
     198                if ( view !== selected ) {
     199                        deselect();
     200                        return;
    186201                }
     202
     203                // If delete or backspace is pressed, delete the view.
     204                if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
     205                        if ( (instance = wp.mce.view.instance( selected )) ) {
     206                                instance.remove();
     207                                deselect();
     208                        }
     209                }
     210
     211                event.preventDefault();
    187212        });
    188213
    189         // Register plugin
    190         tinymce.PluginManager.add( 'wpview', tinymce.plugins.wpView );
    191 })();
     214        editor.on( 'keyup', function() {
     215                var instance;
     216
     217                if ( selected && removeSelected ) {
     218                        instance = wp.mce.view.instance( selected );
     219                        removeSelected = false;
     220                        instance.remove();
     221                        deselect();
     222                }
     223
     224        });
     225});
  • 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 92e1f35..f1e44d4 100644
    img::selection { 
    127127        border-style: solid;
    128128}
    129129
     130
     131/**
     132 * WP Views
     133 */
     134
     135/* delegate the handling of the selection to the wpview tinymce plugin */
     136
     137.wp-view-wrap,
     138.wp-view-wrap * {
     139        -moz-user-select: none;
     140        -webkit-user-select: none;
     141        -ms-user-select: none;
     142        user-select: none;
     143}
     144
     145/* hide the shortcode content, but allow the content to still be selected */
     146.wp-view-wrap .wp-view-shortcode {
     147        position: absolute;
     148        top: 20px;
     149        width: 10px;
     150        z-index: 100;
     151        overflow: hidden;
     152        opacity: 1;
     153        left: -9999px;
     154        display: block;
     155        -moz-user-select: element;
     156        -webkit-user-select: element;
     157        -ms-user-select: element;
     158        user-select: element;
     159
     160}
     161
     162/**
     163 * Gallery preview
     164 */
     165.wp-view-type-gallery {
     166    position: relative;
     167    padding: 16px 0;
     168    margin-bottom: 16px;
     169        cursor: pointer;
     170}
     171
     172 .wp-view-type-gallery:after {
     173    content: '';
     174    display: block;
     175    height: 0;
     176    clear: both;
     177    visibility: hidden;
     178}
     179
     180 .wp-view-type-gallery.selected {
     181        background-color: #efefef;
     182}
     183
     184.wp-view-type-gallery .toolbar {
     185    position: absolute;
     186    top: 0;
     187    left: 0;
     188    background-color: #333;
     189    color: white;
     190    padding: 4px;
     191        display: none;
     192}
     193
     194.wp-view-type-gallery.selected .toolbar {
     195        display: block;
     196}
     197
     198.wp-view-type-gallery .toolbar span {
     199        cursor: pointer;
     200}
     201
     202.gallery img[data-mce-selected]:focus {
     203        outline: none;
     204}
     205
     206.gallery a {
     207        cursor: default;
     208}
     209
     210.gallery {
     211        margin: auto;
     212    line-height: 1;
     213}
     214
     215.gallery .gallery-item {
     216        float: left;
     217        margin: 10px 0 0 0;
     218        text-align: center;
     219}
     220
     221.gallery .gallery-caption,
     222.gallery .gallery-icon {
     223        margin: 0;
     224}
     225
     226.gallery-columns-1 .gallery-item {
     227        width: 100%;
     228}
     229
     230.gallery-columns-2 .gallery-item {
     231        width: 50%;
     232}
     233
     234.gallery-columns-3 .gallery-item {
     235        width: 33.333%;
     236}
     237
     238.gallery-columns-4 .gallery-item {
     239        width: 25%;
     240}
     241
     242.gallery-columns-5 .gallery-item {
     243        width: 20%;
     244}
     245
     246.gallery-columns-6 .gallery-item {
     247        width: 16%;
     248}
     249
     250.gallery-columns-7 .gallery-item {
     251        width: 14%;
     252}
     253
     254.gallery-columns-8 .gallery-item {
     255        width: 12%;
     256}
     257
     258.gallery-columns-9 .gallery-item {
     259        width: 11%;
     260}
     261
     262.gallery img {
     263        border: 2px solid #cfcfcf;
     264}
     265
    130266img.wp-oembed {
    131267        border: 1px dashed #888;
    132268        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 93bf672..68e305d 100644
    function wp_print_media_templates() { 
    600600        </script>
    601601        <?php
    602602
     603                //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
     604                // do we want to deal with the difference between display and edit context at all? (e.g. wptexturize() being applied to the caption.
     605        ?>
     606
     607        <script type="text/html" id="tmpl-editor-gallery">
     608                <div class="toolbar"><span class="edit"><?php _e( 'edit' ); ?></span> <span class="remove"><?php _e( 'remove' ); ?></span></div>
     609                <div class="gallery gallery-columns-{{{ data.columns }}}">
     610                        <# _.each( data.attachments, function( attachment, index ) { #>
     611                                <dl class="gallery-item">
     612                                        <dt class="gallery-icon">
     613                                                <?php // TODO: need to figure out the best way to make sure that we have thumbnails ?>
     614                                                <img src="{{{ attachment.sizes.thumbnail.url }}}" />
     615                                        </dt>
     616                                        <dd class="wp-caption-text gallery-caption">
     617                                                {{ attachment.caption }}
     618                                        </dd>
     619                                </dl>
     620                                <?php // this is kind silly, but copied from the gallery shortcode. Maybe it should be removed ?>
     621                                <# if ( index % data.columns === data.columns - 1 ) { #>
     622                                        <br style="clear: both;">
     623                                <# } #>
     624
     625                        <# } ); #>
     626                </div>
     627        </script>
     628        <?php
     629
    603630        /**
    604631         * Prints the media manager custom media templates.
    605632         *