WordPress.org

Make WordPress Core

Ticket #24716: 24716.29.diff

File 24716.29.diff, 8.9 KB (added by adamsilverstein, 3 years ago)

keyboard nav, history/routes

  • src/wp-admin/upload.php

     
    2424        wp_enqueue_media(); 
    2525        wp_enqueue_script( 'media-grid' ); 
    2626        wp_enqueue_script( 'media' ); 
     27        wp_localize_script( 'media-grid', 'mediaGridSettings', array( 'adminUrl' => parse_url( self_admin_url(), PHP_URL_PATH )  ) ); 
    2728 
    2829        require_once( ABSPATH . 'wp-admin/admin-header.php' ); 
    2930        include( ABSPATH . 'wp-admin/admin-footer.php' ); 
  • src/wp-includes/js/media-grid.js

     
    1 /* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer */ 
     1/* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer, mediaGridSettings*/ 
    22(function($, _, Backbone, wp) { 
    33        var media = wp.media, l10n; 
    44 
     
    120120                 * @global wp.Uploader 
    121121                 */ 
    122122                initialize: function() { 
     123                        var self = this; 
    123124                        _.defaults( this.options, { 
    124125                                title:     l10n.mediaLibraryTitle, 
    125126                                modal:     false, 
     
    168169                        this.createStates(); 
    169170                        this.bindHandlers(); 
    170171                        this.render(); 
     172 
     173                        // Set up the Backbone router after a brief delay 
     174                        _.delay( function(){ 
     175                                wp.media.mediarouter = new media.view.Frame.Router( self ); 
     176                                // Verify pushState support and activate 
     177                                if ( window.history && window.history.pushState ) { 
     178                                        Backbone.history.start({ 
     179                                                root: mediaGridSettings.adminUrl, 
     180                                                pushState: true 
     181                                        }); 
     182                                } 
     183                        }, 150); 
     184 
     185                        // Update the URL when entering search string (at most once per second) 
     186                        $( '#media-search-input' ).on( 'input', _.debounce( function() { 
     187                                if ( '' !== $( this ).val() ) { 
     188                                        wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '?search=' + $( this ).val() ) ); 
     189                                } 
     190                        }, 1000 ) ); 
    171191                }, 
    172192 
    173193                createSelection: function() { 
     
    213233 
    214234                        // Handle a frame-level event for editing an attachment. 
    215235                        this.on( 'edit:attachment', this.editAttachment, this ); 
    216                         this.on( 'edit:attachment:next', this.editNextAttachment, this ); 
    217                         this.on( 'edit:attachment:previous', this.editPreviousAttachment, this ); 
    218236                }, 
    219237 
    220                 editPreviousAttachment: function( currentModel ) { 
    221                         var library = this.state().get('library'), 
    222                                 currentModelIndex = library.indexOf( currentModel ); 
    223                         this.trigger( 'edit:attachment', library.at( currentModelIndex - 1 ) ); 
    224                 }, 
    225  
    226                 editNextAttachment: function( currentModel ) { 
    227                         var library = this.state().get('library'), 
    228                                 currentModelIndex = library.indexOf( currentModel ); 
    229                         this.trigger( 'edit:attachment', library.at( currentModelIndex + 1 ) ); 
    230                 }, 
    231  
    232238                /** 
    233239                 * Open the Edit Attachment modal. 
    234240                 */ 
    235241                editAttachment: function( model ) { 
    236                         var library = this.state().get('library'), hasPrevious, hasNext; 
    237                         if ( library.indexOf( model ) > 0 ) { 
    238                                 hasPrevious = true; 
    239                         } 
    240                         else { 
    241                                 hasPrevious = false; 
    242                         } 
    243                         if ( library.indexOf( model ) < library.length - 1 ) { 
    244                                 hasNext = true; 
    245                         } 
    246                         else { 
    247                                 hasNext = false; 
    248                         } 
    249  
    250                         new media.view.Frame.EditAttachment({ 
    251                                 hasPrevious:    hasPrevious, 
    252                                 hasNext:        hasNext, 
    253                                 model:          model, 
    254                                 gridController: this 
     242                        var self = this; 
     243                        // Create a new EditAttachment frame, passing along the library. 
     244                        this.editAttachmentFrame = new media.view.Frame.EditAttachments({ 
     245                                library: this.state().get('library'), 
     246                                model: model 
    255247                        }); 
     248                        $( 'body' ).on( 'keydown.media-modal', function( e ) { 
     249                                self.editAttachmentFrame.keyEvent( e ); 
     250                                } ); 
    256251                }, 
    257252 
    258253                /** 
     
    324319        }); 
    325320 
    326321        /** 
     322         * A router for handling the browser history and application state 
     323         */ 
     324        media.view.Frame.Router = Backbone.Router.extend({ 
     325 
     326                mediaFrame: '', 
     327 
     328                initialize: function( mediaFrame ){ 
     329                        this.mediaFrame = mediaFrame; 
     330                }, 
     331 
     332                routes: { 
     333                        'upload.php?item=:slug':    'showitem', 
     334                        'upload.php?search=:query': 'search', 
     335                        ':default':                 'defaultRoute' 
     336                }, 
     337 
     338                // Map routes against the page URL 
     339                baseUrl: function( url ) { 
     340                        return 'upload.php' + url; 
     341                }, 
     342 
     343                // Respond to the search route by filling the search field and trigggering the input event 
     344                search: function( query ) { 
     345                        // Ensure modal closed, see back button 
     346                        this.closeModal(); 
     347                        $( '#media-search-input' ).val( query ).trigger( 'input' ); 
     348                }, 
     349 
     350                // Show the modal with a specific item 
     351                showitem: function( query ) { 
     352                        var library = this.mediaFrame.state().get('library'); 
     353 
     354                        // Remove existing modal if present 
     355                        this.closeModal(); 
     356                        // Trigger the media frame to open the correct item 
     357                        this.mediaFrame.trigger( 'edit:attachment', library.findWhere( { id: parseInt( query, 10 ) } ) ); 
     358                }, 
     359 
     360                // Close the modal if set up 
     361                closeModal: function() { 
     362                        if ( 'undefined' !== typeof this.mediaFrame.editAttachmentFrame ) { 
     363                                this.mediaFrame.editAttachmentFrame.modal.close(); 
     364                        } 
     365                }, 
     366 
     367                // Default route: make sure the modal and search are reset 
     368                defaultRoute: function() { 
     369                        this.closeModal(); 
     370                        $( '#media-search-input' ).val( '' ).trigger( 'input' ); 
     371                } 
     372        }); 
     373 
     374        /** 
    327375         * A frame for editing the details of a specific media item. 
    328376         * 
    329377         * Opens in a modal by default. 
     
    330378         * 
    331379         * Requires an attachment model to be passed in the options hash under `model`. 
    332380         */ 
    333         media.view.Frame.EditAttachment = media.view.Frame.extend({ 
     381        media.view.Frame.EditAttachments = media.view.Frame.extend({ 
    334382 
    335383                className: 'edit-attachment-frame', 
    336384                template: media.template( 'edit-attachment-frame' ), 
     
    352400                                state: 'edit-attachment' 
    353401                        }); 
    354402 
     403                        this.library = this.options.library; 
     404                        if ( this.options.model ) { 
     405                                this.model = this.options.model; 
     406                        } else { 
     407                                this.model = this.library.at( 0 ); 
     408                        } 
     409 
    355410                        this.createStates(); 
    356411 
    357412                        this.on( 'content:render:edit-metadata', this.editMetadataContent, this ); 
     
    358413                        this.on( 'content:render:edit-image', this.editImageContentUgh, this ); 
    359414 
    360415                        // Only need a tab to Edit Image for images. 
    361                         if ( this.model.get( 'type' ) === 'image' ) { 
     416                        if ( 'undefined' !== typeof this.model && this.model.get( 'type' ) === 'image' ) { 
    362417                                this.on( 'router:create', this.createRouter, this ); 
    363418                                this.on( 'router:render', this.browseRouter, this ); 
    364419                        } 
     
    373428                                // Completely destroy the modal DOM element when closing it. 
    374429                                this.modal.close = function() { 
    375430                                        self.modal.remove(); 
     431                                        $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ 
     432                                        // Reset the browser URL 
     433                                        self.resetRoute(); 
    376434                                }; 
    377435 
    378436                                this.modal.content( this ); 
     
    412470                                model:      this.model 
    413471                        }); 
    414472                        this.content.set( view ); 
     473                        // Update browser url when navigating media details 
     474                        wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '?item=' + this.model.id ) ); 
    415475                }, 
    416476 
    417477                /** 
     
    464524                        }); 
    465525                }, 
    466526 
     527                getCurrentIndex: function() { 
     528                        return this.library.indexOf( this.model ); 
     529                }, 
     530 
     531                hasNext: function() { 
     532                        return ( this.getCurrentIndex() + 1 ) < this.library.length; 
     533                }, 
     534 
     535                hasPrevious: function() { 
     536                        return ( this.getCurrentIndex() - 1 ) > -1; 
     537                }, 
     538 
    467539                /** 
    468540                 * Click handler to switch to the previous media item. 
    469541                 */ 
    470542                previousMediaItem: function() { 
    471                         if ( ! this.options.hasPrevious ) 
     543                        if ( ! this.hasPrevious() ) { 
    472544                                return; 
    473                         this.modal.close(); 
    474                         this.options.gridController.trigger( 'edit:attachment:previous', this.model ); 
     545                        } 
     546                        this.model = this.library.at( this.getCurrentIndex() - 1 ); 
     547                        this.editMetadataContent(); 
    475548                }, 
    476549 
    477550                /** 
     
    478551                 * Click handler to switch to the next media item. 
    479552                 */ 
    480553                nextMediaItem: function() { 
    481                         if ( ! this.options.hasNext ) 
     554                        if ( ! this.hasNext() ) { 
    482555                                return; 
    483                         this.modal.close(); 
    484                         this.options.gridController.trigger( 'edit:attachment:next', this.model ); 
     556                        } 
     557                        this.model = this.library.at( this.getCurrentIndex() + 1 ); 
     558                        this.editMetadataContent(); 
     559                }, 
     560                /** 
     561                 * Respond to the keyboard events: right arrow, left arrow, escape. 
     562                 */ 
     563                keyEvent: function( event ) { 
     564                        var $target = $( event.target ); 
     565 
     566                        // Pressing the escape key routes back to main url 
     567                        if ( event.keyCode === 27 ) { 
     568                                this.resetRoute(); 
     569                                return event; 
     570                        } 
     571 
     572                        //Don't go left/right if we are in a textarea or input field 
     573                        if ( $target.is( 'input' ) || $target.is( 'textarea' ) ) { 
     574                                return event; 
     575                        } 
     576 
     577                        // The right arrow key 
     578                        if ( event.keyCode === 39 ) { 
     579                                if ( ! this.hasNext ) { return; } 
     580                                _.debounce( this.nextMediaItem(), 250 ); 
     581                        } 
     582 
     583                        // The left arrow key 
     584                        if ( event.keyCode === 37 ) { 
     585                                if ( ! this.hasPrevious ) { return; } 
     586                                _.debounce( this.previousMediaItem(), 250 ); 
     587                        } 
     588                }, 
     589 
     590                resetRoute: function() { 
     591                        wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '' ) ); 
     592                        return; 
    485593                } 
    486  
    487594        }); 
    488595 
    489596        media.view.GridFieldOptions = media.View.extend({