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 #21776.



git-svn-id: https://develop.svn.wordpress.org/trunk@22979 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Andrew Nacin 2012-12-03 02:38:10 +00:00
parent 8a6d1c5b89
commit f88b538001
10 changed files with 248 additions and 212 deletions

View File

@ -1674,23 +1674,31 @@ function wp_ajax_image_editor() {
}
function wp_ajax_set_post_thumbnail() {
$json = ! empty( $_REQUEST['json'] );
$post_ID = intval( $_POST['post_id'] );
if ( !current_user_can( 'edit_post', $post_ID ) )
wp_die( -1 );
if ( !current_user_can( 'edit_post', $post_ID ) ) {
$json ? wp_send_json_error() : wp_die( -1 );
}
$thumbnail_id = intval( $_POST['thumbnail_id'] );
check_ajax_referer( "set_post_thumbnail-$post_ID" );
if ( $thumbnail_id == '-1' ) {
if ( delete_post_thumbnail( $post_ID ) )
wp_die( _wp_post_thumbnail_html( null, $post_ID ) );
else
wp_die( 0 );
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 );
}
}
if ( set_post_thumbnail( $post_ID, $thumbnail_id ) )
wp_die( _wp_post_thumbnail_html( $thumbnail_id, $post_ID ) );
wp_die( 0 );
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 );
}
function wp_ajax_date_format() {

View File

@ -1001,119 +1001,6 @@ function link_advanced_meta_box($link) {
* @since 2.9.0
*/
function post_thumbnail_meta_box( $post ) {
global $_wp_additional_image_sizes;
?><script type="text/javascript">
jQuery( function($) {
var $element = $('#select-featured-image'),
$thumbnailId = $element.find('input[name="thumbnail_id"]'),
title = '<?php _e( "Choose a Featured Image" ); ?>',
update = '<?php _e( "Update Featured Image" ); ?>',
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 && -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();
$( '<img />', {
src: size.url,
width: size.width
}).prependTo( $element );
}
}
});
});
frame.toolbar.mode('select');
});
$element.on( 'click', '.remove', function( event ) {
event.preventDefault();
setFeaturedImage( -1 );
});
});
</script>
<?php
$thumbnail_id = get_post_meta( $post->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';
?><div id="select-featured-image"
class="<?php echo esc_attr( $classes ); ?>"
data-post-id="<?php echo esc_attr( $post->ID ); ?>">
<?php echo $thumbnail_html; ?>
<input type="hidden" name="thumbnail_id" value="<?php echo esc_attr( $thumbnail_id ); ?>" />
<a href="#" class="choose button-secondary"><?php _e( 'Choose a Featured Image' ); ?></a>
<a href="#" class="remove"><?php _e( 'Remove Featured Image' ); ?></a>
</div>
<?php
echo _wp_post_thumbnail_html( $thumbnail_id, $post->ID );
}

View File

@ -199,14 +199,6 @@ function edit_post( $post_data = null ) {
set_post_format( $post_ID, false );
}
// 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'] );
}
// Meta Stuff
if ( isset($post_data['meta']) && $post_data['meta'] ) {
foreach ( $post_data['meta'] as $key => $value ) {

View File

@ -57,7 +57,7 @@
});
});
frame.setState('library');
frame.setState('library').open();
});
});
})(jQuery);

View File

@ -40,7 +40,7 @@
});
});
frame.setState('library');
frame.setState('library').open();
});
});
}(jQuery));

View File

@ -403,6 +403,11 @@
*/
.media-frame {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.media-frame .region-content {

View File

@ -375,6 +375,7 @@
workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
frame: 'post',
state: 'upload',
title: wp.media.view.l10n.addMedia,
multiple: true
} ) );
@ -427,14 +428,52 @@
}
}, this );
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 );
return workflow;
},
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 && typeof tinymce !== 'undefined' && tinymce.activeEditor )
id = tinymce.activeEditor.id;
// Last but not least, fall back to the empty string.
id = id || '';
return id;
},
get: function( id ) {
id = this.id( id );
return workflows[ id ];
},
remove: function( id ) {
id = this.id( id );
delete workflows[ id ];
},
@ -497,6 +536,30 @@
}
},
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 && editor && ! 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();
},
init: function() {
$(document.body).on( 'click', '.insert-media', function( event ) {
var $this = $(this),
@ -513,45 +576,40 @@
wp.media.editor.open( editor );
});
},
open: function( id ) {
var workflow, editor;
// 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();
// If an empty `id` is provided, default to `wpActiveEditor`.
id = id || wpActiveEditor;
// Always get the 'content' frame, since this is tailored to post.php.
var frame = wp.media.editor.add('content'),
initialState = frame.state().id,
escape;
if ( typeof tinymce !== 'undefined' && 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 );
}
escape = function() {
// Only run this event once.
this.off( 'escape', escape );
// Save a bookmark of the caret position, needed for IE
if ( tinymce.isIE && editor && ! editor.isHidden() ) {
editor.focus();
editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
}
}
// If we're still on the 'featured-image' state, restore
// the initial state.
if ( 'featured-image' === this.state().id )
this.setState( initialState );
};
// Last but not least, fall back to the empty string.
id = id || '';
frame.on( 'escape', escape, frame );
workflow = wp.media.editor.get( id );
frame.setState('featured-image').open();
// 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;
// Update the featured image id when the 'remove' link is clicked.
}).on( 'click', '#remove-post-thumbnail', function() {
wp.media.view.settings.featuredImage.id = -1;
});
}
};
_.bindAll( wp.media.editor, 'open' );
$( wp.media.editor.init );
}(jQuery));

View File

@ -30,10 +30,8 @@ window.wp = window.wp || {};
frame = new MediaFrame.Post( attributes );
delete attributes.frame;
// Set the default state.
frame.setState( frame.options.state );
// Render, attach, and open the frame.
return frame.render().attach().open();
return frame;
};
_.extend( media, { model: {}, view: {}, controller: {} });
@ -235,6 +233,9 @@ window.wp = window.wp || {};
// Overload the `update` request so properties can be saved.
} else if ( 'update' === method ) {
if ( ! this.get('nonces') )
return $.Deferred().resolveWith( this ).promise();
options = options || {};
options.context = this;

View File

@ -170,7 +170,7 @@
// created the `states` collection, or are trying to select a state
// that does not exist.
if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) )
return;
return this;
if ( previous ) {
previous.trigger('deactivate');
@ -179,6 +179,8 @@
this._state = id;
this.state().trigger('activate');
return this;
},
// Returns the previous active state.
@ -549,6 +551,40 @@
}
});
// 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 ),
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 && -1 !== id ) {
attachment = Attachment.get( id );
attachment.fetch();
}
selection.reset( attachment ? [ attachment ] : [] );
media.controller.Library.prototype.activate.apply( this, arguments );
}
});
// wp.media.controller.Embed
// -------------------------
@ -605,6 +641,8 @@
}, this );
this.set( 'url', '' );
if ( this.id === this.frame.state().id )
this.frame.toolbar.view().refresh();
}
});
@ -1077,9 +1115,10 @@
if ( this.options.modal ) {
this.modal = new media.view.Modal({
controller: this,
$content: this.$el,
title: this.options.title
});
this.modal.content( this );
}
// Force the uploader off if the upload limit has been exceeded or
@ -1102,14 +1141,6 @@
this.on( 'attach', _.bind( this.views.ready, this.views ), this );
},
render: function() {
if ( this.modal )
this.modal.render();
media.view.Frame.prototype.render.apply( this, arguments );
return this;
},
createIframeStates: function( options ) {
var settings = media.view.settings,
tabs = settings.tabs,
@ -1186,7 +1217,7 @@
});
// Map some of the modal's methods to the frame.
_.each(['open','close','attach','detach'], function( method ) {
_.each(['open','close','attach','detach','escape'], function( method ) {
media.view.MediaFrame.prototype[ method ] = function( view ) {
if ( this.modal )
this.modal[ method ].apply( this.modal, arguments );
@ -1202,7 +1233,6 @@
media.view.MediaFrame.prototype.initialize.apply( this, arguments );
_.defaults( this.options, {
state: 'upload',
selection: [],
library: {},
multiple: false
@ -1349,7 +1379,6 @@
media.view.MediaFrame.Post = media.view.MediaFrame.Select.extend({
initialize: function() {
_.defaults( this.options, {
state: 'upload',
multiple: true,
editing: false
});
@ -1409,6 +1438,14 @@
libraryState: 'gallery-edit'
})
]);
if ( media.view.settings.featuredImage ) {
this.states.add( new media.controller.FeaturedImage({
controller: this,
menu: 'main'
}) );
}
},
bindHandlers: function() {
@ -1427,6 +1464,7 @@
toolbar: {
'main-attachments': 'mainAttachmentsToolbar',
'main-embed': 'mainEmbedToolbar',
'featured-image': 'featuredImageToolbar',
'gallery-edit': 'galleryEditToolbar',
'gallery-add': 'galleryAddToolbar'
}
@ -1444,15 +1482,22 @@
media.view.MediaFrame.Select.prototype.mainMenu.call( this, { silent: true });
this.menu.view().set({
separateLibrary: new media.View({
'library-separator': new media.View({
className: 'separator',
priority: 60
}),
embed: {
'embed': {
text: l10n.fromUrlTitle,
priority: 80
}
});
if ( media.view.settings.featuredImage ) {
this.menu.view().set( 'featured-image', {
text: l10n.featuredImageTitle,
priority: 100
});
}
},
galleryMenu: function() {
@ -1559,6 +1604,14 @@
}) );
},
featuredImageToolbar: function() {
this.toolbar.view( new media.view.Toolbar.Select({
controller: this,
text: l10n.setFeaturedImage,
state: this.options.state || 'upload'
}) );
},
mainEmbedToolbar: function() {
this.toolbar.view( new media.view.Toolbar.Embed({
controller: this
@ -1629,7 +1682,7 @@
},
events: {
'click .media-modal-backdrop, .media-modal-close': 'closeHandler',
'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
'keydown': 'keydown'
},
@ -1643,56 +1696,73 @@
});
},
render: function() {
// Ensure content div exists.
this.options.$content = this.options.$content || $('<div />');
// 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({
prepare: function() {
return {
title: this.options.title
}) );
this.options.$content.addClass('media-modal-content');
this.$('.media-modal').append( this.options.$content );
return this;
};
},
attach: function() {
if ( this.views.attached )
return this;
if ( ! this.views.rendered )
this.render();
this.$el.appendTo( this.options.container );
// Manually mark the view as attached and trigger ready.
this.views.attached = true;
this.views.ready();
return this.propagate('attach');
},
detach: function() {
if ( this.$el.is(':visible') )
this.close();
this.$el.detach();
this.views.attached = false;
return this.propagate('detach');
},
open: function() {
if ( this.$el.is(':visible') )
return this;
if ( ! this.views.attached )
this.attach();
this.$el.show().focus();
return this.propagate('open');
},
close: function() {
close: function( options ) {
if ( ! this.views.attached || ! this.$el.is(':visible') )
return this;
this.$el.hide();
return this.propagate('close');
this.propagate('close');
if ( options && options.escape )
this.propagate('escape');
return this;
},
closeHandler: function( event ) {
escape: function() {
return this.close({ escape: true });
},
escapeHandler: function( event ) {
event.preventDefault();
this.close();
this.escape();
},
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();
content: function( content ) {
this.views.set( '.media-modal-content', content );
return this;
},
// Triggers a modal event and if the `propagate` option is set,
@ -1710,7 +1780,7 @@
// Close the modal when escape is pressed.
if ( 27 === event.which ) {
event.preventDefault();
this.close();
this.escape();
return;
}
}

View File

@ -1434,6 +1434,16 @@ function wp_enqueue_media( $args = array() ) {
$post = get_post( $args['post'] );
$settings['postId'] = $post->ID;
$settings['nonce']['updatePost'] = wp_create_nonce( 'update-post_' . $post->ID );
if ( current_theme_supports( 'post-thumbnails', $post->post_type ) && post_type_supports( $post->post_type, 'thumbnail' ) ) {
$featuredImageId = get_post_meta( $post->ID, '_thumbnail_id', true );
$settings['featuredImage'] = array(
'id' => $featuredImageId ? $featuredImageId : -1,
'nonce' => wp_create_nonce( 'set_post_thumbnail-' . $post->ID ),
);
}
}
$hier = $post && is_post_type_hierarchical( $post->post_type );
@ -1467,6 +1477,10 @@ function wp_enqueue_media( $args = array() ) {
// From URL
'fromUrlTitle' => __( 'From URL' ),
// Featured Images
'featuredImageTitle' => __( 'Featured Image' ),
'setFeaturedImage' => __( 'Set featured image' ),
// Gallery
'createGalleryTitle' => __( 'Create Gallery' ),
'editGalleryTitle' => __( 'Edit Gallery' ),
@ -1511,6 +1525,7 @@ function wp_print_media_templates() {
<div class="media-modal wp-core-ui">
<h3 class="media-modal-title">{{ data.title }}</h3>
<a class="media-modal-close media-modal-icon" href="#" title="<?php esc_attr_e('Close'); ?>"></a>
<div class="media-modal-content"></div>
</div>
<div class="media-modal-backdrop">
<div></div>