<!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>[22979] trunk: Bring Featured Images back into the main media dialog.</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/22979">22979</a></dd>
<dt>Author</dt> <dd>nacin</dd>
<dt>Date</dt> <dd>2012-12-03 02:38:10 +0000 (Mon, 03 Dec 2012)</dd>
</dl>

<h3>Log Message</h3>
<pre>Bring Featured Images back into the main media dialog.

Most users don't realize that the Featured Image meta box exists; if they do, few use it.

Restores the old meta box UI, including the admin_post_thumbnail_html filter. If a plugin is using _wp_post_thumbnail_html() in conjunction with Thickbox elsewhere, it will also magically still work.

Specific underlying changes:
 * Converts the modal view to use the view manager, which means that a call to open() will automatically call render and attach if necessary.
 * Doesn't automatically set a state in wp.media, to allow code to customize the states to be added before activation.

props koopersmith.
fixes <a href="http://core.trac.wordpress.org/ticket/21776">#21776</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkwpadminincludesajaxactionsphp">trunk/wp-admin/includes/ajax-actions.php</a></li>
<li><a href="#trunkwpadminincludesmetaboxesphp">trunk/wp-admin/includes/meta-boxes.php</a></li>
<li><a href="#trunkwpadminincludespostphp">trunk/wp-admin/includes/post.php</a></li>
<li><a href="#trunkwpadminjscustombackgroundjs">trunk/wp-admin/js/custom-background.js</a></li>
<li><a href="#trunkwpadminjscustomheaderjs">trunk/wp-admin/js/custom-header.js</a></li>
<li><a href="#trunkwpincludescssmediaviewscss">trunk/wp-includes/css/media-views.css</a></li>
<li><a href="#trunkwpincludesjsmediaeditorjs">trunk/wp-includes/js/media-editor.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="#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 (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/includes/ajax-actions.php        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-admin/includes/ajax-actions.php        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -1674,23 +1674,31 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function wp_ajax_set_post_thumbnail() {
</span><ins>+        $json = ! empty( $_REQUEST['json'] );
+
</ins><span class="cx">         $post_ID = intval( $_POST['post_id'] );
</span><del>-        if ( !current_user_can( 'edit_post', $post_ID ) )
-                wp_die( -1 );
</del><ins>+        if ( !current_user_can( 'edit_post', $post_ID ) ) {
+                $json ? wp_send_json_error() : wp_die( -1 );
+        }
</ins><span class="cx">         $thumbnail_id = intval( $_POST['thumbnail_id'] );
</span><span class="cx"> 
</span><span class="cx">         check_ajax_referer( &quot;set_post_thumbnail-$post_ID&quot; );
</span><span class="cx"> 
</span><span class="cx">         if ( $thumbnail_id == '-1' ) {
</span><del>-                if ( delete_post_thumbnail( $post_ID ) )
-                        wp_die( _wp_post_thumbnail_html( null, $post_ID ) );
-                else
-                        wp_die( 0 );
</del><ins>+                if ( delete_post_thumbnail( $post_ID ) ) {
+                        $return = _wp_post_thumbnail_html( null, $post_ID );
+                        $json ? wp_send_json_success( $return ) : wp_die( $return );
+                } else {
+                        $json ? wp_send_json_error() : wp_die( 0 );
+                }
</ins><span class="cx">         }
</span><span class="cx"> 
</span><del>-        if ( set_post_thumbnail( $post_ID, $thumbnail_id ) )
-                wp_die( _wp_post_thumbnail_html( $thumbnail_id, $post_ID ) );
-        wp_die( 0 );
</del><ins>+        if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
+                $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
+                $json ? wp_send_json_success( $return ) : wp_die( $return );
+        }
+
+        $json ? wp_send_json_error() : wp_die( 0 );
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function wp_ajax_date_format() {
</span></span></pre></div>
<a id="trunkwpadminincludesmetaboxesphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/includes/meta-boxes.php (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/includes/meta-boxes.php        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-admin/includes/meta-boxes.php        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -1001,119 +1001,6 @@
</span><span class="cx">  * @since 2.9.0
</span><span class="cx">  */
</span><span class="cx"> function post_thumbnail_meta_box( $post ) {
</span><del>-        global $_wp_additional_image_sizes;
-
-        ?&gt;&lt;script type=&quot;text/javascript&quot;&gt;
-        jQuery( function($) {
-                var $element     = $('#select-featured-image'),
-                        $thumbnailId = $element.find('input[name=&quot;thumbnail_id&quot;]'),
-                        title        = '&lt;?php _e( &quot;Choose a Featured Image&quot; ); ?&gt;',
-                        update       = '&lt;?php _e( &quot;Update Featured Image&quot; ); ?&gt;',
-                        Attachment   = wp.media.model.Attachment,
-                        frame, setFeaturedImage;
-
-                setFeaturedImage = function( thumbnailId ) {
-                        var selection;
-
-                        $element.find('img').remove();
-                        $element.toggleClass( 'has-featured-image', -1 != thumbnailId );
-                        $thumbnailId.val( thumbnailId );
-
-                        if ( frame ) {
-                                selection = frame.state('library').get('selection');
-
-                                if ( -1 === thumbnailId )
-                                        selection.clear();
-                                else
-                                        selection.add( Attachment.get( thumbnailId ) );
-                        }
-                };
-
-                $element.on( 'click', '.choose, img', function( event ) {
-                        var options, thumbnailId, attachment;
-
-                        event.preventDefault();
-
-                        if ( frame ) {
-                                frame.open();
-                                return;
-                        }
-
-                        options = {
-                                title:   title,
-                                library: {
-                                        type: 'image'
-                                }
-                        };
-
-                        thumbnailId = $thumbnailId.val();
-                        if ( '' !== thumbnailId &amp;&amp; -1 !== thumbnailId ) {
-                                attachment = Attachment.get( thumbnailId );
-                                attachment.fetch();
-                                options.selection = [ attachment ];
-                        }
-
-                        frame = wp.media( options );
-
-                        frame.state('library').set( 'filterable', 'uploaded' );
-
-                        frame.toolbar.on( 'activate:select', function() {
-                                frame.toolbar.view().set({
-                                        select: {
-                                                style: 'primary',
-                                                text:  update,
-
-                                                click: function() {
-                                                        var selection = frame.state().get('selection'),
-                                                                model = selection.first(),
-                                                                sizes = model.get('sizes'),
-                                                                size;
-
-                                                        setFeaturedImage( model.id );
-
-                                                        // @todo: might need a size hierarchy equivalent.
-                                                        if ( sizes )
-                                                                size = sizes['post-thumbnail'] || sizes.medium;
-
-                                                        // @todo: Need a better way of accessing full size
-                                                        // data besides just calling toJSON().
-                                                        size = size || model.toJSON();
-
-                                                        frame.close();
-
-                                                        $( '&lt;img /&gt;', {
-                                                                src:    size.url,
-                                                                width:  size.width
-                                                        }).prependTo( $element );
-                                                }
-                                        }
-                                });
-                        });
-
-                        frame.toolbar.mode('select');
-                });
-
-                $element.on( 'click', '.remove', function( event ) {
-                        event.preventDefault();
-                        setFeaturedImage( -1 );
-                });
-        });
-        &lt;/script&gt;
-
-        &lt;?php
-        $thumbnail_id   = get_post_meta( $post-&gt;ID, '_thumbnail_id', true );
-        $thumbnail_size = isset( $_wp_additional_image_sizes['post-thumbnail'] ) ? 'post-thumbnail' : 'medium';
-        $thumbnail_html = wp_get_attachment_image( $thumbnail_id, $thumbnail_size );
-
-        $classes = empty( $thumbnail_id ) ? '' : 'has-featured-image';
-
-        ?&gt;&lt;div id=&quot;select-featured-image&quot;
-                class=&quot;&lt;?php echo esc_attr( $classes ); ?&gt;&quot;
-                data-post-id=&quot;&lt;?php echo esc_attr( $post-&gt;ID ); ?&gt;&quot;&gt;
-                &lt;?php echo $thumbnail_html; ?&gt;
-                &lt;input type=&quot;hidden&quot; name=&quot;thumbnail_id&quot; value=&quot;&lt;?php echo esc_attr( $thumbnail_id ); ?&gt;&quot; /&gt;
-                &lt;a href=&quot;#&quot; class=&quot;choose button-secondary&quot;&gt;&lt;?php _e( 'Choose a Featured Image' ); ?&gt;&lt;/a&gt;
-                &lt;a href=&quot;#&quot; class=&quot;remove&quot;&gt;&lt;?php _e( 'Remove Featured Image' ); ?&gt;&lt;/a&gt;
-        &lt;/div&gt;
-        &lt;?php
</del><ins>+        $thumbnail_id = get_post_meta( $post-&gt;ID, '_thumbnail_id', true );
+        echo _wp_post_thumbnail_html( $thumbnail_id, $post-&gt;ID );
</ins><span class="cx"> }
</span><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkwpadminincludespostphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/includes/post.php (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/includes/post.php        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-admin/includes/post.php        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -199,14 +199,6 @@
</span><span class="cx">                         set_post_format( $post_ID, false );
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        // Featured Images
-        if ( isset( $post_data['thumbnail_id'] ) ) {
-                if ( '-1' == $post_data['thumbnail_id'] )
-                        delete_post_thumbnail( $post_ID );
-                else
-                        set_post_thumbnail( $post_ID, $post_data['thumbnail_id'] );
-        }
-
</del><span class="cx">         // Meta Stuff
</span><span class="cx">         if ( isset($post_data['meta']) &amp;&amp; $post_data['meta'] ) {
</span><span class="cx">                 foreach ( $post_data['meta'] as $key =&gt; $value ) {
</span></span></pre></div>
<a id="trunkwpadminjscustombackgroundjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/js/custom-background.js (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/js/custom-background.js        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-admin/js/custom-background.js        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -57,7 +57,7 @@
</span><span class="cx">                                 });
</span><span class="cx">                         });
</span><span class="cx"> 
</span><del>-                        frame.setState('library');
</del><ins>+                        frame.setState('library').open();
</ins><span class="cx">                 });
</span><span class="cx">         });
</span><span class="cx"> })(jQuery);
</span><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="trunkwpadminjscustomheaderjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-admin/js/custom-header.js (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-admin/js/custom-header.js        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-admin/js/custom-header.js        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -40,7 +40,7 @@
</span><span class="cx">                                 });
</span><span class="cx">                         });
</span><span class="cx"> 
</span><del>-                        frame.setState('library');
</del><ins>+                        frame.setState('library').open();
</ins><span class="cx">                 });
</span><span class="cx">         });
</span><span class="cx"> }(jQuery));
</span></span></pre></div>
<a id="trunkwpincludescssmediaviewscss"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/css/media-views.css (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/css/media-views.css        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-includes/css/media-views.css        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -403,6 +403,11 @@
</span><span class="cx">  */
</span><span class="cx"> .media-frame {
</span><span class="cx">         overflow: hidden;
</span><ins>+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .media-frame .region-content {
</span></span></pre></div>
<a id="trunkwpincludesjsmediaeditorjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/media-editor.js (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/media-editor.js        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-includes/js/media-editor.js        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -375,6 +375,7 @@
</span><span class="cx"> 
</span><span class="cx">                         workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
</span><span class="cx">                                 frame:    'post',
</span><ins>+                                state:    'upload',
</ins><span class="cx">                                 title:    wp.media.view.l10n.addMedia,
</span><span class="cx">                                 multiple: true
</span><span class="cx">                         } ) );
</span><span class="lines">@@ -427,14 +428,52 @@
</span><span class="cx">                                 }
</span><span class="cx">                         }, this );
</span><span class="cx"> 
</span><ins>+                        workflow.state('featured-image').on( 'select', function() {
+                                var settings = wp.media.view.settings,
+                                        featuredImage = settings.featuredImage,
+                                        selection = this.get('selection').single();
+
+                                if ( ! featuredImage )
+                                        return;
+
+                                featuredImage.id = selection ? selection.id : -1;
+                                wp.media.post( 'set-post-thumbnail', {
+                                        json:         true,
+                                        post_id:      settings.postId,
+                                        thumbnail_id: featuredImage.id,
+                                        _wpnonce:     featuredImage.nonce
+                                }).done( function( html ) {
+                                        $( '.inside', '#postimagediv' ).html( html );
+                                });
+                        });
+
+                        workflow.setState( workflow.options.state );
</ins><span class="cx">                         return workflow;
</span><span class="cx">                 },
</span><span class="cx"> 
</span><ins>+                id: function( id ) {
+                        if ( id )
+                                return id;
+
+                        // If an empty `id` is provided, default to `wpActiveEditor`.
+                        id = wpActiveEditor;
+
+                        // If that doesn't work, fall back to `tinymce.activeEditor.id`.
+                        if ( ! id &amp;&amp; typeof tinymce !== 'undefined' &amp;&amp; tinymce.activeEditor )
+                                id = tinymce.activeEditor.id;
+
+                        // Last but not least, fall back to the empty string.
+                        id = id || '';
+                        return id;
+                },
+
</ins><span class="cx">                 get: function( id ) {
</span><ins>+                        id = this.id( id );
</ins><span class="cx">                         return workflows[ id ];
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 remove: function( id ) {
</span><ins>+                        id = this.id( id );
</ins><span class="cx">                         delete workflows[ id ];
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="lines">@@ -497,6 +536,30 @@
</span><span class="cx">                         }
</span><span class="cx">                 },
</span><span class="cx"> 
</span><ins>+                open: function( id ) {
+                        var workflow, editor;
+
+                        id = this.id( id );
+
+                        // Save a bookmark of the caret position in IE.
+                        if ( typeof tinymce !== 'undefined' ) {
+                                editor = tinymce.get( id );
+
+                                if ( tinymce.isIE &amp;&amp; editor &amp;&amp; ! editor.isHidden() ) {
+                                        editor.focus();
+                                        editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
+                                }
+                        }
+
+                        workflow = this.get( id );
+
+                        // Initialize the editor's workflow if we haven't yet.
+                        if ( ! workflow )
+                                workflow = this.add( id );
+
+                        return workflow.open();
+                },
+
</ins><span class="cx">                 init: function() {
</span><span class="cx">                         $(document.body).on( 'click', '.insert-media', function( event ) {
</span><span class="cx">                                 var $this = $(this),
</span><span class="lines">@@ -513,45 +576,40 @@
</span><span class="cx"> 
</span><span class="cx">                                 wp.media.editor.open( editor );
</span><span class="cx">                         });
</span><del>-                },
</del><span class="cx"> 
</span><del>-                open: function( id ) {
-                        var workflow, editor;
</del><ins>+                        // Open the content media manager to the 'featured image' tab when
+                        // the post thumbnail is clicked.
+                        $('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
+                                event.preventDefault();
+                                // Stop propagation to prevent thickbox from activating.
+                                event.stopPropagation();
</ins><span class="cx"> 
</span><del>-                        // If an empty `id` is provided, default to `wpActiveEditor`.
-                        id = id || wpActiveEditor;
</del><ins>+                                // Always get the 'content' frame, since this is tailored to post.php.
+                                var frame = wp.media.editor.add('content'),
+                                        initialState = frame.state().id,
+                                        escape;
</ins><span class="cx"> 
</span><del>-                        if ( typeof tinymce !== 'undefined' &amp;&amp; tinymce.activeEditor ) {
-                                // If that doesn't work, fall back to `tinymce.activeEditor`.
-                                if ( ! id ) {
-                                        editor = tinymce.activeEditor;
-                                        id = id || editor.id;
-                                } else {
-                                        editor = tinymce.get( id );
-                                }
</del><ins>+                                escape = function() {
+                                        // Only run this event once.
+                                        this.off( 'escape', escape );
</ins><span class="cx"> 
</span><del>-                                // Save a bookmark of the caret position, needed for IE
-                                if ( tinymce.isIE &amp;&amp; editor &amp;&amp; ! editor.isHidden() ) {
-                                        editor.focus();
-                                        editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
-                                }
-                        }
</del><ins>+                                        // If we're still on the 'featured-image' state, restore
+                                        // the initial state.
+                                        if ( 'featured-image' === this.state().id )
+                                                this.setState( initialState );
+                                };
</ins><span class="cx"> 
</span><del>-                        // Last but not least, fall back to the empty string.
-                        id = id || '';
</del><ins>+                                frame.on( 'escape', escape, frame );
</ins><span class="cx"> 
</span><del>-                        workflow = wp.media.editor.get( id );
</del><ins>+                                frame.setState('featured-image').open();
</ins><span class="cx"> 
</span><del>-                        // If the workflow exists, open it.
-                        // Initialize the editor's workflow if we haven't yet.
-                        if ( workflow )
-                                workflow.open();
-                        else
-                                workflow = wp.media.editor.add( id );
-
-                        return workflow;
</del><ins>+                        // Update the featured image id when the 'remove' link is clicked.
+                        }).on( 'click', '#remove-post-thumbnail', function() {
+                                wp.media.view.settings.featuredImage.id = -1;
+                        });
</ins><span class="cx">                 }
</span><span class="cx">         };
</span><span class="cx"> 
</span><ins>+        _.bindAll( wp.media.editor, 'open' );
</ins><span class="cx">         $( wp.media.editor.init );
</span><span class="cx"> }(jQuery));
</span></span></pre></div>
<a id="trunkwpincludesjsmediamodelsjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/media-models.js (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/media-models.js        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-includes/js/media-models.js        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -30,10 +30,8 @@
</span><span class="cx">                         frame = new MediaFrame.Post( attributes );
</span><span class="cx"> 
</span><span class="cx">                 delete attributes.frame;
</span><del>-                // Set the default state.
-                frame.setState( frame.options.state );
-                // Render, attach, and open the frame.
-                return frame.render().attach().open();
</del><ins>+
+                return frame;
</ins><span class="cx">         };
</span><span class="cx"> 
</span><span class="cx">         _.extend( media, { model: {}, view: {}, controller: {} });
</span><span class="lines">@@ -235,6 +233,9 @@
</span><span class="cx"> 
</span><span class="cx">                         // Overload the `update` request so properties can be saved.
</span><span class="cx">                         } else if ( 'update' === method ) {
</span><ins>+                                if ( ! this.get('nonces') )
+                                        return $.Deferred().resolveWith( this ).promise();
+
</ins><span class="cx">                                 options = options || {};
</span><span class="cx">                                 options.context = this;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkwpincludesjsmediaviewsjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/media-views.js (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/media-views.js        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-includes/js/media-views.js        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -170,7 +170,7 @@
</span><span class="cx">                         // created the `states` collection, or are trying to select a state
</span><span class="cx">                         // that does not exist.
</span><span class="cx">                         if ( ( previous &amp;&amp; id === previous.id ) || ! this.states || ! this.states.get( id ) )
</span><del>-                                return;
</del><ins>+                                return this;
</ins><span class="cx"> 
</span><span class="cx">                         if ( previous ) {
</span><span class="cx">                                 previous.trigger('deactivate');
</span><span class="lines">@@ -179,6 +179,8 @@
</span><span class="cx"> 
</span><span class="cx">                         this._state = id;
</span><span class="cx">                         this.state().trigger('activate');
</span><ins>+
+                        return this;
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 // Returns the previous active state.
</span><span class="lines">@@ -549,7 +551,41 @@
</span><span class="cx">                 }
</span><span class="cx">         });
</span><span class="cx"> 
</span><ins>+        // wp.media.controller.FeaturedImage
+        // ---------------------------------
+        media.controller.FeaturedImage = media.controller.Library.extend({
+                defaults: _.defaults({
+                        id:         'featured-image',
+                        filterable: 'uploaded',
+                        multiple:   false,
+                        menu:       'main',
+                        toolbar:    'featured-image'
+                }, media.controller.Library.prototype.defaults ),
</ins><span class="cx"> 
</span><ins>+                initialize: function() {
+                        // If we haven't been provided a `library`, create a `Selection`.
+                        if ( ! this.get('library') )
+                                this.set( 'library', media.query({ type: 'image' }) );
+
+                        media.controller.Library.prototype.initialize.apply( this, arguments );
+                },
+
+                activate: function() {
+                        var selection = this.get('selection'),
+                                id = media.view.settings.featuredImage.id,
+                                attachment;
+
+                        if ( '' !== id &amp;&amp; -1 !== id ) {
+                                attachment = Attachment.get( id );
+                                attachment.fetch();
+                        }
+
+                        selection.reset( attachment ? [ attachment ] : [] );
+                        media.controller.Library.prototype.activate.apply( this, arguments );
+                }
+        });
+
+
</ins><span class="cx">         // wp.media.controller.Embed
</span><span class="cx">         // -------------------------
</span><span class="cx">         media.controller.Embed = media.controller.State.extend({
</span><span class="lines">@@ -605,7 +641,9 @@
</span><span class="cx">                         }, this );
</span><span class="cx"> 
</span><span class="cx">                         this.set( 'url', '' );
</span><del>-                        this.frame.toolbar.view().refresh();
</del><ins>+
+                        if ( this.id === this.frame.state().id )
+                                this.frame.toolbar.view().refresh();
</ins><span class="cx">                 }
</span><span class="cx">         });
</span><span class="cx"> 
</span><span class="lines">@@ -1077,9 +1115,10 @@
</span><span class="cx">                         if ( this.options.modal ) {
</span><span class="cx">                                 this.modal = new media.view.Modal({
</span><span class="cx">                                         controller: this,
</span><del>-                                        $content:   this.$el,
</del><span class="cx">                                         title:      this.options.title
</span><span class="cx">                                 });
</span><ins>+
+                                this.modal.content( this );
</ins><span class="cx">                         }
</span><span class="cx"> 
</span><span class="cx">                         // Force the uploader off if the upload limit has been exceeded or
</span><span class="lines">@@ -1102,14 +1141,6 @@
</span><span class="cx">                         this.on( 'attach', _.bind( this.views.ready, this.views ), this );
</span><span class="cx">                 },
</span><span class="cx"> 
</span><del>-                render: function() {
-                        if ( this.modal )
-                                this.modal.render();
-
-                        media.view.Frame.prototype.render.apply( this, arguments );
-                        return this;
-                },
-
</del><span class="cx">                 createIframeStates: function( options ) {
</span><span class="cx">                         var settings = media.view.settings,
</span><span class="cx">                                 tabs = settings.tabs,
</span><span class="lines">@@ -1186,7 +1217,7 @@
</span><span class="cx">         });
</span><span class="cx"> 
</span><span class="cx">         // Map some of the modal's methods to the frame.
</span><del>-        _.each(['open','close','attach','detach'], function( method ) {
</del><ins>+        _.each(['open','close','attach','detach','escape'], function( method ) {
</ins><span class="cx">                 media.view.MediaFrame.prototype[ method ] = function( view ) {
</span><span class="cx">                         if ( this.modal )
</span><span class="cx">                                 this.modal[ method ].apply( this.modal, arguments );
</span><span class="lines">@@ -1202,7 +1233,6 @@
</span><span class="cx">                         media.view.MediaFrame.prototype.initialize.apply( this, arguments );
</span><span class="cx"> 
</span><span class="cx">                         _.defaults( this.options, {
</span><del>-                                state:     'upload',
</del><span class="cx">                                 selection: [],
</span><span class="cx">                                 library:   {},
</span><span class="cx">                                 multiple:  false
</span><span class="lines">@@ -1349,7 +1379,6 @@
</span><span class="cx">         media.view.MediaFrame.Post = media.view.MediaFrame.Select.extend({
</span><span class="cx">                 initialize: function() {
</span><span class="cx">                         _.defaults( this.options, {
</span><del>-                                state:     'upload',
</del><span class="cx">                                 multiple:  true,
</span><span class="cx">                                 editing:   false
</span><span class="cx">                         });
</span><span class="lines">@@ -1409,6 +1438,14 @@
</span><span class="cx">                                         libraryState: 'gallery-edit'
</span><span class="cx">                                 })
</span><span class="cx">                         ]);
</span><ins>+
+
+                        if ( media.view.settings.featuredImage ) {
+                                this.states.add( new media.controller.FeaturedImage({
+                                        controller: this,
+                                        menu:       'main'
+                                }) );
+                        }
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 bindHandlers: function() {
</span><span class="lines">@@ -1427,6 +1464,7 @@
</span><span class="cx">                                         toolbar: {
</span><span class="cx">                                                 'main-attachments': 'mainAttachmentsToolbar',
</span><span class="cx">                                                 'main-embed':       'mainEmbedToolbar',
</span><ins>+                                                'featured-image':   'featuredImageToolbar',
</ins><span class="cx">                                                 'gallery-edit':     'galleryEditToolbar',
</span><span class="cx">                                                 'gallery-add':      'galleryAddToolbar'
</span><span class="cx">                                         }
</span><span class="lines">@@ -1444,15 +1482,22 @@
</span><span class="cx">                         media.view.MediaFrame.Select.prototype.mainMenu.call( this, { silent: true });
</span><span class="cx"> 
</span><span class="cx">                         this.menu.view().set({
</span><del>-                                separateLibrary: new media.View({
</del><ins>+                                'library-separator': new media.View({
</ins><span class="cx">                                         className: 'separator',
</span><span class="cx">                                         priority: 60
</span><span class="cx">                                 }),
</span><del>-                                embed: {
</del><ins>+                                'embed': {
</ins><span class="cx">                                         text: l10n.fromUrlTitle,
</span><span class="cx">                                         priority: 80
</span><span class="cx">                                 }
</span><span class="cx">                         });
</span><ins>+
+                        if ( media.view.settings.featuredImage ) {
+                                this.menu.view().set( 'featured-image', {
+                                        text: l10n.featuredImageTitle,
+                                        priority: 100
+                                });
+                        }
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 galleryMenu: function() {
</span><span class="lines">@@ -1559,6 +1604,14 @@
</span><span class="cx">                         }) );
</span><span class="cx">                 },
</span><span class="cx"> 
</span><ins>+                featuredImageToolbar: function() {
+                        this.toolbar.view( new media.view.Toolbar.Select({
+                                controller: this,
+                                text:       l10n.setFeaturedImage,
+                                state:      this.options.state || 'upload'
+                        }) );
+                },
+
</ins><span class="cx">                 mainEmbedToolbar: function() {
</span><span class="cx">                         this.toolbar.view( new media.view.Toolbar.Embed({
</span><span class="cx">                                 controller: this
</span><span class="lines">@@ -1629,7 +1682,7 @@
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 events: {
</span><del>-                        'click .media-modal-backdrop, .media-modal-close': 'closeHandler',
</del><ins>+                        'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
</ins><span class="cx">                         'keydown': 'keydown'
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="lines">@@ -1643,56 +1696,73 @@
</span><span class="cx">                         });
</span><span class="cx">                 },
</span><span class="cx"> 
</span><del>-                render: function() {
-                        // Ensure content div exists.
-                        this.options.$content = this.options.$content || $('&lt;div /&gt;');
-
-                        // Detach the content element from the DOM to prevent
-                        // `this.$el.html()` from garbage collecting its events.
-                        this.options.$content.detach();
-
-                        this.$el.html( this.template({
</del><ins>+                prepare: function() {
+                        return {
</ins><span class="cx">                                 title: this.options.title
</span><del>-                        }) );
-
-                        this.options.$content.addClass('media-modal-content');
-                        this.$('.media-modal').append( this.options.$content );
-                        return this;
</del><ins>+                        };
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 attach: function() {
</span><ins>+                        if ( this.views.attached )
+                                return this;
+
+                        if ( ! this.views.rendered )
+                                this.render();
+
</ins><span class="cx">                         this.$el.appendTo( this.options.container );
</span><ins>+
+                        // Manually mark the view as attached and trigger ready.
+                        this.views.attached = true;
+                        this.views.ready();
+
</ins><span class="cx">                         return this.propagate('attach');
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 detach: function() {
</span><ins>+                        if ( this.$el.is(':visible') )
+                                this.close();
+
</ins><span class="cx">                         this.$el.detach();
</span><ins>+                        this.views.attached = false;
</ins><span class="cx">                         return this.propagate('detach');
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 open: function() {
</span><ins>+                        if ( this.$el.is(':visible') )
+                                return this;
+
+                        if ( ! this.views.attached )
+                                this.attach();
+
</ins><span class="cx">                         this.$el.show().focus();
</span><span class="cx">                         return this.propagate('open');
</span><span class="cx">                 },
</span><span class="cx"> 
</span><del>-                close: function() {
</del><ins>+                close: function( options ) {
+                        if ( ! this.views.attached || ! this.$el.is(':visible') )
+                                return this;
+
</ins><span class="cx">                         this.$el.hide();
</span><del>-                        return this.propagate('close');
</del><ins>+                        this.propagate('close');
+
+                        if ( options &amp;&amp; options.escape )
+                                this.propagate('escape');
+
+                        return this;
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><del>-                closeHandler: function( event ) {
</del><ins>+                escape: function() {
+                        return this.close({ escape: true });
+                },
+
+                escapeHandler: function( event ) {
</ins><span class="cx">                         event.preventDefault();
</span><del>-                        this.close();
</del><ins>+                        this.escape();
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><del>-                content: function( $content ) {
-                        // Detach any existing content to prevent events from being lost.
-                        if ( this.options.$content )
-                                this.options.$content.detach();
-
-                        // Set and render the content.
-                        this.options.$content = ( $content instanceof Backbone.View ) ? $content.$el : $content;
-                        return this.render();
</del><ins>+                content: function( content ) {
+                        this.views.set( '.media-modal-content', content );
+                        return this;
</ins><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 // Triggers a modal event and if the `propagate` option is set,
</span><span class="lines">@@ -1710,7 +1780,7 @@
</span><span class="cx">                         // Close the modal when escape is pressed.
</span><span class="cx">                         if ( 27 === event.which ) {
</span><span class="cx">                                 event.preventDefault();
</span><del>-                                this.close();
</del><ins>+                                this.escape();
</ins><span class="cx">                                 return;
</span><span class="cx">                         }
</span><span class="cx">                 }
</span></span></pre></div>
<a id="trunkwpincludesmediaphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/media.php (22978 => 22979)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/media.php        2012-12-03 00:42:26 UTC (rev 22978)
+++ trunk/wp-includes/media.php        2012-12-03 02:38:10 UTC (rev 22979)
</span><span class="lines">@@ -1434,6 +1434,16 @@
</span><span class="cx">                 $post = get_post( $args['post'] );
</span><span class="cx">                 $settings['postId'] = $post-&gt;ID;
</span><span class="cx">                 $settings['nonce']['updatePost'] = wp_create_nonce( 'update-post_' . $post-&gt;ID );
</span><ins>+
+                if ( current_theme_supports( 'post-thumbnails', $post-&gt;post_type ) &amp;&amp; post_type_supports( $post-&gt;post_type, 'thumbnail' ) ) {
+
+                        $featuredImageId = get_post_meta( $post-&gt;ID, '_thumbnail_id', true );
+
+                        $settings['featuredImage'] = array(
+                                'id'    =&gt; $featuredImageId ? $featuredImageId : -1,
+                                'nonce' =&gt; wp_create_nonce( 'set_post_thumbnail-' . $post-&gt;ID ),
+                        );
+                }
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         $hier = $post &amp;&amp; is_post_type_hierarchical( $post-&gt;post_type );
</span><span class="lines">@@ -1467,6 +1477,10 @@
</span><span class="cx">                 // From URL
</span><span class="cx">                 'fromUrlTitle'       =&gt; __( 'From URL' ),
</span><span class="cx"> 
</span><ins>+                // Featured Images
+                'featuredImageTitle'  =&gt; __( 'Featured Image' ),
+                'setFeaturedImage'    =&gt; __( 'Set featured image' ),
+
</ins><span class="cx">                 // Gallery
</span><span class="cx">                 'createGalleryTitle' =&gt; __( 'Create Gallery' ),
</span><span class="cx">                 'editGalleryTitle'   =&gt; __( 'Edit Gallery' ),
</span><span class="lines">@@ -1511,6 +1525,7 @@
</span><span class="cx">                 &lt;div class=&quot;media-modal wp-core-ui&quot;&gt;
</span><span class="cx">                         &lt;h3 class=&quot;media-modal-title&quot;&gt;{{ data.title }}&lt;/h3&gt;
</span><span class="cx">                         &lt;a class=&quot;media-modal-close media-modal-icon&quot; href=&quot;#&quot; title=&quot;&lt;?php esc_attr_e('Close'); ?&gt;&quot;&gt;&lt;/a&gt;
</span><ins>+                        &lt;div class=&quot;media-modal-content&quot;&gt;&lt;/div&gt;
</ins><span class="cx">                 &lt;/div&gt;
</span><span class="cx">                 &lt;div class=&quot;media-modal-backdrop&quot;&gt;
</span><span class="cx">                         &lt;div&gt;&lt;/div&gt;
</span></span></pre>
</div>
</div>

</body>
</html>