Make WordPress Core

Ticket #28965: 28965.diff

File 28965.diff, 24.5 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..105a81b 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
    video#inline-media-node { 
    25962596/**
    25972597 * Media Grid
    25982598 */
    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 {
     2599.media-frame.mode-grid,
     2600.media-frame.mode-grid .media-frame-content,
     2601.media-frame.mode-grid .attachments-browser .attachments,
     2602.media-frame.mode-grid .uploader-inline-content {
    26032603        position: static;
    26042604}
    26052605
    26062606/* 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 {
     2607.media-frame.mode-grid .media-frame-title,
     2608.media-frame.mode-grid .media-frame-toolbar,
     2609.media-frame.mode-grid .media-frame-router,
     2610.media-frame.mode-grid .media-frame-menu {
    26102611        display: none;
    26112612}
    26122613
    2613 .media-grid-view .media-frame-content {
     2614.media-frame.mode-grid .media-frame-content {
    26142615        background-color: transparent;
    26152616        border: none;
    26162617}
    26172618
    2618 .media-grid-view .uploader-inline {
     2619.media-frame.mode-grid .uploader-inline {
    26192620        position: relative;
    26202621        top: auto;
    26212622        right: auto;
    video#inline-media-node { 
    26252626        margin-top: 0;
    26262627}
    26272628
    2628 .media-grid-view .media-toolbar select {
     2629.media-frame.mode-grid .media-toolbar select {
    26292630        margin-top: 1px;
    26302631        font-size: inherit;
    26312632}
    26322633
    2633 .media-grid-view .attachments-browser .bulk-select {
     2634.media-frame.mode-grid .attachments-browser .bulk-select {
    26342635        display: inline-block;
    26352636}
    26362637
    video#inline-media-node { 
    26392640 *
    26402641 * This should be OOCSS'd so both use a shared selector.
    26412642 */
    2642 .media-grid-view .attachments-browser .media-toolbar {
     2643.media-frame.mode-grid .attachments-browser .media-toolbar {
    26432644        background: #fff;
    26442645        -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
    26452646        box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
    video#inline-media-node { 
    26572658        border: none;
    26582659}
    26592660
    2660 .media-grid-view input[type="search"] {
     2661.media-frame.mode-grid input[type="search"] {
    26612662        margin: 1px;
    26622663        padding: 3px 5px;
    26632664        position: absolute;
    video#inline-media-node { 
    26692670        width: 280px;
    26702671}
    26712672
    2672 .media-grid-view .view-switch {
     2673.media-frame.mode-grid .view-switch {
    26732674        display: inline-block;
    26742675        float: none;
    26752676        vertical-align: middle;
    video#inline-media-node { 
    26772678        margin: 0 20px 0 0;
    26782679}
    26792680
    2680 .media-grid-view select {
     2681.media-frame.mode-grid select {
    26812682        margin: 0 10px 0 0;
    26822683}
    26832684
    2684 .media-grid-view .spinner {
     2685.media-frame.mode-grid .spinner {
    26852686        margin-top: 15px;
    26862687}
    26872688
    2688 .media-grid-view .attachments-browser {
     2689.media-frame.mode-grid .attachments-browser {
    26892690        padding: 0;
    26902691}
    26912692
    2692 .media-grid-view .attachments-browser .no-media {
     2693.media-frame.mode-grid .attachments-browser .no-media {
    26932694        color: #999;
    26942695        font-size: 18px;
    26952696        font-style: normal;
    video#inline-media-node { 
    28052806        top: 56px;
    28062807}
    28072808
    2808 /* Hiding this for the moment instead of removing it from the template. */
    2809 .edit-attachment-frame h3 {
    2810         display: none;
    2811 }
    2812 
    28132809.edit-attachment-frame .attachment-details {
    28142810        position: absolute;
    28152811        overflow: auto;
    video#inline-media-node { 
    29502946 */
    29512947
    29522948@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 {
     2949        .media-frame.mode-grid .attachments-browser .media-toolbar-primary,
     2950        .media-frame.mode-grid .attachments-browser .media-toolbar-secondary {
    29552951                float: none;
    29562952        }
    29572953
    2958         .media-grid-view input[type="search"] {
     2954        .media-frame.mode-grid input[type="search"] {
    29592955                margin: 20px 0;
    29602956                position: static;
    29612957                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..01ada52 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.
     
    12392                        }
    12493
    12594                        /**
    126                          * call 'initialize' directly on the parent class
     95                         * call 'initialize' directly on the parent class.
    12796                         */
    12897                        media.view.MediaFrame.prototype.initialize.apply( this, arguments );
    12998
    130                         // Since we're not using the default modal built into
    131                         // a media frame, append our $element to the supplied container.
     99                        // Append the frame view directly the supplied container.
    132100                        this.$el.appendTo( this.options.container );
    133101
    134                         this.createSelection();
    135102                        this.createStates();
    136                         this.bindHandlers();
     103                        this.bindRegionModeHandlers();
    137104                        this.render();
    138105
    139106                        // Update the URL when entering search string (at most once per second)
     
    148115                        _.delay( _.bind( this.createRouter, this ), 1000 );
    149116                },
    150117
    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 
    159118                createRouter: function() {
    160119                        this.gridRouter = new media.view.MediaFrame.Manage.Router();
    161120
     
    168127                        }
    169128                },
    170129
    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 
     130                /**
     131                 * Create the default states for the frame.
     132                 */
    186133                createStates: function() {
    187134                        var options = this.options;
    188135
     
    196143                                        library:    media.query( options.library ),
    197144                                        multiple:   options.multiple,
    198145                                        title:      options.title,
    199                                         priority:   20,
    200 
    201                                         router:     false,
    202146                                        content:    'browse',
    203147
    204148                                        filterable: 'mime-types'
     
    206150                        ]);
    207151                },
    208152
    209                 bindHandlers: function() {
    210                         this.on( 'content:create:browse', this.browseContent, this );
    211                         this.on( 'content:render:edit-image', this.editImageContent, this );
     153                /**
     154                 * Bind region mode activation events to proper handlers.
     155                 */
     156                bindRegionModeHandlers: function() {
     157                        this.on( 'content:create:browse', this.createViewForContentRegionInBrowseMode, this );
    212158
    213159                        // Handle a frame-level event for editing an attachment.
    214                         this.on( 'edit:attachment', this.editAttachment, this );
     160                        this.on( 'edit:attachment', this.openEditAttachmentModal, this );
    215161                },
    216162
     163                /**
     164                 * Click handler for the `Add New` button.
     165                 */
    217166                addNewClickHandler: function( event ) {
    218167                        event.preventDefault();
    219168                        this.trigger( 'toggle:upload:attachment' );
     
    222171                /**
    223172                 * Open the Edit Attachment modal.
    224173                 */
    225                 editAttachment: function( model ) {
     174                openEditAttachmentModal: function( model ) {
    226175                        // Create a new EditAttachment frame, passing along the library and the attachment model.
    227176                        wp.media( {
    228177                                frame:       'edit-attachments',
     
    233182                },
    234183
    235184                /**
    236                  * Content
     185                 * Create an attachments browser view within the content region.
    237186                 *
    238                  * @param {Object} content
     187                 * @param {Object} contentRegion Basic object with a `view` property, which
     188                 *                               should be set with the proper region view.
    239189                 * @this wp.media.controller.Region
    240190                 */
    241                 browseContent: function( content ) {
     191                createViewForContentRegionInBrowseMode: function( contentRegion ) {
    242192                        var state = this.state();
    243193
    244194                        // Browse our library of attachments.
    245                         content.view = new media.view.AttachmentsBrowser({
     195                        contentRegion.view = new media.view.AttachmentsBrowser({
    246196                                controller: this,
    247197                                collection: state.get('library'),
    248198                                selection:  state.get('selection'),
     
    259209
    260210                                AttachmentView: state.get('AttachmentView')
    261211                        });
    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 
    273212                }
    274213        });
    275214
     215        /**
     216         * A similar view to media.view.Attachment.Details
     217         * for use in the Edit Attachment modal.
     218         */
    276219        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                 },
     220                template: media.template( 'attachment-details-two-column' ),
    309221
    310222                preDestroy: function( event ) {
    311223                        event.preventDefault();
     
    324236                        media.view.Attachment.Details.prototype.deleteAttachment.apply( this, arguments );
    325237                },
    326238
    327                 handleEditImageClick: function() {
     239                editAttachment: function( event ) {
     240                        event.preventDefault();
    328241                        this.controller.setState( 'edit-image' );
    329242                },
    330243
     244                /**
     245                 * Noop this from parent class, doesn't apply here.
     246                 */
     247                toggleSelectionHandler: function() {},
     248
    331249                afterDelete: function( model ) {
    332250                        if ( ! model.destroyed ) {
    333251                                return;
     
    444362                        this.createStates();
    445363
    446364                        this.on( 'content:render:edit-metadata', this.editMetadataContent, this );
    447                         this.on( 'content:render:edit-image', this.editImageContentUgh, this );
     365                        this.on( 'content:create:edit-image', this.createViewForContentRegionInEditImageMode, this );
     366                        // this.on( 'content:render:edit-image', this.rendereditImageContentUgh, this );
    448367                        this.on( 'close', this.detach );
    449368
    450369                        // Bind default title creation.
     
    512431                },
    513432
    514433                /**
    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.
    517                  */
    518                 editImageContentUgh: function() {
    519                         _.defer( _.bind( this.editImageContent, this ) );
    520                 },
    521 
    522                 /**
    523434                 * Render the EditImage view into the frame's content region.
    524435                 */
    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();
     436                createViewForContentRegionInEditImageMode: function( contentRegion ) {
     437                        contentRegion.view = new media.view.EditImage( { model: this.model, controller: this } );
     438                        // Defer a call to load the editor, which
     439                        // requires DOM elements to exist.
     440                        _.defer( _.bind( contentRegion.view.loadEditor, contentRegion.view ) );
    532441                },
    533442
    534443                resetContent: function() {
  • 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 ) { #>