Make WordPress Core

Ticket #28965: 28965.2.diff

File 28965.2.diff, 29.7 KB (added by ericlewis, 10 years ago)
  • src/wp-includes/css/media-views.css

    diff --git a/src/wp-includes/css/media-views.css b/src/wp-includes/css/media-views.css
    index 907f252..120ffdd 100644
    a b  
    912912}
    913913
    914914.attachment.details,
    915 .media-grid-view .selected.attachment {
     915.media-frame.mode-grid .selected.attachment {
    916916        -webkit-box-shadow: 0 0 0 1px #fff,
    917917                                0 0 0 5px #1e8cbe;
    918918        box-shadow: 0 0 0 1px #fff,
     
    921921
    922922.attachment.details .check,
    923923.attachment.selected .check:focus,
    924 .media-grid-view .attachment.selected .check {
     924.media-frame.mode-grid .attachment.selected .check {
    925925        background-color: #1e8cbe;
    926926        -webkit-box-shadow: 0 0 0 1px #fff,
    927927                                0 0 0 2px #1e8cbe;
     
    929929                                0 0 0 2px #1e8cbe;
    930930}
    931931
    932 .media-grid-view .attachment .check {
     932.media-frame.mode-grid .attachment .check {
    933933        display: block;
    934934}
    935935
    936 .media-grid-view .attachment .check div {
     936.media-frame.mode-grid .attachment .check div {
    937937        background-position: 21px 0;
    938938}
    939939
    940940.attachment.details .check div,
    941 .media-grid-view .attachment.selected .check div {
     941.media-frame.mode-grid .attachment.selected .check div {
    942942        background-position: -21px 0;
    943943}
    944944
    945945.attachment.details .check:hover div,
    946946.attachment.selected .check:focus div,
    947 .media-grid-view .attachment.selected .check:hover div {
     947.media-frame.mode-grid .attachment.selected .check:hover div {
    948948        background-position: -60px 0;
    949949}
    950950
     
    10681068        display: inline-block;
    10691069}
    10701070
    1071 .attachment-preview:hover ~ .inline-toolbar {
     1071.attachment-preview:hover + .inline-toolbar,
     1072.inline-toolbar:hover {
    10721073        display: block;
    10731074}
    10741075
    video#inline-media-node { 
    25962597/**
    25972598 * Media Grid
    25982599 */
    2599 .media-grid-view,
    2600 .media-grid-view .media-frame-content,
    2601 .media-grid-view .attachments-browser .attachments,
    2602 .media-grid-view .uploader-inline-content {
     2600.media-frame.mode-grid,
     2601.media-frame.mode-grid .media-frame-content,
     2602.media-frame.mode-grid .attachments-browser .attachments,
     2603.media-frame.mode-grid .uploader-inline-content {
    26032604        position: static;
    26042605}
    26052606
    26062607/* Regions we don't use at all */
    2607 .media-grid-view .media-frame-title,
    2608 .media-grid-view .media-frame-toolbar,
    2609 .media-grid-view .media-frame-menu {
     2608.media-frame.mode-grid .media-frame-title,
     2609.media-frame.mode-grid .media-frame-toolbar,
     2610.media-frame.mode-grid .media-frame-router,
     2611.media-frame.mode-grid .media-frame-menu {
    26102612        display: none;
    26112613}
    26122614
    2613 .media-grid-view .media-frame-content {
     2615.media-frame.mode-grid .media-frame-content {
    26142616        background-color: transparent;
    26152617        border: none;
    26162618}
    26172619
    2618 .media-grid-view .uploader-inline {
     2620.media-frame.mode-grid .uploader-inline {
    26192621        position: relative;
    26202622        top: auto;
    26212623        right: auto;
    video#inline-media-node { 
    26252627        margin-top: 0;
    26262628}
    26272629
    2628 .media-grid-view .media-toolbar select {
     2630.media-frame.mode-grid .media-toolbar select {
    26292631        margin-top: 1px;
    26302632        font-size: inherit;
    26312633}
    26322634
    2633 .media-grid-view .attachments-browser .bulk-select {
     2635.media-frame.mode-grid .attachments-browser .bulk-select {
    26342636        display: inline-block;
    26352637}
    26362638
    video#inline-media-node { 
    26392641 *
    26402642 * This should be OOCSS'd so both use a shared selector.
    26412643 */
    2642 .media-grid-view .attachments-browser .media-toolbar {
     2644.media-frame.mode-grid .attachments-browser .media-toolbar {
    26432645        background: #fff;
    26442646        -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
    26452647        box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
    video#inline-media-node { 
    26572659        border: none;
    26582660}
    26592661
    2660 .media-grid-view input[type="search"] {
     2662.media-frame.mode-grid input[type="search"] {
    26612663        margin: 1px;
    26622664        padding: 3px 5px;
    26632665        position: absolute;
    video#inline-media-node { 
    26692671        width: 280px;
    26702672}
    26712673
    2672 .media-grid-view .view-switch {
     2674.media-frame.mode-grid .view-switch {
    26732675        display: inline-block;
    26742676        float: none;
    26752677        vertical-align: middle;
    video#inline-media-node { 
    26772679        margin: 0 20px 0 0;
    26782680}
    26792681
    2680 .media-grid-view select {
     2682.media-frame.mode-grid select {
    26812683        margin: 0 10px 0 0;
    26822684}
    26832685
    2684 .media-grid-view .spinner {
     2686.media-frame.mode-grid .spinner {
    26852687        margin-top: 15px;
    26862688}
    26872689
    2688 .media-grid-view .attachments-browser {
     2690.media-frame.mode-grid .attachments-browser {
    26892691        padding: 0;
    26902692}
    26912693
    2692 .media-grid-view .attachments-browser .no-media {
     2694.media-frame.mode-grid .attachments-browser .no-media {
    26932695        color: #999;
    26942696        font-size: 18px;
    26952697        font-style: normal;
    video#inline-media-node { 
    28052807        top: 56px;
    28062808}
    28072809
    2808 /* Hiding this for the moment instead of removing it from the template. */
    2809 .edit-attachment-frame h3 {
    2810         display: none;
    2811 }
    2812 
    28132810.edit-attachment-frame .attachment-details {
    28142811        position: absolute;
    28152812        overflow: auto;
    video#inline-media-node { 
    29502947 */
    29512948
    29522949@media only screen and (max-width: 1120px) {
    2953         .media-grid-view .attachments-browser .media-toolbar-primary,
    2954         .media-grid-view .attachments-browser .media-toolbar-secondary {
     2950        .media-frame.mode-grid .attachments-browser .media-toolbar-primary,
     2951        .media-frame.mode-grid .attachments-browser .media-toolbar-secondary {
    29552952                float: none;
    29562953        }
    29572954
    2958         .media-grid-view input[type="search"] {
     2955        .media-frame.mode-grid input[type="search"] {
    29592956                margin: 20px 0;
    29602957                position: static;
    29612958                width: 100%;
  • src/wp-includes/js/media-grid.js

    diff --git a/src/wp-includes/js/media-grid.js b/src/wp-includes/js/media-grid.js
    index 67f4dc5..1aee748 100644
    a b  
    11/* global _wpMediaViewsL10n, MediaElementPlayer, _wpMediaGridSettings, confirm */
    22(function($, _, Backbone, wp) {
     3        // Local reference to the WordPress media namespace.
    34        var media = wp.media, l10n;
    45
    5         // Link any localized strings.
     6        // Link localized strings and settings.
    67        if ( media.view.l10n ) {
    78                l10n = media.view.l10n;
    89        } else {
     
    1112        }
    1213
    1314        /**
     15         * wp.media.controller.EditAttachmentMetadata
     16         *
    1417         * A state for editing an attachment's metadata.
    1518         *
    1619         * @constructor
     
    2023        media.controller.EditAttachmentMetadata = media.controller.State.extend({
    2124                defaults: {
    2225                        id:      'edit-attachment',
     26                        // Title string passed to the frame's title region view.
    2327                        title:   l10n.attachmentDetails,
    2428                        // Region mode defaults.
    25                         menu:    false,
    2629                        content: 'edit-metadata',
    27 
    28                         url:     ''
    29                 },
    30 
    31                 _ready: function() {},
    32 
    33                 /**
    34                  * Override media.controller.State._postActivate, since this state doesn't
    35                  * include the regions expected there.
    36                  */
    37                 _postActivate: function() {
    38                         this.frame.on( 'title:render:default', this._renderTitle, this );
    39 
    40                         this._title();
    41                         this._content();
    42                 },
    43 
    44                 /**
    45                  * @access private
    46                  */
    47                 _title: function() {
    48                         this.frame.title.render( this.get('titleMode') || 'default' );
    49                 },
    50                 /**
    51                  * @access private
    52                  */
    53                 _renderTitle: function( view ) {
    54                         view.$el.text( this.get('title') || '' );
    55                 },
    56 
    57                 _content: function() {
    58                         var mode = this.get( 'content' );
    59                         if ( mode ) {
    60                                 this.frame.content.render( mode );
    61                         }
     30                        menu:    false,
     31                        toolbar: false,
     32                        router:  false
    6233                }
    6334        });
    6435
     
    8758                                title:     '',
    8859                                modal:     false,
    8960                                selection: [],
    90                                 library:   {},
     61                                library:   {}, // Options hash for the query to the media library.
    9162                                multiple:  'add',
    9263                                state:     'library',
    9364                                uploader:  true,
    94                                 mode:      [ 'grid', 'edit' ]
     65                                mode:      [ 'grid' ]
    9566                        });
    9667
    9768                        $(document).on( 'click', '.add-new-h2', _.bind( this.addNewClickHandler, this ) );
    98                         $(document).on( 'screen:options:open', _.bind( this.screenOptionsOpen, this ) );
    99                         $(document).on( 'screen:options:close', _.bind( this.screenOptionsClose, this ) );
    10069
    10170                        // Ensure core and media grid view UI is enabled.
    102                         this.$el.addClass('wp-core-ui media-grid-view');
     71                        this.$el.addClass('wp-core-ui');
    10372
    10473                        // Force the uploader off if the upload limit has been exceeded or
    10574                        // if the browser isn't supported.
     
    12291                                this.options.uploader = false;
    12392                        }
    12493
    125                         /**
    126                          * call 'initialize' directly on the parent class
    127                          */
     94                        // Call 'initialize' directly on the parent class.
    12895                        media.view.MediaFrame.prototype.initialize.apply( this, arguments );
    12996
    130                         // Since we're not using the default modal built into
    131                         // a media frame, append our $element to the supplied container.
     97                        // Append the frame view directly the supplied container.
    13298                        this.$el.appendTo( this.options.container );
    13399
    134                         this.createSelection();
    135100                        this.createStates();
    136                         this.bindHandlers();
     101                        this.bindRegionModeHandlers();
    137102                        this.render();
    138103
    139104                        // Update the URL when entering search string (at most once per second)
     
    145110                                self.gridRouter.navigate( self.gridRouter.baseUrl( url ) );
    146111                        }, 1000 ) );
    147112
     113                        // This is problematic.
    148114                        _.delay( _.bind( this.createRouter, this ), 1000 );
    149115                },
    150116
    151                 screenOptionsOpen: function() {
    152                         this.$el.addClass( 'media-grid-view-options' );
    153                 },
    154 
    155                 screenOptionsClose: function() {
    156                         this.$el.removeClass( 'media-grid-view-options' );
    157                 },
    158 
    159117                createRouter: function() {
    160118                        this.gridRouter = new media.view.MediaFrame.Manage.Router();
    161119
     
    168126                        }
    169127                },
    170128
    171                 createSelection: function() {
    172                         var selection = this.options.selection;
    173 
    174                         if ( ! (selection instanceof media.model.Selection) ) {
    175                                 this.options.selection = new media.model.Selection( selection, {
    176                                         multiple: this.options.multiple
    177                                 });
    178                         }
    179 
    180                         this._selection = {
    181                                 attachments: new media.model.Attachments(),
    182                                 difference: []
    183                         };
    184                 },
    185 
     129                /**
     130                 * Create the default states for the frame.
     131                 */
    186132                createStates: function() {
    187133                        var options = this.options;
    188134
     
    196142                                        library:    media.query( options.library ),
    197143                                        multiple:   options.multiple,
    198144                                        title:      options.title,
    199                                         priority:   20,
    200 
    201                                         router:     false,
    202145                                        content:    'browse',
    203146
    204147                                        filterable: 'mime-types'
     
    206149                        ]);
    207150                },
    208151
    209                 bindHandlers: function() {
    210                         this.on( 'content:create:browse', this.browseContent, this );
    211                         this.on( 'content:render:edit-image', this.editImageContent, this );
     152                /**
     153                 * Bind region mode activation events to proper handlers.
     154                 */
     155                bindRegionModeHandlers: function() {
     156                        this.on( 'content:create:browse', this.createViewForContentRegionInBrowseMode, this );
    212157
    213158                        // Handle a frame-level event for editing an attachment.
    214                         this.on( 'edit:attachment', this.editAttachment, this );
     159                        this.on( 'edit:attachment', this.openEditAttachmentModal, this );
    215160                },
    216161
     162                /**
     163                 * Click handler for the `Add New` button.
     164                 */
    217165                addNewClickHandler: function( event ) {
    218166                        event.preventDefault();
    219167                        this.trigger( 'toggle:upload:attachment' );
     
    222170                /**
    223171                 * Open the Edit Attachment modal.
    224172                 */
    225                 editAttachment: function( model ) {
     173                openEditAttachmentModal: function( model ) {
    226174                        // Create a new EditAttachment frame, passing along the library and the attachment model.
    227175                        wp.media( {
    228176                                frame:       'edit-attachments',
     
    233181                },
    234182
    235183                /**
    236                  * Content
     184                 * Create an attachments browser view within the content region.
    237185                 *
    238                  * @param {Object} content
     186                 * @param {Object} contentRegion Basic object with a `view` property, which
     187                 *                               should be set with the proper region view.
    239188                 * @this wp.media.controller.Region
    240189                 */
    241                 browseContent: function( content ) {
     190                createViewForContentRegionInBrowseMode: function( contentRegion ) {
    242191                        var state = this.state();
    243192
    244193                        // Browse our library of attachments.
    245                         content.view = new media.view.AttachmentsBrowser({
     194                        contentRegion.view = new media.view.AttachmentsBrowser({
    246195                                controller: this,
    247196                                collection: state.get('library'),
    248197                                selection:  state.get('selection'),
     
    259208
    260209                                AttachmentView: state.get('AttachmentView')
    261210                        });
    262                 },
    263 
    264                 editImageContent: function() {
    265                         var image = this.state().get('image'),
    266                                 view = new media.view.EditImage( { model: image, controller: this } ).render();
    267 
    268                         this.content.set( view );
    269 
    270                         // after creating the wrapper view, load the actual editor via an ajax call
    271                         view.loadEditor();
    272 
    273211                }
    274212        });
    275213
     214        /**
     215         * A similar view to media.view.Attachment.Details
     216         * for use in the Edit Attachment modal.
     217         *
     218         * @constructor
     219         * @augments wp.media.view.Attachment.Details
     220         * @augments wp.media.view.Attachment
     221         * @augments wp.media.View
     222         * @augments wp.Backbone.View
     223         * @augments Backbone.View
     224         */
    276225        media.view.Attachment.Details.TwoColumn = media.view.Attachment.Details.extend({
    277                 template: wp.template( 'attachment-details-two-column' ),
    278 
    279                 events: {
    280                         'change [data-setting]':          'updateSetting',
    281                         'change [data-setting] input':    'updateSetting',
    282                         'change [data-setting] select':   'updateSetting',
    283                         'change [data-setting] textarea': 'updateSetting',
    284                         'click .delete-attachment':       'deleteAttachment',
    285                         'click .trash-attachment':        'trashAttachment',
    286                         'click .edit-attachment':         'editAttachment',
    287                         'click .refresh-attachment':      'refreshAttachment',
    288                         'click .edit-image':              'handleEditImageClick'
    289                 },
    290 
    291                 initialize: function() {
    292                         if ( ! this.model ) {
    293                                 return;
    294                         }
    295 
    296                         this.$el.attr('aria-label', this.model.get( 'title' ) ).attr( 'aria-checked', false );
    297 
    298                         this.model.on( 'change:title',   this._syncTitle, this );
    299                         this.model.on( 'change:caption', this._syncCaption, this );
    300                         this.model.on( 'change:percent', this.progress, this );
    301                         this.model.on( 'change:album',   this._syncAlbum, this );
    302                         this.model.on( 'change:artist',  this._syncArtist, this );
    303 
    304                         // Update the selection.
    305                         this.model.on( 'add', this.select, this );
    306                         this.model.on( 'remove', this.deselect, this );
    307                         this.model.on( 'sync', this.afterDelete, this );
    308                 },
     226                template: media.template( 'attachment-details-two-column' ),
    309227
    310228                preDestroy: function( event ) {
    311229                        event.preventDefault();
     
    324242                        media.view.Attachment.Details.prototype.deleteAttachment.apply( this, arguments );
    325243                },
    326244
    327                 handleEditImageClick: function() {
     245                editAttachment: function( event ) {
     246                        event.preventDefault();
    328247                        this.controller.setState( 'edit-image' );
    329248                },
    330249
     250                /**
     251                 * Noop this from parent class, doesn't apply here.
     252                 */
     253                toggleSelectionHandler: function() {},
     254
    331255                afterDelete: function( model ) {
    332256                        if ( ! model.destroyed ) {
    333257                                return;
     
    359283        });
    360284
    361285        /**
    362          * A router for handling the browser history and application state
     286         * A router for handling the browser history and application state.
     287         *
     288         * @constructor
     289         * @augments Backbone.Router
    363290         */
    364291        media.view.MediaFrame.Manage.Router = Backbone.Router.extend({
    365292                routes: {
    366                         'upload.php?item=:slug':    'showitem',
     293                        'upload.php?item=:slug':    'showItem',
    367294                        'upload.php?search=:query': 'search',
    368295                        ':default':                 'defaultRoute'
    369296                },
     
    381308                },
    382309
    383310                // Show the modal with a specific item
    384                 showitem: function( query ) {
     311                showItem: function( query ) {
    385312                        var library = media.frame.state().get('library');
    386313
    387314                        // Remove existing modal if present
     
    410337         * Opens in a modal by default.
    411338         *
    412339         * Requires an attachment model to be passed in the options hash under `model`.
     340         *
     341         * @constructor
     342         * @augments wp.media.view.Frame
     343         * @augments wp.media.View
     344         * @augments wp.Backbone.View
     345         * @augments Backbone.View
     346         * @mixes wp.media.controller.StateMachine
    413347         */
    414348        media.view.MediaFrame.EditAttachments = media.view.MediaFrame.extend({
    415349
     
    443377
    444378                        this.createStates();
    445379
    446                         this.on( 'content:render:edit-metadata', this.editMetadataContent, this );
    447                         this.on( 'content:render:edit-image', this.editImageContentUgh, this );
     380                        this.on( 'content:create:edit-metadata', this.createViewForContentRegionInEditMetadataMode, this );
     381                        this.on( 'content:create:edit-image', this.createViewForContentRegionInEditImageMode, this );
    448382                        this.on( 'close', this.detach );
    449383
    450384                        // Bind default title creation.
     
    473407                                        self.resetRoute();
    474408                                } );
    475409
     410                                // Set this frame as the modal's content.
    476411                                this.modal.content( this );
    477412                                this.modal.open();
    478413                        }
     
    497432                /**
    498433                 * Content region rendering callback for the `edit-metadata` mode.
    499434                 */
    500                 editMetadataContent: function() {
    501                         var view = new media.view.Attachment.Details.TwoColumn({
     435                createViewForContentRegionInEditMetadataMode: function( contentRegion ) {
     436                        contentRegion.view = new media.view.Attachment.Details.TwoColumn({
    502437                                controller: this,
    503438                                model:      this.model
    504439                        });
    505                         this.content.set( view );
    506440                        // Update browser url when navigating media details
    507441                        if ( this.model ) {
    508442                                this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
    509                         } else {
    510                                 this.resetRoute();
    511443                        }
    512444                },
    513445
    514446                /**
    515                  * For some reason the view doesn't exist in the DOM yet, don't have the
    516                  * patience to track this down right now.
     447                 * Render the EditImage view into the frame's content region.
    517448                 */
    518                 editImageContentUgh: function() {
    519                         _.defer( _.bind( this.editImageContent, this ) );
     449                createViewForContentRegionInEditImageMode: function( contentRegion ) {
     450                        contentRegion.view = new media.view.EditImage( { model: this.model, controller: this } );
     451                        // Defer a call to load the editor, which
     452                        // requires DOM elements to exist.
     453                        _.defer( _.bind( contentRegion.view.loadEditor, contentRegion.view ) );
    520454                },
    521455
    522456                /**
    523                  * Render the EditImage view into the frame's content region.
     457                 * Close this modal and immediately open another one.
     458                 *
     459                 * Allows for quickly swapping out the attachment being edited.
    524460                 */
    525                 editImageContent: function() {
    526                         var view = new media.view.EditImage( { model: this.model, controller: this } ).render();
    527 
    528                         this.content.set( view );
    529 
    530                         // after creating the wrapper view, load the actual editor via an ajax call
    531                         view.loadEditor();
    532                 },
    533 
    534461                resetContent: function() {
    535462                        this.modal.close();
    536463                        wp.media( {
     
    603530                }
    604531        });
    605532
     533        /**
     534         * Controller for bulk selection.
     535         */
    606536        media.view.BulkSelection = media.View.extend({
    607537                className: 'bulk-select',
    608538
     
    628558                }
    629559        });
    630560
     561        /**
     562         * Bulk Selection dropdown view.
     563         *
     564         * @constructor
     565         * @augments wp.media.View
     566         * @augments wp.Backbone.View
     567         * @augments Backbone.View
     568         */
    631569        media.view.BulkSelectionActionDropdown = media.View.extend({
    632570                tagName:   'select',
    633571
     
    637575                        this.$el.append( $('<option></option>').val( '' ).html( l10n.bulkActions ) )
    638576                                .append( $('<option></option>').val( 'delete' ).html( l10n.deletePermanently ) );
    639577                        this.$el.prop( 'disabled', true );
    640                         this.$el.on( 'change', _.bind( this.toggleChange, this ) );
     578                        this.$el.on( 'change', _.bind( this.changeHandler, this ) );
    641579                },
    642580
    643                 toggleChange: function() {
     581                /**
     582                 * Change handler for the dropdown.
     583                 *
     584                 * Sets the bulk selection controller's currentAction.
     585                 */
     586                changeHandler: function() {
    644587                        this.controller.model.set( { 'currentAction': this.$el.val() } );
    645588                },
     589
     590                /**
     591                 * Enable or disable the dropdown if attachments have been selected.
     592                 */
    646593                enabled: function() {
    647594                        var disabled = ! this.controller.controller.state().get('selection').length;
    648595                        this.$el.prop( 'disabled', disabled );
    649596                }
    650597        });
    651598
     599        /**
     600         * Bulk Selection dropdown view.
     601         *
     602         * @constructor
     603         *
     604         * @augments wp.media.view.Button
     605         * @augments wp.media.View
     606         * @augments wp.Backbone.View
     607         * @augments Backbone.View
     608         */
    652609        media.view.BulkSelectionActionButton = media.view.Button.extend({
    653610                tagName: 'button',
    654611
     
    658615                        this.listenTo( this.controller.model, 'change', this.enabled, this );
    659616                        this.listenTo( this.controller.controller.state().get( 'selection' ), 'add remove reset', _.bind( this.enabled, this ) );
    660617                },
    661 
     618                /**
     619                 * Button click handler.
     620                 */
    662621                click: function() {
    663622                        var selection = this.controller.controller.state().get('selection');
    664623                        media.view.Button.prototype.click.apply( this, arguments );
     
    672631
    673632                        this.enabled();
    674633                },
    675 
     634                /**
     635                 * Enable or disable the button depending if a bulk action is selected
     636                 * in the bulk select dropdown, and if attachments have been selected.
     637                 */
    676638                enabled: function() {
    677639                        var currentAction = this.controller.model.get( 'currentAction' ),
    678640                                selection = this.controller.controller.state().get('selection'),
     
    681643                }
    682644        });
    683645
    684 }(jQuery, _, Backbone, wp));
     646}(jQuery, _, Backbone, wp));
     647 No newline at end of file
  • src/wp-includes/js/media-views.js

    diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js
    index c57df05..ce8eadf 100644
    a b  
    16851685         */
    16861686        media.view.Frame = media.View.extend({
    16871687                initialize: function() {
     1688                        _.defaults( this.options, {
     1689                                mode: [ 'select' ]
     1690                        });
    16881691                        this._createRegions();
    16891692                        this._createStates();
     1693                        this._createModes();
    16901694                },
    16911695
    16921696                _createRegions: function() {
     
    17211725                                this.states.add( this.options.states );
    17221726                        }
    17231727                },
     1728                _createModes: function() {
     1729                        // Store active "modes" that the frame is in. Unrelated to region modes.
     1730                        this.activeModes = new Backbone.Collection();
     1731                        this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
     1732
     1733                        _.each( this.options.mode, function( mode ) {
     1734                                this.activateMode( mode );
     1735                        }, this );
     1736                },
    17241737                /**
    17251738                 * @returns {wp.media.view.Frame} Returns itself to allow chaining
    17261739                 */
    17271740                reset: function() {
    17281741                        this.states.invoke( 'trigger', 'reset' );
    17291742                        return this;
     1743                },
     1744                /**
     1745                 * Map activeMode collection events to the frame.
     1746                 */
     1747                triggerModeEvents: function( model, collection, options ) {
     1748                        var collectionEvent,
     1749                                modeEventMap = {
     1750                                        add: 'activate',
     1751                                        remove: 'deactivate'
     1752                                },
     1753                                eventToTrigger;
     1754                        // Probably a better way to do this.
     1755                        _.each( options, function( value, key ) {
     1756                                if ( value ) {
     1757                                        collectionEvent = key;
     1758                                }
     1759                        } );
     1760
     1761                        if ( ! _.has( modeEventMap, collectionEvent ) ) {
     1762                                return;
     1763                        }
     1764
     1765                        eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
     1766                        this.trigger( eventToTrigger );
     1767                },
     1768                /**
     1769                 * Activate a mode on the frame.
     1770                 *
     1771                 * @param string mode Mode ID.
     1772                 * @returns {this} Returns itself to allow chaining.
     1773                 */
     1774                activateMode: function( mode ) {
     1775                        // Bail if the mode is already active.
     1776                        if ( this.isModeActive( mode ) ) {
     1777                                return;
     1778                        }
     1779                        this.activeModes.add( [ { id: mode } ] );
     1780                        // Add a CSS class to the frame so elements can be styled for the mode.
     1781                        this.$el.addClass( 'mode-' + mode );
     1782                        /**
     1783                         * Frame mode activation event.
     1784                         *
     1785                         * @event this#{mode}:activate
     1786                         */
     1787                        this.trigger( mode + ':activate' );
     1788
     1789                        return this;
     1790                },
     1791                /**
     1792                 * Deactivate a mode on the frame.
     1793                 *
     1794                 * @param string mode Mode ID.
     1795                 * @returns {this} Returns itself to allow chaining.
     1796                 */
     1797                deactivateMode: function( mode ) {
     1798                        // Bail if the mode isn't active.
     1799                        if ( ! this.isModeActive( mode ) ) {
     1800                                return;
     1801                        }
     1802                        this.activeModes.remove( this.activeModes.where( { id: mode } ) );
     1803                        this.$el.removeClass( 'mode-' + mode );
     1804                        /**
     1805                         * Frame mode deactivation event.
     1806                         *
     1807                         * @event this#{mode}:deactivate
     1808                         */
     1809                        this.trigger( mode + ':deactivate' );
     1810
     1811                        return this;
     1812                },
     1813                /**
     1814                 * Check if a mode is enabled on the frame.
     1815                 *
     1816                 * @param  string mode Mode ID.
     1817                 * @return bool
     1818                 */
     1819                isModeActive: function( mode ) {
     1820                        return Boolean( this.activeModes.where( { id: mode } ).length );
    17301821                }
    17311822        });
    17321823
     
    17591850                        _.defaults( this.options, {
    17601851                                title:    '',
    17611852                                modal:    true,
    1762                                 uploader: true,
    1763                                 mode:     [ 'select' ]
     1853                                uploader: true
    17641854                        });
    17651855
    17661856                        // Ensure core UI is enabled.
     
    17761866                                this.modal.content( this );
    17771867                        }
    17781868
    1779                         // Store active "modes" that the frame is in. Unrelated to region modes.
    1780                         this.activeModes = new Backbone.Collection();
    1781                         this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
    1782 
    1783                         _.each( this.options.mode, function( mode ) {
    1784                                 this.activateMode( mode );
    1785                         }, this );
    1786 
    17871869                        // Force the uploader off if the upload limit has been exceeded or
    17881870                        // if the browser isn't supported.
    17891871                        if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
     
    19482030
    19492031                        window.tb_remove = this._tb_remove;
    19502032                        delete this._tb_remove;
    1951                 },
    1952 
    1953                 /**
    1954                  * Map activeMode collection events to the frame.
    1955                  */
    1956                 triggerModeEvents: function( model, collection, options ) {
    1957                         var collectionEvent,
    1958                                 modeEventMap = {
    1959                                         add: 'activate',
    1960                                         remove: 'deactivate'
    1961                                 },
    1962                                 eventToTrigger;
    1963                         // Probably a better way to do this.
    1964                         _.each( options, function( value, key ) {
    1965                                 if ( value ) {
    1966                                         collectionEvent = key;
    1967                                 }
    1968                         } );
    1969 
    1970                         if ( ! _.has( modeEventMap, collectionEvent ) )
    1971                                 return;
    1972 
    1973                         eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
    1974                         this.trigger( eventToTrigger );
    1975                 },
    1976                 /**
    1977                  * Activate a mode on the frame.
    1978                  *
    1979                  * @param string mode Mode ID.
    1980                  * @returns {this} Returns itself to allow chaining.
    1981                  */
    1982                 activateMode: function( mode ) {
    1983                         // Bail if the mode is already active.
    1984                         if ( this.isModeActive( mode ) ) {
    1985                                 return;
    1986                         }
    1987                         this.activeModes.add( [ { id: mode } ] );
    1988                         // Add a css class to the frame for anything that needs to be styled
    1989                         // for the mode.
    1990                         this.$el.addClass( 'mode-' + mode );
    1991                         /**
    1992                          * Frame mode activation event.
    1993                          *
    1994                          * @event this#{mode}:activate
    1995                          */
    1996                         this.trigger( mode + ':activate' );
    1997 
    1998                         return this;
    1999                 },
    2000                 /**
    2001                  * Deactivate a mode on the frame.
    2002                  *
    2003                  * @param string mode Mode ID.
    2004                  * @returns {this} Returns itself to allow chaining.
    2005                  */
    2006                 deactivateMode: function( mode ) {
    2007                         // Bail if the mode isn't active.
    2008                         if ( ! this.isModeActive( mode ) ) {
    2009                                 return;
    2010                         }
    2011                         this.activeModes.remove( this.activeModes.where( { id: mode } ) );
    2012                         this.$el.removeClass( 'mode-' + mode );
    2013                         /**
    2014                          * Frame mode deactivation event.
    2015                          *
    2016                          * @event this#{mode}:deactivate
    2017                          */
    2018                         this.trigger( mode + ':deactivate' );
    2019 
    2020                         return this;
    2021                 },
    2022                 /**
    2023                  * Check if a mode is enabled on the frame.
    2024                  *
    2025                  * @param  string mode Mode ID.
    2026                  * @return bool
    2027                  */
    2028                 isModeActive: function( mode ) {
    2029                         return Boolean( this.activeModes.where( { id: mode } ).length );
    20302033                }
    20312034        });
    20322035
     
    46314634                buttons: {},
    46324635
    46334636                initialize: function() {
    4634                         var selection = this.options.selection;
     4637                        var selection = this.options.selection,
     4638                                options = _.defaults( this.options, {
     4639                                        rerenderOnModelChange: true
     4640                                } );
     4641                        this.$el.attr( 'aria-label', this.model.get( 'title' ) ).attr( 'aria-checked', false );
    46354642
    4636                         this.$el.attr('aria-label', this.model.attributes.title).attr('aria-checked', false);
    4637                         this.model.on( 'change', this.render, this );
     4643                        if ( options.rerenderOnModelChange ) {
     4644                                this.model.on( 'change', this.render, this );
     4645                        }
    46384646                        this.model.on( 'change:title', this._syncTitle, this );
    46394647                        this.model.on( 'change:caption', this._syncCaption, this );
    46404648                        this.model.on( 'change:artist', this._syncArtist, this );
     
    46464654                        this.model.on( 'remove', this.deselect, this );
    46474655                        if ( selection ) {
    46484656                                selection.on( 'reset', this.updateSelect, this );
     4657                                // Update the model's details view.
     4658                                this.model.on( 'selection:single selection:unsingle', this.details, this );
     4659                                this.details( this.model, this.controller.state().get('selection') );
    46494660                        }
    4650 
    4651                         // Update the model's details view.
    4652                         this.model.on( 'selection:single selection:unsingle', this.details, this );
    4653                         this.details( this.model, this.controller.state().get('selection') );
    46544661                },
    46554662                /**
    46564663                 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
     
    47524759                        }
    47534760
    47544761                        // In the grid view, bubble up an edit:attachment event to the controller.
    4755                         if ( this.controller.activeModes.where( { id: 'edit' } ).length ) {
     4762                        if ( this.controller.isModeActive( 'grid' ) ) {
    47564763                                this.controller.trigger( 'edit:attachment', this.model );
    47574764                                return;
    47584765                        }
     
    63186325                },
    63196326
    63206327                initialize: function() {
     6328                        this.options = _.defaults( this.options, {
     6329                                rerenderOnModelChange: false
     6330                        });
    63216331                        /**
    63226332                         * call 'initialize' directly on the parent class
    63236333                         */
    63246334                        media.view.Attachment.prototype.initialize.apply( this, arguments );
    63256335                },
    63266336                /**
    6327                  * @returns {wp.media.view..Attachment.Details} Returns itself to allow chaining
    6328                  */
    6329                 render: function() {
    6330                         /**
    6331                          * call 'render' directly on the parent class
    6332                          */
    6333                         media.view.Attachment.prototype.render.apply( this, arguments );
    6334                         return this;
    6335                 },
    6336                 /**
    63376337                 * @param {Object} event
    63386338                 */
    63396339                deleteAttachment: function( event ) {
     
    63796379                        this.model.fetch();
    63806380                },
    63816381                /**
     6382                 * When reverse tabbing(shift+tab) out of the right details panel, deliver
     6383                 * the focus to the item in the list that was being edited.
     6384                 *
    63826385                 * @param {Object} event
    63836386                 */
    63846387                toggleSelectionHandler: function( event ) {
    6385                         // Reverse tabbing out of the right details panel
    6386                         // should take me back to the item in the list that was being edited.
    63876388                        if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === $( ':tabbable', this.$el ).filter( ':first' )[0] ) {
    63886389                                $('.attachments-browser .details').focus();
    63896390                                return false;
  • src/wp-includes/media-template.php

    diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php
    index c71086e..5ccc5ff 100644
    a b function wp_print_media_templates() { 
    263263        </script>
    264264
    265265        <script type="text/html" id="tmpl-attachment-details-two-column">
    266                 <h3>
    267                         <?php _e('Attachment Details'); ?>
    268                 </h3>
    269266                <div class="attachment-media-view">
    270267                        <div class="thumbnail thumbnail-{{ data.type }}">
    271268                                <# if ( data.uploading ) { #>
    function wp_print_media_templates() { 
    294291
    295292                                <div class="attachment-actions">
    296293                                        <# if ( 'image' === data.type && ! data.uploading ) { #>
    297                                                 <a class="button edit-image" href="#"><?php _e( 'Edit Image' ); ?></a>
     294                                                <a class="button edit-attachment" href="#"><?php _e( 'Edit Image' ); ?></a>
    298295                                        <# } #>
    299296
    300297                                        <# if ( ! data.uploading && data.can.remove ) { #>