WordPress.org

Make WordPress Core

Changeset 22120


Ignore:
Timestamp:
10/05/2012 04:23:59 AM (7 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.