<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[22120] trunk: Use the new media modal to insert galleries into TinyMCE and the text editor.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg > ul, #logmsg > ol { margin-left: 0; margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://core.trac.wordpress.org/changeset/22120">22120</a></dd>
<dt>Author</dt> <dd>koopersmith</dd>
<dt>Date</dt> <dd>2012-10-05 04:23:59 +0000 (Fri, 05 Oct 2012)</dd>
</dl>
<h3>Log Message</h3>
<pre>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 <a href="http://core.trac.wordpress.org/ticket/21390">#21390</a>, <a href="http://core.trac.wordpress.org/ticket/21809">#21809</a>, <a href="http://core.trac.wordpress.org/ticket/21812">#21812</a>, <a href="http://core.trac.wordpress.org/ticket/21815">#21815</a>, <a href="http://core.trac.wordpress.org/ticket/21817">#21817</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkwpadminincludesajaxactionsphp">trunk/wp-admin/includes/ajax-actions.php</a></li>
<li><a href="#trunkwpadminjsmediauploadjs">trunk/wp-admin/js/media-upload.js</a></li>
<li><a href="#trunkwpincludesclasswpeditorphp">trunk/wp-includes/class-wp-editor.php</a></li>
<li><a href="#trunkwpincludesjsmceviewjs">trunk/wp-includes/js/mce-view.js</a></li>
<li><a href="#trunkwpincludesjsmediamodelsjs">trunk/wp-includes/js/media-models.js</a></li>
<li><a href="#trunkwpincludesjsmediaviewsjs">trunk/wp-includes/js/media-views.js</a></li>
<li><a href="#trunkwpincludesjsshortcodejs">trunk/wp-includes/js/shortcode.js</a></li>
<li><a href="#trunkwpincludesjstinymcethemesadvancedskinswp_themecontentcss">trunk/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css</a></li>
<li><a href="#trunkwpincludesmediaphp">trunk/wp-includes/media.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkwpadminincludesajaxactionsphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/includes/ajax-actions.php (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/includes/ajax-actions.php        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-admin/includes/ajax-actions.php        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -1802,7 +1802,10 @@
</span><span class="cx"> */
</span><span class="cx"> function wp_ajax_query_attachments() {
</span><span class="cx">         $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
</span><del>-        $query = array_intersect_key( $query, array_flip( array( 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type' ) ) );
</del><ins>+        $query = array_intersect_key( $query, array_flip( array(
+                's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
+                'post_parent', 'post__in', 'post__not_in',
+        ) ) );
</ins><span class="cx">
</span><span class="cx">         $query['post_type'] = 'attachment';
</span><span class="cx">         $query['post_status'] = 'inherit';
</span></span></pre></div>
<a id="trunkwpadminjsmediauploadjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/js/media-upload.js (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/js/media-upload.js        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-admin/js/media-upload.js        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -107,12 +107,23 @@
</span><span class="cx">                                 multiple: true
</span><span class="cx">                         } ) );
</span><span class="cx">
</span><del>-                        workflow.on( 'update', function( selection ) {
</del><ins>+                        workflow.on( 'update:insert', function( selection ) {
</ins><span class="cx">                                 this.insert( '\n' + selection.map( function( attachment ) {
</span><span class="cx">                                         return wp.media.string.image( attachment );
</span><span class="cx">                                 }).join('\n\n') + '\n' );
</span><span class="cx">                         }, this );
</span><span class="cx">
</span><ins>+                        workflow.on( 'update:gallery', function( selection ) {
+                                var view = wp.mce.view.get('gallery'),
+                                        shortcode;
+
+                                if ( ! view )
+                                        return;
+
+                                shortcode = view.gallery.shortcode( selection );
+                                this.insert( shortcode.string() );
+                        }, this );
+
</ins><span class="cx">                         return workflow;
</span><span class="cx">                 },
</span><span class="cx">
</span></span></pre></div>
<a id="trunkwpincludesclasswpeditorphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/class-wp-editor.php (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/class-wp-editor.php        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-includes/class-wp-editor.php        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -191,7 +191,7 @@
</span><span class="cx">                                 self::$baseurl = includes_url('js/tinymce');
</span><span class="cx">                                 self::$mce_locale = $mce_locale = ( '' == get_locale() ) ? 'en' : strtolower( substr(get_locale(), 0, 2) ); // only ISO 639-1
</span><span class="cx">                                 $no_captions = (bool) apply_filters( 'disable_captions', '' );
</span><del>-                                $plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpgallery', 'wplink', 'wpdialogs', 'wpview' );
</del><ins>+                                $plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wplink', 'wpdialogs', 'wpview' );
</ins><span class="cx">                                 $first_run = true;
</span><span class="cx">                                 $ext_plugins = '';
</span><span class="cx">
</span></span></pre></div>
<a id="trunkwpincludesjsmceviewjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/mce-view.js (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/mce-view.js        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-includes/js/mce-view.js        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -117,7 +117,7 @@
</span><span class="cx">                         shortcode: {
</span><span class="cx">                                 view: Backbone.View,
</span><span class="cx">                                 text: function( instance ) {
</span><del>-                                        return instance.options.shortcode.text();
</del><ins>+                                        return instance.options.shortcode.string();
</ins><span class="cx">                                 },
</span><span class="cx">
</span><span class="cx">                                 toView: function( content ) {
</span><span class="lines">@@ -503,4 +503,104 @@
</span><span class="cx">                         }
</span><span class="cx">                 }
</span><span class="cx">         });
</span><ins>+
+        mceview.add( 'gallery', {
+                shortcode: 'gallery',
+
+                gallery: (function() {
+                        var galleries = {};
+
+                        return {
+                                attachments: function( shortcode, parent ) {
+                                        var shortcodeString = shortcode.string(),
+                                                result = galleries[ shortcodeString ],
+                                                attrs, args;
+
+                                        delete galleries[ shortcodeString ];
+
+                                        if ( result )
+                                                return result;
+
+                                        attrs = shortcode.attrs.named;
+                                        args = _.pick( attrs, 'orderby', 'order' );
+
+                                        args.type = 'image';
+                                        args.perPage = -1;
+
+                                        // Map the `ids` param to the correct query args.
+                                        if ( attrs.ids ) {
+                                                args.post__in = attrs.ids.split(',');
+                                                args.orderby = 'post__in';
+                                        } else if ( attrs.include ) {
+                                                args.post__in = attrs.include.split(',');
+                                        }
+
+                                        if ( attrs.exclude )
+                                                args.post__not_in = attrs.exclude.split(',');
+
+                                        if ( ! args.post__in )
+                                                args.parent = attrs.id || parent;
+
+                                        return media.query( args );
+                                },
+
+                                shortcode: function( attachments ) {
+                                        var attrs = _.pick( attachments.props.toJSON(), 'include', 'exclude', 'orderby', 'order' ),
+                                                shortcode;
+
+                                        attrs.ids = attachments.pluck('id');
+
+                                        shortcode = new wp.shortcode({
+                                                tag: 'gallery',
+                                                attrs: attrs,
+                                                type: 'single'
+                                        });
+
+                                        galleries[ shortcode.string() ] = attachments;
+                                        return shortcode;
+                                }
+                        };
+                }()),
+
+                view: {
+                        className: 'editor-gallery',
+                        template: media.template('editor-gallery'),
+
+                        // The fallback post ID to use as a parent for galleries that don't
+                        // specify the `ids` or `include` parameters.
+                        //
+                        // Uses the hidden input on the edit posts page by default.
+                        parent: $('#post_ID').val(),
+
+                        events: {
+                                'click .close': 'remove'
+                        },
+
+                        initialize: function() {
+                                var        view = mceview.get('gallery'),
+                                        shortcode = this.options.shortcode;
+
+                                this.attachments = view.gallery.attachments( shortcode, this.parent );
+                                this.attachments.more().done( _.bind( this.render, this ) );
+                        },
+
+                        render: function() {
+                                var options, thumbnail, size;
+
+                                if ( ! this.attachments.length )
+                                        return;
+
+                                thumbnail = this.attachments.first().toJSON();
+                                size = thumbnail.sizes && thumbnail.sizes.thumbnail ? thumbnail.sizes.thumbnail : thumbnail;
+
+                                options = {
+                                        url: size.url,
+                                        orientation: size.orientation,
+                                        count: this.attachments.length
+                                };
+
+                                this.$el.html( this.template( options ) );
+                        }
+                }
+        });
</ins><span class="cx"> }(jQuery));
</span><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkwpincludesjsmediamodelsjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/media-models.js (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/media-models.js        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-includes/js/media-models.js        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -227,9 +227,7 @@
</span><span class="cx">                         this.props.on( 'change:type', this._changeType, this );
</span><span class="cx">
</span><span class="cx">                         // Set the `props` model and fill the default property values.
</span><del>-                        this.props.set( _.defaults( options.props || {}, {
-                                order: 'DESC'
-                        }) );
</del><ins>+                        this.props.set( _.defaults( options.props || {} ) );
</ins><span class="cx">
</span><span class="cx">                         // Observe another `Attachments` collection if one is provided.
</span><span class="cx">                         if ( options.observe )
</span><span class="lines">@@ -248,7 +246,7 @@
</span><span class="cx">                         if ( this.comparator && this.comparator !== Attachments.comparator )
</span><span class="cx">                                 return;
</span><span class="cx">
</span><del>-                        if ( orderby )
</del><ins>+                        if ( orderby && 'post__in' !== orderby )
</ins><span class="cx">                                 this.comparator = Attachments.comparator;
</span><span class="cx">                         else
</span><span class="cx">                                 delete this.comparator;
</span><span class="lines">@@ -347,6 +345,7 @@
</span><span class="cx">                 more: function( options ) {
</span><span class="cx">                         if ( this.mirroring && this.mirroring.more )
</span><span class="cx">                                 return this.mirroring.more( options );
</span><ins>+                        return $.Deferred().resolve().promise();
</ins><span class="cx">                 },
</span><span class="cx">
</span><span class="cx">                 parse: function( resp, xhr ) {
</span><span class="lines">@@ -363,7 +362,7 @@
</span><span class="cx">         }, {
</span><span class="cx">                 comparator: function( a, b ) {
</span><span class="cx">                         var key = this.props.get('orderby'),
</span><del>-                                order = this.props.get('order'),
</del><ins>+                                order = this.props.get('order') || 'DESC',
</ins><span class="cx">                                 ac = a.cid,
</span><span class="cx">                                 bc = b.cid;
</span><span class="cx">
</span><span class="lines">@@ -423,6 +422,8 @@
</span><span class="cx">          */
</span><span class="cx">         Query = media.model.Query = Attachments.extend({
</span><span class="cx">                 initialize: function( models, options ) {
</span><ins>+                        var allowed;
+
</ins><span class="cx">                         options = options || {};
</span><span class="cx">                         Attachments.prototype.initialize.apply( this, arguments );
</span><span class="cx">
</span><span class="lines">@@ -451,20 +452,28 @@
</span><span class="cx">                                 return false;
</span><span class="cx">                         };
</span><span class="cx">
</span><del>-                        this.observe( Attachments.all );
</del><ins>+                        // Observe the central `Attachments.all` model to watch for new
+                        // matches for the query.
+                        //
+                        // Only observe when a limited number of query args are set. There
+                        // are no filters for other properties, so observing will result in
+                        // false positives in those queries.
+                        allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type' ];
+                        if ( _( this.args ).chain().keys().difference().isEmpty().value() )
+                                this.observe( Attachments.all );
</ins><span class="cx">                 },
</span><span class="cx">
</span><span class="cx">                 more: function( options ) {
</span><span class="cx">                         var query = this;
</span><span class="cx">
</span><span class="cx">                         if ( ! this.hasMore )
</span><del>-                                return;
</del><ins>+                                return $.Deferred().resolve().promise();
</ins><span class="cx">
</span><span class="cx">                         options = options || {};
</span><span class="cx">                         options.add = true;
</span><span class="cx">
</span><span class="cx">                         return this.fetch( options ).done( function( resp ) {
</span><del>-                                if ( _.isEmpty( resp ) || resp.length < this.args.posts_per_page )
</del><ins>+                                if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page )
</ins><span class="cx">                                         query.hasMore = false;
</span><span class="cx">                         });
</span><span class="cx">                 },
</span><span class="lines">@@ -484,7 +493,8 @@
</span><span class="cx">                                 args = _.clone( this.args );
</span><span class="cx">
</span><span class="cx">                                 // Determine which page to query.
</span><del>-                                args.paged = Math.floor( this.length / args.posts_per_page ) + 1;
</del><ins>+                                if ( -1 !== args.posts_per_page )
+                                        args.paged = Math.floor( this.length / args.posts_per_page ) + 1;
</ins><span class="cx">
</span><span class="cx">                                 options.data.query = args;
</span><span class="cx">                                 return media.ajax( options );
</span><span class="lines">@@ -506,7 +516,7 @@
</span><span class="cx">                 },
</span><span class="cx">
</span><span class="cx">                 orderby: {
</span><del>-                        allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id' ],
</del><ins>+                        allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in' ],
</ins><span class="cx">                         valuemap: {
</span><span class="cx">                                 'id': 'ID',
</span><span class="cx">                                 'uploadedTo': 'parent'
</span><span class="lines">@@ -514,8 +524,10 @@
</span><span class="cx">                 },
</span><span class="cx">
</span><span class="cx">                 propmap: {
</span><del>-                        'search': 's',
-                        'type': 'post_mime_type'
</del><ins>+                        'search': 's',
+                        'type': 'post_mime_type',
+                        'parent': 'post_parent',
+                        'perPage': 'posts_per_page'
</ins><span class="cx">                 },
</span><span class="cx">
</span><span class="cx">                 // Caches query objects so queries can be easily reused.
</span></span></pre></div>
<a id="trunkwpincludesjsmediaviewsjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/media-views.js (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/media-views.js        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-includes/js/media-views.js        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -117,9 +117,10 @@
</span><span class="cx">                         return this;
</span><span class="cx">                 },
</span><span class="cx">
</span><del>-                update: function() {
</del><ins>+                update: function( event ) {
</ins><span class="cx">                         this.close();
</span><span class="cx">                         this.trigger( 'update', this.selection );
</span><ins>+                        this.trigger( 'update:' + event, this.selection );
</ins><span class="cx">                         this.selection.clear();
</span><span class="cx">                 },
</span><span class="cx">
</span><span class="lines">@@ -630,7 +631,7 @@
</span><span class="cx">                                         'insert-into-post': {
</span><span class="cx">                                                 text: l10n.insertIntoPost,
</span><span class="cx">                                                 priority: 30,
</span><del>-                                                click: _.bind( controller.update, controller )
</del><ins>+                                                click: _.bind( controller.update, controller, 'insert' )
</ins><span class="cx">                                         },
</span><span class="cx">
</span><span class="cx">                                         'add-to-gallery': {
</span><span class="lines">@@ -698,7 +699,7 @@
</span><span class="cx">                                                 style: 'primary',
</span><span class="cx">                                                 text: l10n.insertGalleryIntoPost,
</span><span class="cx">                                                 priority: 40,
</span><del>-                                                click: _.bind( controller.update, controller )
</del><ins>+                                                click: _.bind( controller.update, controller, 'gallery' )
</ins><span class="cx">                                         },
</span><span class="cx">
</span><span class="cx">                                         'add-images-from-library': {
</span></span></pre></div>
<a id="trunkwpincludesjsshortcodejs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/shortcode.js (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/shortcode.js        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-includes/js/shortcode.js        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -8,7 +8,7 @@
</span><span class="cx">         wp.shortcode = {
</span><span class="cx">                 // ### Find the next matching shortcode
</span><span class="cx">                 //
</span><del>-                // Given a shortcode `tag`, a block of `text, and an optional starting
</del><ins>+                // Given a shortcode `tag`, a block of `text`, and an optional starting
</ins><span class="cx">                 // `index`, returns the next matching shortcode or `undefined`.
</span><span class="cx">                 //
</span><span class="cx">                 // Shortcodes are formatted as an object that contains the match
</span><span class="lines">@@ -24,13 +24,13 @@
</span><span class="cx">                                 return;
</span><span class="cx">
</span><span class="cx">                         // If we matched an escaped shortcode, try again.
</span><del>-                        if ( match[1] === '[' && match[6] === ']' )
</del><ins>+                        if ( match[1] === '[' && match[7] === ']' )
</ins><span class="cx">                                 return wp.shortcode.next( tag, text, re.lastIndex );
</span><span class="cx">
</span><span class="cx">                         result = {
</span><span class="cx">                                 index: match.index,
</span><span class="cx">                                 content: match[0],
</span><del>-                                shortcode: new wp.shortcode.Match( match )
</del><ins>+                                shortcode: wp.shortcode.fromMatch( match )
</ins><span class="cx">                         };
</span><span class="cx">
</span><span class="cx">                         // If we matched a leading `[`, strip it from the match
</span><span class="lines">@@ -41,13 +41,13 @@
</span><span class="cx">                         }
</span><span class="cx">
</span><span class="cx">                         // If we matched a trailing `]`, strip it from the match.
</span><del>-                        if ( match[6] )
</del><ins>+                        if ( match[7] )
</ins><span class="cx">                                 result.match = result.match.slice( 0, -1 );
</span><span class="cx">
</span><span class="cx">                         return result;
</span><span class="cx">                 },
</span><span class="cx">
</span><del>-                // ### Replace matching shortcodes in a block of text.
</del><ins>+                // ### Replace matching shortcodes in a block of text
</ins><span class="cx">                 //
</span><span class="cx">                 // Accepts a shortcode `tag`, content `text` to scan, and a `callback`
</span><span class="cx">                 // to process the shortcode matches and return a replacement string.
</span><span class="lines">@@ -57,14 +57,14 @@
</span><span class="cx">                 // a shortcode `attrs` object, the `content` between shortcode tags,
</span><span class="cx">                 // and a boolean flag to indicate if the match was a `single` tag.
</span><span class="cx">                 replace: function( tag, text, callback ) {
</span><del>-                        return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, closing, content, right, offset ) {
</del><ins>+                        return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right, offset ) {
</ins><span class="cx">                                 // If both extra brackets exist, the shortcode has been
</span><span class="cx">                                 // properly escaped.
</span><span class="cx">                                 if ( left === '[' && right === ']' )
</span><span class="cx">                                         return match;
</span><span class="cx">
</span><span class="cx">                                 // Create the match object and pass it through the callback.
</span><del>-                                var result = callback( new wp.shortcode.Match( arguments ) );
</del><ins>+                                var result = callback( wp.shortcode.fromMatch( arguments ) );
</ins><span class="cx">
</span><span class="cx">                                 // Make sure to return any of the extra brackets if they
</span><span class="cx">                                 // weren't used to escape the shortcode.
</span><span class="lines">@@ -72,8 +72,20 @@
</span><span class="cx">                         });
</span><span class="cx">                 },
</span><span class="cx">
</span><del>-                // ### Generate a shortcode RegExp.
</del><ins>+                // ### Generate a string from shortcode parameters
</ins><span class="cx">                 //
</span><ins>+                // Creates a `wp.shortcode` instance and returns a string.
+                //
+                // Accepts the same `options` as the `wp.shortcode()` constructor,
+                // containing a `tag` string, a string or object of `attrs`, a boolean
+                // indicating whether to format the shortcode using a `single` tag, and a
+                // `content` string.
+                string: function( options ) {
+                        return new wp.shortcode( options ).string();
+                },
+
+                // ### Generate a RegExp to identify a shortcode
+                //
</ins><span class="cx">                 // The base regex is functionally equivalent to the one found in
</span><span class="cx">                 // `get_shortcode_regex()` in `wp-includes/shortcodes.php`.
</span><span class="cx">                 //
</span><span class="lines">@@ -84,13 +96,14 @@
</span><span class="cx">                 // 3. The shortcode argument list
</span><span class="cx">                 // 4. The self closing `/`
</span><span class="cx">                 // 5. The content of a shortcode when it wraps some content.
</span><del>-                // 6. An extra `]` to allow for escaping shortcodes with double `[[]]`
</del><ins>+                // 6. The closing tag.
+                // 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
</ins><span class="cx">                 regexp: _.memoize( function( tag ) {
</span><del>-                        return new RegExp( '\\[(\\[?)(' + tag + ')\\b([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)\\[\\/\\2\\])?)(\\]?)', 'g' );
</del><ins>+                        return new RegExp( '\\[(\\[?)(' + tag + ')\\b([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
</ins><span class="cx">                 }),
</span><span class="cx">
</span><span class="cx">
</span><del>-                // ### Parse shortcode attributes.
</del><ins>+                // ### Parse shortcode attributes
</ins><span class="cx">                 //
</span><span class="cx">                 // Shortcodes accept many types of attributes. These can chiefly be
</span><span class="cx">                 // divided into named and numeric attributes:
</span><span class="lines">@@ -143,29 +156,74 @@
</span><span class="cx">                                 named: named,
</span><span class="cx">                                 numeric: numeric
</span><span class="cx">                         };
</span><del>-                })
</del><ins>+                }),
+
+                // ### Generate a Shortcode Object from a RegExp match
+                // Accepts a `match` object from calling `regexp.exec()` on a `RegExp`
+                // generated by `wp.shortcode.regexp()`. `match` can also be set to the
+                // `arguments` from a callback passed to `regexp.replace()`.
+                fromMatch: function( match ) {
+                        var type;
+
+                        if ( match[4] )
+                                type = 'self-closing';
+                        else if ( match[6] )
+                                type = 'closed';
+                        else
+                                type = 'single';
+
+                        return new wp.shortcode({
+                                tag: match[2],
+                                attrs: match[3],
+                                type: type,
+                                content: match[5]
+                        });
+                }
</ins><span class="cx">         };
</span><span class="cx">
</span><span class="cx">
</span><del>-        // Shortcode Matches
</del><ins>+        // Shortcode Objects
</ins><span class="cx">         // -----------------
</span><span class="cx">         //
</span><del>-        // Shortcode matches are generated automatically when using
-        // `wp.shortcode.next()` and `wp.shortcode.replace()`. These two methods
-        // should handle most shortcode needs.
</del><ins>+        // Shortcode objects are generated automatically when using the main
+        // `wp.shortcode` methods: `next()`, `replace()`, and `string()`.
</ins><span class="cx">         //
</span><del>-        // Accepts a `match` object from calling `regexp.exec()` on a `RegExp`
-        // generated by `wp.shortcode.regexp()`. `match` can also be set to the
-        // `arguments` from a callback passed to `regexp.replace()`.
-        wp.shortcode.Match = function( match ) {
-                this.tag = match[2];
-                this.attrs = wp.shortcode.attrs( match[3] );
-                this.single = !! match[4];
-                this.content = match[5];
-        };
</del><ins>+        // To access a raw representation of a shortcode, pass an `options` object,
+        // containing a `tag` string, a string or object of `attrs`, a string
+        // indicating the `type` of the shortcode ('single', 'self-closing', or
+        // 'closed'), and a `content` string.
+        wp.shortcode = _.extend( function( options ) {
+                _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) );
</ins><span class="cx">
</span><del>-        _.extend( wp.shortcode.Match.prototype, {
-                // ### Get a shortcode attribute.
</del><ins>+                var attrs = this.attrs;
+
+                // Ensure we have a correctly formatted `attrs` object.
+                this.attrs = {
+                        named: {},
+                        numeric: []
+                };
+
+                if ( ! attrs )
+                        return;
+
+                // Parse a string of attributes.
+                if ( _.isString( attrs ) ) {
+                        this.attrs = wp.shortcode.attrs( attrs );
+
+                // Identify a correctly formatted `attrs` object.
+                } else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) {
+                        this.attrs = attrs;
+
+                // Handle a flat object of attributes.
+                } else {
+                        _.each( options.attrs, function( value, key ) {
+                                this.set( key, value );
+                        }, this );
+                }
+        }, wp.shortcode );
+
+        _.extend( wp.shortcode.prototype, {
+                // ### Get a shortcode attribute
</ins><span class="cx">                 //
</span><span class="cx">                 // Automatically detects whether `attr` is named or numeric and routes
</span><span class="cx">                 // it accordingly.
</span><span class="lines">@@ -173,7 +231,7 @@
</span><span class="cx">                         return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ];
</span><span class="cx">                 },
</span><span class="cx">
</span><del>-                // ### Set a shortcode attribute.
</del><ins>+                // ### Set a shortcode attribute
</ins><span class="cx">                 //
</span><span class="cx">                 // Automatically detects whether `attr` is named or numeric and routes
</span><span class="cx">                 // it accordingly.
</span><span class="lines">@@ -182,8 +240,8 @@
</span><span class="cx">                         return this;
</span><span class="cx">                 },
</span><span class="cx">
</span><del>-                // ### Transform the shortcode match into text.
-                text: function() {
</del><ins>+                // ### Transform the shortcode match into a string
+                string: function() {
</ins><span class="cx">                         var text = '[' + this.tag;
</span><span class="cx">
</span><span class="cx">                         _.each( this.attrs.numeric, function( value ) {
</span><span class="lines">@@ -197,9 +255,11 @@
</span><span class="cx">                                 text += ' ' + name + '="' + value + '"';
</span><span class="cx">                         });
</span><span class="cx">
</span><del>-                        // If the tag is marked as singular, self-close the tag and
-                        // ignore any additional content.
-                        if ( this.single )
</del><ins>+                        // If the tag is marked as `single` or `self-closing`, close the
+                        // tag and ignore any additional content.
+                        if ( 'single' === this.type )
+                                return text + ']';
+                        else if ( 'self-closing' === this.type )
</ins><span class="cx">                                 return text + ' /]';
</span><span class="cx">
</span><span class="cx">                         // Complete the opening tag.
</span></span></pre></div>
<a id="trunkwpincludesjstinymcethemesadvancedskinswp_themecontentcss"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -144,34 +144,21 @@
</span><span class="cx"> /* WordPress TinyMCE Previews */
</span><span class="cx"> div.wp-view-wrap,
</span><span class="cx"> div.wp-view {
</span><ins>+        position: relative;
</ins><span class="cx">         display: inline-block;
</span><span class="cx"> }
</span><span class="cx">
</span><del>-.spinner {
-        background: #fff url("../../../../../../../wp-admin/images/wpspin_light.gif") no-repeat center center;
-        border: 1px solid #dfdfdf;
-        margin-top: 10px;
-        margin-right: 10px;
-}
-
-.editor-attachment {
-        position: relative;
-        padding: 5px;
-}
-
-.editor-attachment,
-.editor-attachment img {
-        min-height: 100px;
-        min-width: 100px;
-}
-
-.editor-attachment img {
</del><ins>+div.wp-view-wrap img {
</ins><span class="cx">         display: block;
</span><span class="cx">         border: 0;
</span><span class="cx">         padding: 0;
</span><span class="cx">         margin: 0;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+.spinner {
+        background: #fff url("../../../../../../../wp-admin/images/wpspin_light.gif") no-repeat center center;
+}
+
</ins><span class="cx"> .close {
</span><span class="cx">         display: none;
</span><span class="cx">         position: absolute;
</span><span class="lines">@@ -187,6 +174,38 @@
</span><span class="cx">         background: #fff;
</span><span class="cx"> }
</span><span class="cx">
</span><del>-.editor-attachment:hover .close {
</del><ins>+.editor-attachment:hover .close,
+.editor-gallery:hover .close {
</ins><span class="cx">         display: block;
</span><ins>+}
+
+.editor-attachment {
+        position: relative;
+        margin-top: 10px;
+        margin-right: 10px;
+        padding: 4px;
+        border: 1px solid #dfdfdf;
+}
+
+.editor-attachment,
+.editor-attachment img {
+        min-height: 100px;
+        min-width: 100px;
+}
+
+.editor-gallery {
+        min-height: 150px;
+        min-width: 150px;
+        margin: 1px;
+        border: 4px solid #fff;
+        box-shadow:
+         0 0 0 1px #ccc,
+         5px 5px 0 0 #fff,
+         5px 5px 0 1px #ccc,
+         10px 10px 0 0 #fff,
+         10px 10px 0 1px #ccc;
+}
+.editor-gallery .close {
+        top: 1px;
+        right: 1px;
</ins><span class="cx"> }
</span><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkwpincludesmediaphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/media.php (22119 => 22120)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/media.php        2012-10-04 21:59:36 UTC (rev 22119)
+++ trunk/wp-includes/media.php        2012-10-05 04:23:59 UTC (rev 22120)
</span><span class="lines">@@ -1371,5 +1371,12 @@
</span><span class="cx">                 <div class="close">&times;</div>
</span><span class="cx">                 <div class="describe"></div>
</span><span class="cx">         </script>
</span><ins>+
+        <script type="text/html" id="tmpl-editor-gallery">
+                <% if ( url ) { %>
+                        <img src="<%- url %>" draggable="false" />
+                <% } %>
+                <div class="close">&times;</div>
+        </script>
</ins><span class="cx">         <?php
</span><span class="cx"> }
</span></span></pre>
</div>
</div>
</body>
</html>