Make WordPress Core

Changeset 22979


Ignore:
Timestamp:
12/03/2012 02:38:10 AM (12 years ago)
Author:
nacin
Message:

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.

Location:
trunk
Files:
10 edited

Legend:

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

    r22967 r22979  
    16751675
    16761676function wp_ajax_set_post_thumbnail() {
     1677    $json = ! empty( $_REQUEST['json'] );
     1678
    16771679    $post_ID = intval( $_POST['post_id'] );
    1678     if ( !current_user_can( 'edit_post', $post_ID ) )
    1679         wp_die( -1 );
     1680    if ( !current_user_can( 'edit_post', $post_ID ) ) {
     1681        $json ? wp_send_json_error() : wp_die( -1 );
     1682    }
    16801683    $thumbnail_id = intval( $_POST['thumbnail_id'] );
    16811684
     
    16831686
    16841687    if ( $thumbnail_id == '-1' ) {
    1685         if ( delete_post_thumbnail( $post_ID ) )
    1686             wp_die( _wp_post_thumbnail_html( null, $post_ID ) );
    1687         else
    1688             wp_die( 0 );
    1689     }
    1690 
    1691     if ( set_post_thumbnail( $post_ID, $thumbnail_id ) )
    1692         wp_die( _wp_post_thumbnail_html( $thumbnail_id, $post_ID ) );
    1693     wp_die( 0 );
     1688        if ( delete_post_thumbnail( $post_ID ) ) {
     1689            $return = _wp_post_thumbnail_html( null, $post_ID );
     1690            $json ? wp_send_json_success( $return ) : wp_die( $return );
     1691        } else {
     1692            $json ? wp_send_json_error() : wp_die( 0 );
     1693        }
     1694    }
     1695
     1696    if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
     1697        $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
     1698        $json ? wp_send_json_success( $return ) : wp_die( $return );
     1699    }
     1700
     1701    $json ? wp_send_json_error() : wp_die( 0 );
    16941702}
    16951703
  • trunk/wp-admin/includes/meta-boxes.php

    r22962 r22979  
    10021002 */
    10031003function post_thumbnail_meta_box( $post ) {
    1004     global $_wp_additional_image_sizes;
    1005 
    1006     ?><script type="text/javascript">
    1007     jQuery( function($) {
    1008         var $element     = $('#select-featured-image'),
    1009             $thumbnailId = $element.find('input[name="thumbnail_id"]'),
    1010             title        = '<?php _e( "Choose a Featured Image" ); ?>',
    1011             update       = '<?php _e( "Update Featured Image" ); ?>',
    1012             Attachment   = wp.media.model.Attachment,
    1013             frame, setFeaturedImage;
    1014 
    1015         setFeaturedImage = function( thumbnailId ) {
    1016             var selection;
    1017 
    1018             $element.find('img').remove();
    1019             $element.toggleClass( 'has-featured-image', -1 != thumbnailId );
    1020             $thumbnailId.val( thumbnailId );
    1021 
    1022             if ( frame ) {
    1023                 selection = frame.state('library').get('selection');
    1024 
    1025                 if ( -1 === thumbnailId )
    1026                     selection.clear();
    1027                 else
    1028                     selection.add( Attachment.get( thumbnailId ) );
    1029             }
    1030         };
    1031 
    1032         $element.on( 'click', '.choose, img', function( event ) {
    1033             var options, thumbnailId, attachment;
    1034 
    1035             event.preventDefault();
    1036 
    1037             if ( frame ) {
    1038                 frame.open();
    1039                 return;
    1040             }
    1041 
    1042             options = {
    1043                 title:   title,
    1044                 library: {
    1045                     type: 'image'
    1046                 }
    1047             };
    1048 
    1049             thumbnailId = $thumbnailId.val();
    1050             if ( '' !== thumbnailId && -1 !== thumbnailId ) {
    1051                 attachment = Attachment.get( thumbnailId );
    1052                 attachment.fetch();
    1053                 options.selection = [ attachment ];
    1054             }
    1055 
    1056             frame = wp.media( options );
    1057 
    1058             frame.state('library').set( 'filterable', 'uploaded' );
    1059 
    1060             frame.toolbar.on( 'activate:select', function() {
    1061                 frame.toolbar.view().set({
    1062                     select: {
    1063                         style: 'primary',
    1064                         text:  update,
    1065 
    1066                         click: function() {
    1067                             var selection = frame.state().get('selection'),
    1068                                 model = selection.first(),
    1069                                 sizes = model.get('sizes'),
    1070                                 size;
    1071 
    1072                             setFeaturedImage( model.id );
    1073 
    1074                             // @todo: might need a size hierarchy equivalent.
    1075                             if ( sizes )
    1076                                 size = sizes['post-thumbnail'] || sizes.medium;
    1077 
    1078                             // @todo: Need a better way of accessing full size
    1079                             // data besides just calling toJSON().
    1080                             size = size || model.toJSON();
    1081 
    1082                             frame.close();
    1083 
    1084                             $( '<img />', {
    1085                                 src:    size.url,
    1086                                 width:  size.width
    1087                             }).prependTo( $element );
    1088                         }
    1089                     }
    1090                 });
    1091             });
    1092 
    1093             frame.toolbar.mode('select');
    1094         });
    1095 
    1096         $element.on( 'click', '.remove', function( event ) {
    1097             event.preventDefault();
    1098             setFeaturedImage( -1 );
    1099         });
    1100     });
    1101     </script>
    1102 
    1103     <?php
    1104     $thumbnail_id   = get_post_meta( $post->ID, '_thumbnail_id', true );
    1105     $thumbnail_size = isset( $_wp_additional_image_sizes['post-thumbnail'] ) ? 'post-thumbnail' : 'medium';
    1106     $thumbnail_html = wp_get_attachment_image( $thumbnail_id, $thumbnail_size );
    1107 
    1108     $classes = empty( $thumbnail_id ) ? '' : 'has-featured-image';
    1109 
    1110     ?><div id="select-featured-image"
    1111         class="<?php echo esc_attr( $classes ); ?>"
    1112         data-post-id="<?php echo esc_attr( $post->ID ); ?>">
    1113         <?php echo $thumbnail_html; ?>
    1114         <input type="hidden" name="thumbnail_id" value="<?php echo esc_attr( $thumbnail_id ); ?>" />
    1115         <a href="#" class="choose button-secondary"><?php _e( 'Choose a Featured Image' ); ?></a>
    1116         <a href="#" class="remove"><?php _e( 'Remove Featured Image' ); ?></a>
    1117     </div>
    1118     <?php
    1119 }
     1004    $thumbnail_id = get_post_meta( $post->ID, '_thumbnail_id', true );
     1005    echo _wp_post_thumbnail_html( $thumbnail_id, $post->ID );
     1006}
  • trunk/wp-admin/includes/post.php

    r22950 r22979  
    198198        elseif ( '0' == $post_data['post_format'] )
    199199            set_post_format( $post_ID, false );
    200     }
    201 
    202     // Featured Images
    203     if ( isset( $post_data['thumbnail_id'] ) ) {
    204         if ( '-1' == $post_data['thumbnail_id'] )
    205             delete_post_thumbnail( $post_ID );
    206         else
    207             set_post_thumbnail( $post_ID, $post_data['thumbnail_id'] );
    208200    }
    209201
  • trunk/wp-admin/js/custom-background.js

    r22952 r22979  
    5858            });
    5959
    60             frame.setState('library');
     60            frame.setState('library').open();
    6161        });
    6262    });
  • trunk/wp-admin/js/custom-header.js

    r22952 r22979  
    4141            });
    4242
    43             frame.setState('library');
     43            frame.setState('library').open();
    4444        });
    4545    });
  • trunk/wp-includes/css/media-views.css

    r22960 r22979  
    404404.media-frame {
    405405    overflow: hidden;
     406    position: absolute;
     407    top: 0;
     408    left: 0;
     409    right: 0;
     410    bottom: 0;
    406411}
    407412
  • trunk/wp-includes/js/media-editor.js

    r22972 r22979  
    376376            workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
    377377                frame:    'post',
     378                state:    'upload',
    378379                title:    wp.media.view.l10n.addMedia,
    379380                multiple: true
     
    428429            }, this );
    429430
     431            workflow.state('featured-image').on( 'select', function() {
     432                var settings = wp.media.view.settings,
     433                    featuredImage = settings.featuredImage,
     434                    selection = this.get('selection').single();
     435
     436                if ( ! featuredImage )
     437                    return;
     438
     439                featuredImage.id = selection ? selection.id : -1;
     440                wp.media.post( 'set-post-thumbnail', {
     441                    json:         true,
     442                    post_id:      settings.postId,
     443                    thumbnail_id: featuredImage.id,
     444                    _wpnonce:     featuredImage.nonce
     445                }).done( function( html ) {
     446                    $( '.inside', '#postimagediv' ).html( html );
     447                });
     448            });
     449
     450            workflow.setState( workflow.options.state );
    430451            return workflow;
    431452        },
    432453
     454        id: function( id ) {
     455            if ( id )
     456                return id;
     457
     458            // If an empty `id` is provided, default to `wpActiveEditor`.
     459            id = wpActiveEditor;
     460
     461            // If that doesn't work, fall back to `tinymce.activeEditor.id`.
     462            if ( ! id && typeof tinymce !== 'undefined' && tinymce.activeEditor )
     463                id = tinymce.activeEditor.id;
     464
     465            // Last but not least, fall back to the empty string.
     466            id = id || '';
     467            return id;
     468        },
     469
    433470        get: function( id ) {
     471            id = this.id( id );
    434472            return workflows[ id ];
    435473        },
    436474
    437475        remove: function( id ) {
     476            id = this.id( id );
    438477            delete workflows[ id ];
    439478        },
     
    498537        },
    499538
     539        open: function( id ) {
     540            var workflow, editor;
     541
     542            id = this.id( id );
     543
     544            // Save a bookmark of the caret position in IE.
     545            if ( typeof tinymce !== 'undefined' ) {
     546                editor = tinymce.get( id );
     547
     548                if ( tinymce.isIE && editor && ! editor.isHidden() ) {
     549                    editor.focus();
     550                    editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
     551                }
     552            }
     553
     554            workflow = this.get( id );
     555
     556            // Initialize the editor's workflow if we haven't yet.
     557            if ( ! workflow )
     558                workflow = this.add( id );
     559
     560            return workflow.open();
     561        },
     562
    500563        init: function() {
    501564            $(document.body).on( 'click', '.insert-media', function( event ) {
     
    514577                wp.media.editor.open( editor );
    515578            });
    516         },
    517 
    518         open: function( id ) {
    519             var workflow, editor;
    520 
    521             // If an empty `id` is provided, default to `wpActiveEditor`.
    522             id = id || wpActiveEditor;
    523 
    524             if ( typeof tinymce !== 'undefined' && tinymce.activeEditor ) {
    525                 // If that doesn't work, fall back to `tinymce.activeEditor`.
    526                 if ( ! id ) {
    527                     editor = tinymce.activeEditor;
    528                     id = id || editor.id;
    529                 } else {
    530                     editor = tinymce.get( id );
    531                 }
    532 
    533                 // Save a bookmark of the caret position, needed for IE
    534                 if ( tinymce.isIE && editor && ! editor.isHidden() ) {
    535                     editor.focus();
    536                     editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
    537                 }
    538             }
    539 
    540             // Last but not least, fall back to the empty string.
    541             id = id || '';
    542 
    543             workflow = wp.media.editor.get( id );
    544 
    545             // If the workflow exists, open it.
    546             // Initialize the editor's workflow if we haven't yet.
    547             if ( workflow )
    548                 workflow.open();
    549             else
    550                 workflow = wp.media.editor.add( id );
    551 
    552             return workflow;
     579
     580            // Open the content media manager to the 'featured image' tab when
     581            // the post thumbnail is clicked.
     582            $('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
     583                event.preventDefault();
     584                // Stop propagation to prevent thickbox from activating.
     585                event.stopPropagation();
     586
     587                // Always get the 'content' frame, since this is tailored to post.php.
     588                var frame = wp.media.editor.add('content'),
     589                    initialState = frame.state().id,
     590                    escape;
     591
     592                escape = function() {
     593                    // Only run this event once.
     594                    this.off( 'escape', escape );
     595
     596                    // If we're still on the 'featured-image' state, restore
     597                    // the initial state.
     598                    if ( 'featured-image' === this.state().id )
     599                        this.setState( initialState );
     600                };
     601
     602                frame.on( 'escape', escape, frame );
     603
     604                frame.setState('featured-image').open();
     605
     606            // Update the featured image id when the 'remove' link is clicked.
     607            }).on( 'click', '#remove-post-thumbnail', function() {
     608                wp.media.view.settings.featuredImage.id = -1;
     609            });
    553610        }
    554611    };
    555612
     613    _.bindAll( wp.media.editor, 'open' );
    556614    $( wp.media.editor.init );
    557615}(jQuery));
  • trunk/wp-includes/js/media-models.js

    r22968 r22979  
    3131
    3232        delete attributes.frame;
    33         // Set the default state.
    34         frame.setState( frame.options.state );
    35         // Render, attach, and open the frame.
    36         return frame.render().attach().open();
     33
     34        return frame;
    3735    };
    3836
     
    236234            // Overload the `update` request so properties can be saved.
    237235            } else if ( 'update' === method ) {
     236                if ( ! this.get('nonces') )
     237                    return $.Deferred().resolveWith( this ).promise();
     238
    238239                options = options || {};
    239240                options.context = this;
  • trunk/wp-includes/js/media-views.js

    r22967 r22979  
    171171            // that does not exist.
    172172            if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) )
    173                 return;
     173                return this;
    174174
    175175            if ( previous ) {
     
    180180            this._state = id;
    181181            this.state().trigger('activate');
     182
     183            return this;
    182184        },
    183185
     
    550552    });
    551553
     554    // wp.media.controller.FeaturedImage
     555    // ---------------------------------
     556    media.controller.FeaturedImage = media.controller.Library.extend({
     557        defaults: _.defaults({
     558            id:         'featured-image',
     559            filterable: 'uploaded',
     560            multiple:   false,
     561            menu:       'main',
     562            toolbar:    'featured-image'
     563        }, media.controller.Library.prototype.defaults ),
     564
     565        initialize: function() {
     566            // If we haven't been provided a `library`, create a `Selection`.
     567            if ( ! this.get('library') )
     568                this.set( 'library', media.query({ type: 'image' }) );
     569
     570            media.controller.Library.prototype.initialize.apply( this, arguments );
     571        },
     572
     573        activate: function() {
     574            var selection = this.get('selection'),
     575                id = media.view.settings.featuredImage.id,
     576                attachment;
     577
     578            if ( '' !== id && -1 !== id ) {
     579                attachment = Attachment.get( id );
     580                attachment.fetch();
     581            }
     582
     583            selection.reset( attachment ? [ attachment ] : [] );
     584            media.controller.Library.prototype.activate.apply( this, arguments );
     585        }
     586    });
     587
    552588
    553589    // wp.media.controller.Embed
     
    606642
    607643            this.set( 'url', '' );
    608             this.frame.toolbar.view().refresh();
     644
     645            if ( this.id === this.frame.state().id )
     646                this.frame.toolbar.view().refresh();
    609647        }
    610648    });
     
    10781116                this.modal = new media.view.Modal({
    10791117                    controller: this,
    1080                     $content:   this.$el,
    10811118                    title:      this.options.title
    10821119                });
     1120
     1121                this.modal.content( this );
    10831122            }
    10841123
     
    11031142        },
    11041143
    1105         render: function() {
    1106             if ( this.modal )
    1107                 this.modal.render();
    1108 
    1109             media.view.Frame.prototype.render.apply( this, arguments );
    1110             return this;
    1111         },
    1112 
    11131144        createIframeStates: function( options ) {
    11141145            var settings = media.view.settings,
     
    11871218
    11881219    // Map some of the modal's methods to the frame.
    1189     _.each(['open','close','attach','detach'], function( method ) {
     1220    _.each(['open','close','attach','detach','escape'], function( method ) {
    11901221        media.view.MediaFrame.prototype[ method ] = function( view ) {
    11911222            if ( this.modal )
     
    12031234
    12041235            _.defaults( this.options, {
    1205                 state:     'upload',
    12061236                selection: [],
    12071237                library:   {},
     
    13501380        initialize: function() {
    13511381            _.defaults( this.options, {
    1352                 state:     'upload',
    13531382                multiple:  true,
    13541383                editing:   false
     
    14101439                })
    14111440            ]);
     1441
     1442
     1443            if ( media.view.settings.featuredImage ) {
     1444                this.states.add( new media.controller.FeaturedImage({
     1445                    controller: this,
     1446                    menu:       'main'
     1447                }) );
     1448            }
    14121449        },
    14131450
     
    14281465                        'main-attachments': 'mainAttachmentsToolbar',
    14291466                        'main-embed':       'mainEmbedToolbar',
     1467                        'featured-image':   'featuredImageToolbar',
    14301468                        'gallery-edit':     'galleryEditToolbar',
    14311469                        'gallery-add':      'galleryAddToolbar'
     
    14451483
    14461484            this.menu.view().set({
    1447                 separateLibrary: new media.View({
     1485                'library-separator': new media.View({
    14481486                    className: 'separator',
    14491487                    priority: 60
    14501488                }),
    1451                 embed: {
     1489                'embed': {
    14521490                    text: l10n.fromUrlTitle,
    14531491                    priority: 80
    14541492                }
    14551493            });
     1494
     1495            if ( media.view.settings.featuredImage ) {
     1496                this.menu.view().set( 'featured-image', {
     1497                    text: l10n.featuredImageTitle,
     1498                    priority: 100
     1499                });
     1500            }
    14561501        },
    14571502
     
    15601605        },
    15611606
     1607        featuredImageToolbar: function() {
     1608            this.toolbar.view( new media.view.Toolbar.Select({
     1609                controller: this,
     1610                text:       l10n.setFeaturedImage,
     1611                state:      this.options.state || 'upload'
     1612            }) );
     1613        },
     1614
    15621615        mainEmbedToolbar: function() {
    15631616            this.toolbar.view( new media.view.Toolbar.Embed({
     
    16301683
    16311684        events: {
    1632             'click .media-modal-backdrop, .media-modal-close': 'closeHandler',
     1685            'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
    16331686            'keydown': 'keydown'
    16341687        },
     
    16441697        },
    16451698
    1646         render: function() {
    1647             // Ensure content div exists.
    1648             this.options.$content = this.options.$content || $('<div />');
    1649 
    1650             // Detach the content element from the DOM to prevent
    1651             // `this.$el.html()` from garbage collecting its events.
    1652             this.options.$content.detach();
    1653 
    1654             this.$el.html( this.template({
     1699        prepare: function() {
     1700            return {
    16551701                title: this.options.title
    1656             }) );
    1657 
    1658             this.options.$content.addClass('media-modal-content');
    1659             this.$('.media-modal').append( this.options.$content );
    1660             return this;
     1702            };
    16611703        },
    16621704
    16631705        attach: function() {
     1706            if ( this.views.attached )
     1707                return this;
     1708
     1709            if ( ! this.views.rendered )
     1710                this.render();
     1711
    16641712            this.$el.appendTo( this.options.container );
     1713
     1714            // Manually mark the view as attached and trigger ready.
     1715            this.views.attached = true;
     1716            this.views.ready();
     1717
    16651718            return this.propagate('attach');
    16661719        },
    16671720
    16681721        detach: function() {
     1722            if ( this.$el.is(':visible') )
     1723                this.close();
     1724
    16691725            this.$el.detach();
     1726            this.views.attached = false;
    16701727            return this.propagate('detach');
    16711728        },
    16721729
    16731730        open: function() {
     1731            if ( this.$el.is(':visible') )
     1732                return this;
     1733
     1734            if ( ! this.views.attached )
     1735                this.attach();
     1736
    16741737            this.$el.show().focus();
    16751738            return this.propagate('open');
    16761739        },
    16771740
    1678         close: function() {
     1741        close: function( options ) {
     1742            if ( ! this.views.attached || ! this.$el.is(':visible') )
     1743                return this;
     1744
    16791745            this.$el.hide();
    1680             return this.propagate('close');
    1681         },
    1682 
    1683         closeHandler: function( event ) {
     1746            this.propagate('close');
     1747
     1748            if ( options && options.escape )
     1749                this.propagate('escape');
     1750
     1751            return this;
     1752        },
     1753
     1754        escape: function() {
     1755            return this.close({ escape: true });
     1756        },
     1757
     1758        escapeHandler: function( event ) {
    16841759            event.preventDefault();
    1685             this.close();
    1686         },
    1687 
    1688         content: function( $content ) {
    1689             // Detach any existing content to prevent events from being lost.
    1690             if ( this.options.$content )
    1691                 this.options.$content.detach();
    1692 
    1693             // Set and render the content.
    1694             this.options.$content = ( $content instanceof Backbone.View ) ? $content.$el : $content;
    1695             return this.render();
     1760            this.escape();
     1761        },
     1762
     1763        content: function( content ) {
     1764            this.views.set( '.media-modal-content', content );
     1765            return this;
    16961766        },
    16971767
     
    17111781            if ( 27 === event.which ) {
    17121782                event.preventDefault();
    1713                 this.close();
     1783                this.escape();
    17141784                return;
    17151785            }
  • trunk/wp-includes/media.php

    r22978 r22979  
    14351435        $settings['postId'] = $post->ID;
    14361436        $settings['nonce']['updatePost'] = wp_create_nonce( 'update-post_' . $post->ID );
     1437
     1438        if ( current_theme_supports( 'post-thumbnails', $post->post_type ) && post_type_supports( $post->post_type, 'thumbnail' ) ) {
     1439
     1440            $featuredImageId = get_post_meta( $post->ID, '_thumbnail_id', true );
     1441
     1442            $settings['featuredImage'] = array(
     1443                'id'    => $featuredImageId ? $featuredImageId : -1,
     1444                'nonce' => wp_create_nonce( 'set_post_thumbnail-' . $post->ID ),
     1445            );
     1446        }
    14371447    }
    14381448
     
    14681478        'fromUrlTitle'       => __( 'From URL' ),
    14691479
     1480        // Featured Images
     1481        'featuredImageTitle'  => __( 'Featured Image' ),
     1482        'setFeaturedImage'    => __( 'Set featured image' ),
     1483
    14701484        // Gallery
    14711485        'createGalleryTitle' => __( 'Create Gallery' ),
     
    15121526            <h3 class="media-modal-title">{{ data.title }}</h3>
    15131527            <a class="media-modal-close media-modal-icon" href="#" title="<?php esc_attr_e('Close'); ?>"></a>
     1528            <div class="media-modal-content"></div>
    15141529        </div>
    15151530        <div class="media-modal-backdrop">
Note: See TracChangeset for help on using the changeset viewer.