Make WordPress Core

Changeset 22120


Ignore:
Timestamp:
10/05/2012 04:23:59 AM (14 years ago)
Author:
koopersmith
Message:

Use the new media modal to insert galleries into TinyMCE and the text editor.

Galleries

  • Gallery insertion from the new media modal (into TinyMCE, the text editor, etc).
  • Gallery previews in TinyMCE now use the wp.mce.views API.
  • Disables the TinyMCE wpgallery plugin.
  • Gallery previews consist of the first image of the gallery and the appearance of a stack. This will later be fleshed out to include more images/functionality (including editing the gallery, gallery properties, and showing the number of images in the gallery).
  • Multiple galleries can be added to a single post.
  • The gallery MCE view provides a bridge between the wp.shortcode and Attachments representation of a gallery, which allows the existing collection to persist when a gallery is initially created (preventing a request to the server for the query).

Shortcodes

  • Renames wp.shortcode.Match to wp.shortcode to better expose the shortcode constructor.
  • The wp.shortcode constructor now accepts an object of options instead of a wp.shortcode.regexp() match.
  • A wp.shortcode instance can be created from a wp.shortcode.regexp() match by calling wp.shortcode.fromMatch( match ).
  • Adds wp.shortcode.string(), which takes a set of shortcode parameters and converts them into a string.* Renames wp.shortcode.prototype.text() to wp.shortcode.prototype.string().
  • Adds an additional capture group to wp.shortcode.regexp() that records whether or not the shortcode has a closing tag. This allows us to improve the accuracy of the syntax used when transforming a shortcode object back into a string.

Media Models

  • Prevents media Query models from observing the central Attachments.all object when query args without corresponding filters are set (otherwise, queries quickly amass false positives).
  • Adds post__in, post__not_in, and post_parent as acceptable JS attachment Query args.
  • Attachments.more() always returns a $.promise object.

see #21390, #21809, #21812, #21815, #21817.

Location:
trunk
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-admin/includes/ajax-actions.php

    r22117 r22120  
    18031803function wp_ajax_query_attachments() {
    18041804        $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
    1805         $query = array_intersect_key( $query, array_flip( array( 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type' ) ) );
     1805        $query = array_intersect_key( $query, array_flip( array(
     1806                's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
     1807                'post_parent', 'post__in', 'post__not_in',
     1808        ) ) );
    18061809
    18071810        $query['post_type'] = 'attachment';
  • trunk/wp-admin/js/media-upload.js

    r22072 r22120  
    108108                        } ) );
    109109
    110                         workflow.on( 'update', function( selection ) {
     110                        workflow.on( 'update:insert', function( selection ) {
    111111                                this.insert( '\n' + selection.map( function( attachment ) {
    112112                                        return wp.media.string.image( attachment );
    113113                                }).join('\n\n') + '\n' );
     114                        }, this );
     115
     116                        workflow.on( 'update:gallery', function( selection ) {
     117                                var view = wp.mce.view.get('gallery'),
     118                                        shortcode;
     119
     120                                if ( ! view )
     121                                        return;
     122
     123                                shortcode = view.gallery.shortcode( selection );
     124                                this.insert( shortcode.string() );
    114125                        }, this );
    115126
  • trunk/wp-includes/class-wp-editor.php

    r22025 r22120  
    192192                                self::$mce_locale = $mce_locale = ( '' == get_locale() ) ? 'en' : strtolower( substr(get_locale(), 0, 2) ); // only ISO 639-1
    193193                                $no_captions = (bool) apply_filters( 'disable_captions', '' );
    194                                 $plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpgallery', 'wplink', 'wpdialogs', 'wpview' );
     194                                $plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wplink', 'wpdialogs', 'wpview' );
    195195                                $first_run = true;
    196196                                $ext_plugins = '';
  • trunk/wp-includes/js/mce-view.js

    r22102 r22120  
    118118                                view: Backbone.View,
    119119                                text: function( instance ) {
    120                                         return instance.options.shortcode.text();
     120                                        return instance.options.shortcode.string();
    121121                                },
    122122
     
    504504                }
    505505        });
     506
     507        mceview.add( 'gallery', {
     508                shortcode: 'gallery',
     509
     510                gallery: (function() {
     511                        var galleries = {};
     512
     513                        return {
     514                                attachments: function( shortcode, parent ) {
     515                                        var shortcodeString = shortcode.string(),
     516                                                result = galleries[ shortcodeString ],
     517                                                attrs, args;
     518
     519                                        delete galleries[ shortcodeString ];
     520
     521                                        if ( result )
     522                                                return result;
     523
     524                                        attrs = shortcode.attrs.named;
     525                                        args  = _.pick( attrs, 'orderby', 'order' );
     526
     527                                        args.type    = 'image';
     528                                        args.perPage = -1;
     529
     530                                        // Map the `ids` param to the correct query args.
     531                                        if ( attrs.ids ) {
     532                                                args.post__in = attrs.ids.split(',');
     533                                                args.orderby  = 'post__in';
     534                                        } else if ( attrs.include ) {
     535                                                args.post__in = attrs.include.split(',');
     536                                        }
     537
     538                                        if ( attrs.exclude )
     539                                                args.post__not_in = attrs.exclude.split(',');
     540
     541                                        if ( ! args.post__in )
     542                                                args.parent = attrs.id || parent;
     543
     544                                        return media.query( args );
     545                                },
     546
     547                                shortcode: function( attachments ) {
     548                                        var attrs = _.pick( attachments.props.toJSON(), 'include', 'exclude', 'orderby', 'order' ),
     549                                                shortcode;
     550
     551                                        attrs.ids = attachments.pluck('id');
     552
     553                                        shortcode = new wp.shortcode({
     554                                                tag:    'gallery',
     555                                                attrs:  attrs,
     556                                                type:   'single'
     557                                        });
     558
     559                                        galleries[ shortcode.string() ] = attachments;
     560                                        return shortcode;
     561                                }
     562                        };
     563                }()),
     564
     565                view: {
     566                        className: 'editor-gallery',
     567                        template:  media.template('editor-gallery'),
     568
     569                        // The fallback post ID to use as a parent for galleries that don't
     570                        // specify the `ids` or `include` parameters.
     571                        //
     572                        // Uses the hidden input on the edit posts page by default.
     573                        parent: $('#post_ID').val(),
     574
     575                        events: {
     576                                'click .close': 'remove'
     577                        },
     578
     579                        initialize: function() {
     580                                var     view      = mceview.get('gallery'),
     581                                        shortcode = this.options.shortcode;
     582
     583                                this.attachments = view.gallery.attachments( shortcode, this.parent );
     584                                this.attachments.more().done( _.bind( this.render, this ) );
     585                        },
     586
     587                        render: function() {
     588                                var options, thumbnail, size;
     589
     590                                if ( ! this.attachments.length )
     591                                        return;
     592
     593                                thumbnail = this.attachments.first().toJSON();
     594                                size = thumbnail.sizes && thumbnail.sizes.thumbnail ? thumbnail.sizes.thumbnail : thumbnail;
     595
     596                                options = {
     597                                        url:         size.url,
     598                                        orientation: size.orientation,
     599                                        count:       this.attachments.length
     600                                };
     601
     602                                this.$el.html( this.template( options ) );
     603                        }
     604                }
     605        });
    506606}(jQuery));
  • trunk/wp-includes/js/media-models.js

    r22021 r22120  
    228228
    229229                        // Set the `props` model and fill the default property values.
    230                         this.props.set( _.defaults( options.props || {}, {
    231                                 order: 'DESC'
    232                         }) );
     230                        this.props.set( _.defaults( options.props || {} ) );
    233231
    234232                        // Observe another `Attachments` collection if one is provided.
     
    249247                                return;
    250248
    251                         if ( orderby )
     249                        if ( orderby && 'post__in' !== orderby )
    252250                                this.comparator = Attachments.comparator;
    253251                        else
     
    348346                        if ( this.mirroring && this.mirroring.more )
    349347                                return this.mirroring.more( options );
     348                        return $.Deferred().resolve().promise();
    350349                },
    351350
     
    364363                comparator: function( a, b ) {
    365364                        var key   = this.props.get('orderby'),
    366                                 order = this.props.get('order'),
     365                                order = this.props.get('order') || 'DESC',
    367366                                ac    = a.cid,
    368367                                bc    = b.cid;
     
    424423        Query = media.model.Query = Attachments.extend({
    425424                initialize: function( models, options ) {
     425                        var allowed;
     426
    426427                        options = options || {};
    427428                        Attachments.prototype.initialize.apply( this, arguments );
     
    452453                        };
    453454
    454                         this.observe( Attachments.all );
     455                        // Observe the central `Attachments.all` model to watch for new
     456                        // matches for the query.
     457                        //
     458                        // Only observe when a limited number of query args are set. There
     459                        // are no filters for other properties, so observing will result in
     460                        // false positives in those queries.
     461                        allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type' ];
     462                        if ( _( this.args ).chain().keys().difference().isEmpty().value() )
     463                                this.observe( Attachments.all );
    455464                },
    456465
     
    459468
    460469                        if ( ! this.hasMore )
    461                                 return;
     470                                return $.Deferred().resolve().promise();
    462471
    463472                        options = options || {};
     
    465474
    466475                        return this.fetch( options ).done( function( resp ) {
    467                                 if ( _.isEmpty( resp ) || resp.length < this.args.posts_per_page )
     476                                if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page )
    468477                                        query.hasMore = false;
    469478                        });
     
    485494
    486495                                // Determine which page to query.
    487                                 args.paged = Math.floor( this.length / args.posts_per_page ) + 1;
     496                                if ( -1 !== args.posts_per_page )
     497                                        args.paged = Math.floor( this.length / args.posts_per_page ) + 1;
    488498
    489499                                options.data.query = args;
     
    507517
    508518                orderby: {
    509                         allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id' ],
     519                        allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in' ],
    510520                        valuemap: {
    511521                                'id':         'ID',
     
    515525
    516526                propmap: {
    517                         'search': 's',
    518                         'type':   'post_mime_type'
     527                        'search':  's',
     528                        'type':    'post_mime_type',
     529                        'parent':  'post_parent',
     530                        'perPage': 'posts_per_page'
    519531                },
    520532
  • trunk/wp-includes/js/media-views.js

    r22101 r22120  
    118118                },
    119119
    120                 update: function() {
     120                update: function( event ) {
    121121                        this.close();
    122122                        this.trigger( 'update', this.selection );
     123                        this.trigger( 'update:' + event, this.selection );
    123124                        this.selection.clear();
    124125                },
     
    631632                                                text:     l10n.insertIntoPost,
    632633                                                priority: 30,
    633                                                 click:    _.bind( controller.update, controller )
     634                                                click:    _.bind( controller.update, controller, 'insert' )
    634635                                        },
    635636
     
    699700                                                text:     l10n.insertGalleryIntoPost,
    700701                                                priority: 40,
    701                                                 click:    _.bind( controller.update, controller )
     702                                                click:    _.bind( controller.update, controller, 'gallery' )
    702703                                        },
    703704
  • trunk/wp-includes/js/shortcode.js

    r22004 r22120  
    99                // ### Find the next matching shortcode
    1010                //
    11                 // Given a shortcode `tag`, a block of `text, and an optional starting
     11                // Given a shortcode `tag`, a block of `text`, and an optional starting
    1212                // `index`, returns the next matching shortcode or `undefined`.
    1313                //
     
    2525
    2626                        // If we matched an escaped shortcode, try again.
    27                         if ( match[1] === '[' && match[6] === ']' )
     27                        if ( match[1] === '[' && match[7] === ']' )
    2828                                return wp.shortcode.next( tag, text, re.lastIndex );
    2929
     
    3131                                index:     match.index,
    3232                                content:   match[0],
    33                                 shortcode: new wp.shortcode.Match( match )
     33                                shortcode: wp.shortcode.fromMatch( match )
    3434                        };
    3535
     
    4242
    4343                        // If we matched a trailing `]`, strip it from the match.
    44                         if ( match[6] )
     44                        if ( match[7] )
    4545                                result.match = result.match.slice( 0, -1 );
    4646
     
    4848                },
    4949
    50                 // ### Replace matching shortcodes in a block of text.
     50                // ### Replace matching shortcodes in a block of text
    5151                //
    5252                // Accepts a shortcode `tag`, content `text` to scan, and a `callback`
     
    5858                // and a boolean flag to indicate if the match was a `single` tag.
    5959                replace: function( tag, text, callback ) {
    60                         return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, closing, content, right, offset ) {
     60                        return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right, offset ) {
    6161                                // If both extra brackets exist, the shortcode has been
    6262                                // properly escaped.
     
    6565
    6666                                // Create the match object and pass it through the callback.
    67                                 var result = callback( new wp.shortcode.Match( arguments ) );
     67                                var result = callback( wp.shortcode.fromMatch( arguments ) );
    6868
    6969                                // Make sure to return any of the extra brackets if they
     
    7373                },
    7474
    75                 // ### Generate a shortcode RegExp.
     75                // ### Generate a string from shortcode parameters
     76                //
     77                // Creates a `wp.shortcode` instance and returns a string.
     78                //
     79                // Accepts the same `options` as the `wp.shortcode()` constructor,
     80                // containing a `tag` string, a string or object of `attrs`, a boolean
     81                // indicating whether to format the shortcode using a `single` tag, and a
     82                // `content` string.
     83                string: function( options ) {
     84                        return new wp.shortcode( options ).string();
     85                },
     86
     87                // ### Generate a RegExp to identify a shortcode
    7688                //
    7789                // The base regex is functionally equivalent to the one found in
     
    8597                // 4. The self closing `/`
    8698                // 5. The content of a shortcode when it wraps some content.
    87                 // 6. An extra `]` to allow for escaping shortcodes with double `[[]]`
     99                // 6. The closing tag.
     100                // 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
    88101                regexp: _.memoize( function( tag ) {
    89                         return new RegExp( '\\[(\\[?)(' + tag + ')\\b([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)\\[\\/\\2\\])?)(\\]?)', 'g' );
     102                        return new RegExp( '\\[(\\[?)(' + tag + ')\\b([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
    90103                }),
    91104
    92105
    93                 // ### Parse shortcode attributes.
     106                // ### Parse shortcode attributes
    94107                //
    95108                // Shortcodes accept many types of attributes. These can chiefly be
     
    144157                                numeric: numeric
    145158                        };
    146                 })
     159                }),
     160
     161                // ### Generate a Shortcode Object from a RegExp match
     162                // Accepts a `match` object from calling `regexp.exec()` on a `RegExp`
     163                // generated by `wp.shortcode.regexp()`. `match` can also be set to the
     164                // `arguments` from a callback passed to `regexp.replace()`.
     165                fromMatch: function( match ) {
     166                        var type;
     167
     168                        if ( match[4] )
     169                                type = 'self-closing';
     170                        else if ( match[6] )
     171                                type = 'closed';
     172                        else
     173                                type = 'single';
     174
     175                        return new wp.shortcode({
     176                                tag:     match[2],
     177                                attrs:   match[3],
     178                                type:    type,
     179                                content: match[5]
     180                        });
     181                }
    147182        };
    148183
    149184
    150         // Shortcode Matches
     185        // Shortcode Objects
    151186        // -----------------
    152187        //
    153         // Shortcode matches are generated automatically when using
    154         // `wp.shortcode.next()` and `wp.shortcode.replace()`. These two methods
    155         // should handle most shortcode needs.
     188        // Shortcode objects are generated automatically when using the main
     189        // `wp.shortcode` methods: `next()`, `replace()`, and `string()`.
    156190        //
    157         // Accepts a `match` object from calling `regexp.exec()` on a `RegExp`
    158         // generated by `wp.shortcode.regexp()`. `match` can also be set to the
    159         // `arguments` from a callback passed to `regexp.replace()`.
    160         wp.shortcode.Match = function( match ) {
    161                 this.tag     = match[2];
    162                 this.attrs   = wp.shortcode.attrs( match[3] );
    163                 this.single  = !! match[4];
    164                 this.content = match[5];
    165         };
    166 
    167         _.extend( wp.shortcode.Match.prototype, {
    168                 // ### Get a shortcode attribute.
     191        // To access a raw representation of a shortcode, pass an `options` object,
     192        // containing a `tag` string, a string or object of `attrs`, a string
     193        // indicating the `type` of the shortcode ('single', 'self-closing', or
     194        // 'closed'), and a `content` string.
     195        wp.shortcode = _.extend( function( options ) {
     196                _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) );
     197
     198                var attrs = this.attrs;
     199
     200                // Ensure we have a correctly formatted `attrs` object.
     201                this.attrs = {
     202                        named:   {},
     203                        numeric: []
     204                };
     205
     206                if ( ! attrs )
     207                        return;
     208
     209                // Parse a string of attributes.
     210                if ( _.isString( attrs ) ) {
     211                        this.attrs = wp.shortcode.attrs( attrs );
     212
     213                // Identify a correctly formatted `attrs` object.
     214                } else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) {
     215                        this.attrs = attrs;
     216
     217                // Handle a flat object of attributes.
     218                } else {
     219                        _.each( options.attrs, function( value, key ) {
     220                                this.set( key, value );
     221                        }, this );
     222                }
     223        }, wp.shortcode );
     224
     225        _.extend( wp.shortcode.prototype, {
     226                // ### Get a shortcode attribute
    169227                //
    170228                // Automatically detects whether `attr` is named or numeric and routes
     
    174232                },
    175233
    176                 // ### Set a shortcode attribute.
     234                // ### Set a shortcode attribute
    177235                //
    178236                // Automatically detects whether `attr` is named or numeric and routes
     
    183241                },
    184242
    185                 // ### Transform the shortcode match into text.
    186                 text: function() {
     243                // ### Transform the shortcode match into a string
     244                string: function() {
    187245                        var text    = '[' + this.tag;
    188246
     
    198256                        });
    199257
    200                         // If the tag is marked as singular, self-close the tag and
    201                         // ignore any additional content.
    202                         if ( this.single )
     258                        // If the tag is marked as `single` or `self-closing`, close the
     259                        // tag and ignore any additional content.
     260                        if ( 'single' === this.type )
     261                                return text + ']';
     262                        else if ( 'self-closing' === this.type )
    203263                                return text + ' /]';
    204264
  • trunk/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css

    r22012 r22120  
    145145div.wp-view-wrap,
    146146div.wp-view {
     147        position: relative;
    147148        display: inline-block;
    148149}
    149150
    150 .spinner {
    151         background: #fff url("../../../../../../../wp-admin/images/wpspin_light.gif") no-repeat center center;
    152         border: 1px solid #dfdfdf;
    153         margin-top: 10px;
    154         margin-right: 10px;
    155 }
    156 
    157 .editor-attachment {
    158         position: relative;
    159         padding: 5px;
    160 }
    161 
    162 .editor-attachment,
    163 .editor-attachment img {
    164         min-height: 100px;
    165         min-width: 100px;
    166 }
    167 
    168 .editor-attachment img {
     151div.wp-view-wrap img {
    169152        display: block;
    170153        border: 0;
    171154        padding: 0;
    172155        margin: 0;
     156}
     157
     158.spinner {
     159        background: #fff url("../../../../../../../wp-admin/images/wpspin_light.gif") no-repeat center center;
    173160}
    174161
     
    188175}
    189176
    190 .editor-attachment:hover .close {
    191         display: block;
    192 }
     177.editor-attachment:hover .close,
     178.editor-gallery:hover .close {
     179        display: block;
     180}
     181
     182.editor-attachment {
     183        position: relative;
     184        margin-top: 10px;
     185        margin-right: 10px;
     186        padding: 4px;
     187        border: 1px solid #dfdfdf;
     188}
     189
     190.editor-attachment,
     191.editor-attachment img {
     192        min-height: 100px;
     193        min-width: 100px;
     194}
     195
     196.editor-gallery {
     197        min-height: 150px;
     198        min-width: 150px;
     199        margin: 1px;
     200        border: 4px solid #fff;
     201        box-shadow:
     202            0 0 0 1px #ccc,
     203            5px 5px 0 0 #fff,
     204            5px 5px 0 1px #ccc,
     205            10px 10px 0 0 #fff,
     206            10px 10px 0 1px #ccc;
     207}
     208.editor-gallery .close {
     209        top: 1px;
     210        right: 1px;
     211}
  • trunk/wp-includes/media.php

    r22094 r22120  
    13721372                <div class="describe"></div>
    13731373        </script>
     1374
     1375        <script type="text/html" id="tmpl-editor-gallery">
     1376                <% if ( url ) { %>
     1377                        <img src="<%- url %>" draggable="false" />
     1378                <% } %>
     1379                <div class="close">&times;</div>
     1380        </script>
    13741381        <?php
    13751382}
Note: See TracChangeset for help on using the changeset viewer.