WordPress.org

Make WordPress Core

Ticket #24716: 24716.29.diff

File 24716.29.diff, 8.9 KB (added by adamsilverstein, 4 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({