WordPress.org

Make WordPress Core

Ticket #50105: 50105.6.diff

File 50105.6.diff, 21.7 KB (added by joedolson, 11 months ago)

Updated patch. Adds focus handling for last collection, design update.

  • package-lock.json

     
    57385738                },
    57395739                "browserify-aes": {
    57405740                        "version": "1.2.0",
    5741                         "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
     5741                        "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
    57425742                        "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
    57435743                        "dev": true,
    57445744                        "requires": {
  • src/js/media/models/attachments.js

     
    348348                return this.mirroring ? this.mirroring.hasMore() : false;
    349349        },
    350350        /**
     351         * Holds the total number of attachments.
     352         *
     353         * @since 5.7.0
     354         */
     355        totalAttachments: 0,
     356
     357        /**
     358         * Gets the total number of attachments.
     359         *
     360         * @since 5.7.0
     361         *
     362         * @return {number} The total number of attachments.
     363         */
     364        getTotalAttachments: function() {
     365                return this.mirroring ? this.mirroring.totalAttachments : 0;
     366        },
     367
     368        /**
    351369         * A custom Ajax-response parser.
    352370         *
    353371         * See trac ticket #24753
    354372         *
    355          * @param {Object|Array} resp The raw response Object/Array.
     373         * Called automatically by Backbone whenever a collection's models are returned
     374         * by the server, in fetch. The default implementation is a no-op, simply
     375         * passing through the JSON response. We override this to add attributes to
     376         * the collection items.
     377         *
     378         * Since WordPress 5.5, the response returns the attachments under `response.attachments`
     379         * and `response.totalAttachments` holds the total number of attachments found.
     380         *
     381         * @param {Object|Array} response The raw response Object/Array.
    356382         * @param {Object} xhr
    357383         * @return {Array} The array of model attributes to be added to the collection
    358384         */
    359         parse: function( resp, xhr ) {
    360                 if ( ! _.isArray( resp ) ) {
    361                         resp = [resp];
     385        parse: function( response, xhr ) {
     386                if ( ! _.isArray( response.attachments ) ) {
     387                        response = [response.attachments];
    362388                }
    363389
    364                 return _.map( resp, function( attrs ) {
     390                this.totalAttachments = parseInt( response.totalAttachments, 10 );
     391
     392                return _.map( response.attachments, function( attrs ) {
    365393                        var id, attachment, newAttributes;
    366394
    367395                        if ( attrs instanceof Backbone.Model ) {
  • src/js/media/models/query.js

     
    112112                options = options || {};
    113113                options.remove = false;
    114114
    115                 return this._more = this.fetch( options ).done( function( resp ) {
    116                         if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
     115                return this._more = this.fetch( options ).done( function( response ) {
     116                        // Since WordPress 5.5, the response returns the attachments under `response.attachments`.
     117                        var attachments = response.attachments;
     118
     119                        if ( _.isEmpty( attachments ) || -1 === this.args.posts_per_page || attachments.length < this.args.posts_per_page ) {
    117120                                query._hasMore = false;
    118121                        }
    119122                });
  • src/js/media/views/attachments/browser.js

     
    22        mediaTrash = wp.media.view.settings.mediaTrash,
    33        l10n = wp.media.view.l10n,
    44        $ = jQuery,
    5         AttachmentsBrowser;
     5        AttachmentsBrowser,
     6        infiniteScrolling = wp.media.view.settings.infiniteScrolling,
     7        __ = wp.i18n.__,
     8        sprintf = wp.i18n.sprintf;
    69
    710/**
    811 * wp.media.view.AttachmentsBrowser
     
    6871                        this.createUploader();
    6972                }
    7073
    71 
    7274                // Add a heading before the attachments list.
    7375                this.createAttachmentsHeading();
    7476
    75                 // Create the list of attachments.
    76                 this.createAttachments();
     77                // Create the attachments wrapper view.
     78                this.createAttachmentsWrapperView();
    7779
     80                if ( ! infiniteScrolling ) {
     81                        this.$el.addClass( 'has-load-more' );
     82                        this.createLoadMoreView();
     83                }
     84
    7885                // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
    7986                if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
    8087                        this.createSidebar();
     
    9299
    93100                this.collection.on( 'add remove reset', this.updateContent, this );
    94101
     102                if ( ! infiniteScrolling ) {
     103                        this.collection.on( 'add remove reset', this.updateLoadMoreView, this );
     104                }
     105
    95106                // The non-cached or cached attachments query has completed.
    96107                this.collection.on( 'attachments:received', this.announceSearchResults, this );
    97108        },
     
    106117         * @return {void}
    107118         */
    108119        announceSearchResults: _.debounce( function() {
    109                 var count;
     120                var count,
     121                        /* translators: Accessibility text. %d: Number of attachments found in a search. */
     122                        mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Click load more for more results.' );
    110123
     124                if ( infiniteScrolling ) {
     125                        /* translators: Accessibility text. %d: Number of attachments found in a search. */
     126                        mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Scroll the page for more results.' );
     127                }
     128
    111129                if ( this.collection.mirroring.args.s ) {
    112130                        count = this.collection.length;
    113131
     
    117135                        }
    118136
    119137                        if ( this.collection.hasMore() ) {
    120                                 wp.a11y.speak( l10n.mediaFoundHasMoreResults.replace( '%d', count ) );
     138                                wp.a11y.speak( mediaFoundHasMoreResultsMessage.replace( '%d', count ) );
    121139                                return;
    122140                        }
    123141
     
    392410                        noItemsView;
    393411
    394412                if ( this.controller.isModeActive( 'grid' ) ) {
     413                        // Usually the media library.
    395414                        noItemsView = view.attachmentsNoResults;
    396415                } else {
     416                        // Usually the media modal.
    397417                        noItemsView = view.uploader;
    398418                }
    399419
     
    433453                }
    434454        },
    435455
     456        /**
     457         * Creates the Attachments wrapper view.
     458         *
     459         * @since 5.7.0
     460         *
     461         * @return {void}
     462         */
     463        createAttachmentsWrapperView: function() {
     464                this.attachmentsWrapper = new wp.media.View( {
     465                        className: 'attachments-wrapper'
     466                } );
     467
     468                // Create the list of attachments.
     469                this.views.add( this.attachmentsWrapper );
     470                this.createAttachments();
     471        },
     472
    436473        createAttachments: function() {
    437474                this.attachments = new wp.media.view.Attachments({
    438475                        controller:           this.controller,
     
    451488                this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
    452489                this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
    453490
    454                 this.views.add( this.attachments );
     491                this.views.add( '.attachments-wrapper', this.attachments );
    455492
    456 
    457493                if ( this.controller.isModeActive( 'grid' ) ) {
    458494                        this.attachmentsNoResults = new View({
    459495                                controller: this.controller,
     
    467503                }
    468504        },
    469505
     506        /**
     507         * Creates the load more button and attachments counter view.
     508         *
     509         * @since 5.7.0
     510         *
     511         * @return {void}
     512         */
     513        createLoadMoreView: function() {
     514                var view = this;
     515
     516                this.loadMoreWrapper = new View( {
     517                        controller: this.controller,
     518                        className: 'load-more-wrapper'
     519                } );
     520
     521                this.loadMoreCount = new View( {
     522                        controller: this.controller,
     523                        tagName: 'p',
     524                        className: 'load-more-count hidden'
     525                } );
     526
     527                this.loadMoreButton = new wp.media.view.Button( {
     528                        text: __( 'Load more' ),
     529                        className: 'load-more hidden',
     530                        style: 'primary',
     531                        size: '',
     532                        click: function() {
     533                                view.loadMoreAttachments();
     534                        }
     535                } );
     536
     537                this.loadMoreSpinner = new wp.media.view.Spinner();
     538
     539                this.loadMoreJumpToFirst = new wp.media.view.Button( {
     540                        text: __( 'Jump to first new item' ),
     541                        className: 'load-more-jump hidden',
     542                        size: '',
     543                        click: function() {
     544                                view.jumpToFirstAddedItem();
     545                        }
     546                } );
     547
     548                this.views.add( '.attachments-wrapper', this.loadMoreWrapper );
     549                this.views.add( '.load-more-wrapper', this.loadMoreCount );
     550                this.views.add( '.load-more-wrapper', this.loadMoreButton );
     551                this.views.add( '.load-more-wrapper', this.loadMoreSpinner );
     552                this.views.add( '.load-more-wrapper', this.loadMoreJumpToFirst );
     553        },
     554
     555        /**
     556         * Updates the Load More view. This function is debounced because the
     557         * collection updates multiple times at the add, remove, and reset events.
     558         * We need it to run only once, after all attachments are added or removed.
     559         *
     560         * @since 5.7.0
     561         *
     562         * @return {void}
     563         */
     564        updateLoadMoreView: _.debounce( function() {
     565                // Ensure the load more view elements are initially hidden at each update.
     566                this.loadMoreButton.$el.addClass( 'hidden' );
     567                this.loadMoreCount.$el.addClass( 'hidden' );
     568                this.loadMoreJumpToFirst.$el.addClass( 'hidden' ).prop( 'disabled', true );
     569
     570                if ( ! this.collection.getTotalAttachments() ) {
     571                        return;
     572                }
     573
     574                if ( this.collection.length ) {
     575                        this.loadMoreCount.$el.text(
     576                                /* translators: 1: Number of displayed attachments, 2: Number of total attachments. */
     577                                sprintf(
     578                                        __( 'Media items %1$s of %2$s' ),
     579                                        this.collection.length,
     580                                        this.collection.getTotalAttachments()
     581                                )
     582                        );
     583
     584                        this.loadMoreCount.$el.removeClass( 'hidden' );
     585                }
     586
     587                /*
     588                 * Notice that while the collection updates multiple times hasMore() may
     589                 * return true when it's actually not true.
     590                 */
     591                if ( this.collection.hasMore() ) {
     592                        this.loadMoreButton.$el.removeClass( 'hidden' );
     593                }
     594
     595                // Find the media item to move focus to. The jQuery `eq()` index is zero-based.
     596                this.firstAddedMediaItem = this.$el.find( '.attachment' ).eq( this.firstAddedMediaItemIndex );
     597
     598                // If there's a media item to move focus to, make the "Jump to" button available.
     599                if ( this.firstAddedMediaItem.length ) {
     600                        this.firstAddedMediaItem.addClass( 'new-media' );
     601                        this.loadMoreJumpToFirst.$el.removeClass( 'hidden' ).prop( 'disabled', false );
     602                }
     603
     604                // If there are new items added, but no more to be added, move focus to Jump button.
     605                if ( this.firstAddedMediaItem.length && ! this.collection.hasMore() ) {
     606                        this.loadMoreJumpToFirst.$el.trigger( 'focus' );
     607                }
     608        }, 10 ),
     609
     610        /**
     611         * Loads more attachments.
     612         *
     613         * @since 5.7.0
     614         *
     615         * @return {void}
     616         */
     617        loadMoreAttachments: function() {
     618                var view = this;
     619
     620                if ( ! this.collection.hasMore() ) {
     621                        return;
     622                }
     623
     624                /*
     625                 * The collection index is zero-based while the length counts the actual
     626                 * amount of items. Thus the length is equivalent to the position of the
     627                 * first added item.
     628                 */
     629                this.firstAddedMediaItemIndex = this.collection.length;
     630
     631                view.loadMoreSpinner.show();
     632
     633                this.collection.more().done( function() {
     634                        // Within done(), `this` is the returned collection.
     635                        view.loadMoreSpinner.hide();
     636                } );
     637        },
     638
     639        /**
     640         * Moves focus to the first new added item.     .
     641         *
     642         * @since 5.7.0
     643         *
     644         * @return {void}
     645         */
     646        jumpToFirstAddedItem: function() {
     647                // Set focus on first added item.
     648                this.firstAddedMediaItem.focus();
     649        },
     650
    470651        createAttachmentsHeading: function() {
    471652                this.attachmentsHeading = new wp.media.view.Heading( {
    472653                        text: l10n.attachmentsList,
  • src/js/media/views/attachments.js

     
    11var View = wp.media.View,
    22        $ = jQuery,
    3         Attachments;
     3        Attachments,
     4        infiniteScrolling = wp.media.view.settings.infiniteScrolling;
    45
    56Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
    67        tagName:   'ul',
     
    3536                this.el.id = _.uniqueId('__attachments-view-');
    3637
    3738                /**
     39                 * @param infiniteScrolling  Whether to enable infinite scrolling or use
     40                 *                           the default "load more" button.
    3841                 * @param refreshSensitivity The time in milliseconds to throttle the scroll
    3942                 *                           handler.
    4043                 * @param refreshThreshold   The amount of pixels that should be scrolled before
     
    4952                 *                           calculating the total number of columns.
    5053                 */
    5154                _.defaults( this.options, {
     55                        infiniteScrolling:  infiniteScrolling || false,
    5256                        refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
    5357                        refreshThreshold:   3,
    5458                        AttachmentView:     wp.media.view.Attachment,
     
    8488
    8589                this.controller.on( 'library:selection:add', this.attachmentFocus, this );
    8690
    87                 // Throttle the scroll handler and bind this.
    88                 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
     91                if ( this.options.infiniteScrolling ) {
     92                        // Throttle the scroll handler and bind this.
     93                        this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
    8994
    90                 this.options.scrollElement = this.options.scrollElement || this.el;
    91                 $( this.options.scrollElement ).on( 'scroll', this.scroll );
     95                        this.options.scrollElement = this.options.scrollElement || this.el;
     96                        $( this.options.scrollElement ).on( 'scroll', this.scroll );
     97                }
    9298
    9399                this.initSortable();
    94100
     
    387393                        this.views.set( this.collection.map( this.createAttachmentView, this ) );
    388394                } else {
    389395                        this.views.unset();
    390                         this.collection.more().done( this.scroll );
     396                        if ( this.options.infiniteScrolling ) {
     397                                this.collection.more().done( this.scroll );
     398                        }
    391399                }
    392400        },
    393401
     
    400408         * @return {void}
    401409         */
    402410        ready: function() {
    403                 this.scroll();
     411                if ( this.options.infiniteScrolling ) {
     412                        this.scroll();
     413                }
    404414        },
    405415
    406416        /**
  • src/js/media/views/uploader/inline.js

     
    115115
    116116                        $browser.detach().text( $placeholder.text() );
    117117                        $browser[0].className = $placeholder[0].className;
     118                        $browser[0].setAttribute( 'aria-labelledby', $browser[0].id + ' ' + $placeholder[0].getAttribute('aria-labelledby') );
    118119                        $placeholder.replaceWith( $browser.show() );
    119120                }
    120121
  • src/js/media/views/uploader/status.js

     
    116116                        message:  error.get( 'message' )
    117117                } );
    118118
     119                var buttonClose = this.$el.find( 'button' );
     120
    119121                // Can show additional info here while retrying to create image sub-sizes.
    120122                this.views.add( '.upload-errors', statusError, { at: 0 } );
     123                _.defer( function() {
     124                        buttonClose.trigger( 'focus' );
     125                        wp.a11y.speak( error.get( 'message' ), 'assertive' );
     126                });
    121127        },
    122128
    123129        dismiss: function() {
  • src/wp-admin/css/media.css

     
    420420
    421421.media-frame.mode-grid,
    422422.media-frame.mode-grid .media-frame-content,
    423 .media-frame.mode-grid .attachments-browser .attachments,
     423.media-frame.mode-grid .attachments-browser:not(.has-load-more) .attachments,
     424.media-frame.mode-grid .attachments-browser.has-load-more .attachments-wrapper,
    424425.media-frame.mode-grid .uploader-inline-content {
    425426        position: static;
    426427}
     
    498499        border: 4px dashed #c3c4c7;
    499500}
    500501
    501 .media-frame.mode-select .attachments-browser.fixed .attachments {
     502.media-frame.mode-select .attachments-browser.fixed:not(.has-load-more) .attachments,
     503.media-frame.mode-select .attachments-browser.has-load-more.fixed .attachments-wrapper {
    502504        position: relative;
    503505        top: 94px; /* prevent jumping up when the toolbar becomes fixed */
    504506        padding-bottom: 94px; /* offset for above so the bottom doesn't get cut off */
  • src/wp-admin/includes/ajax-actions.php

     
    29932993        $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
    29942994        $posts = array_filter( $posts );
    29952995
    2996         wp_send_json_success( $posts );
     2996        $result = array(
     2997                'attachments'      => $posts,
     2998                'totalAttachments' => $query->found_posts,
     2999        );
     3000
     3001        wp_send_json_success( $result );
    29973002}
    29983003
    29993004/**
  • src/wp-includes/css/media-views.css

     
    11901190        padding: 2px 8px 8px;
    11911191}
    11921192
    1193 .attachments-browser .attachments,
     1193.attachments-browser:not(.has-load-more) .attachments,
     1194.attachments-browser.has-load-more .attachments-wrapper,
    11941195.attachments-browser .uploader-inline {
    11951196        position: absolute;
    11961197        top: 72px;
     
    12691270        padding: 2em 0 0 2em;
    12701271}
    12711272
     1273.attachment.new-media {
     1274        outline: 2px dotted;
     1275        background: #fff;
     1276}
     1277
     1278.load-more-wrapper {
     1279        clear: both;
     1280        display: flex;
     1281        flex-wrap: wrap;
     1282        align-items: center;
     1283        justify-content: center;
     1284        padding: 1em 0;
     1285}
     1286
     1287.load-more-wrapper .load-more-count {
     1288        min-width: 100%;
     1289        margin: 0 0 1em;
     1290        text-align: center;
     1291}
     1292
     1293.load-more-wrapper .load-more {
     1294        margin: 0;
     1295}
     1296
     1297/* Needs high specificity. */
     1298.media-frame .load-more-wrapper .load-more + .spinner {
     1299        float: none;
     1300        margin: 0 -30px 0 10px;
     1301}
     1302
     1303/* Reset spinner margin when the button is hidden to avoid horizontal scrollbar. */
     1304.media-frame .load-more-wrapper .load-more.hidden + .spinner {
     1305        margin: 0;
     1306}
     1307
     1308/* Force a new row within the flex container. */
     1309.load-more-wrapper::after {
     1310        content: "";
     1311        min-width: 100%;
     1312        order: 1;
     1313}
     1314
     1315.load-more-wrapper .load-more-jump {
     1316        margin: 0 0 0 12px;
     1317}
     1318
    12721319/**
    12731320 * Progress Bar
    12741321 */
     
    28202867        .media-frame-content .media-toolbar .instructions {
    28212868                display: none;
    28222869        }
     2870
     2871        /* Change margin direction on load more button in responsive views. */
     2872        .load-more-wrapper .load-more-jump {
     2873                margin: 12px 0 0 0;
     2874        }
     2875
    28232876}
    28242877
    28252878@media only screen and (min-width: 901px) and (max-height: 400px) {
  • src/wp-includes/media-template.php

     
    252252                        <div class="upload-ui">
    253253                                <h2 class="upload-instructions drop-instructions"><?php _e( 'Drop files to upload' ); ?></h2>
    254254                                <p class="upload-instructions drop-instructions"><?php _ex( 'or', 'Uploader: Drop files here - or - Select Files' ); ?></p>
    255                                 <button type="button" class="browser button button-hero"><?php _e( 'Select Files' ); ?></button>
     255                                <button type="button" class="browser button button-hero" aria-labelledby="post-upload-info"><?php _e( 'Select Files' ); ?></button>
    256256                        </div>
    257257
    258258                        <div class="upload-inline-status"></div>
    259259
    260                         <div class="post-upload-ui">
     260                        <div class="post-upload-ui" id="post-upload-info">
    261261                                <?php
    262262                                /** This action is documented in wp-admin/includes/media.php */
    263263                                do_action( 'pre-upload-ui' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
  • src/wp-includes/media.php

     
    42934293                );
    42944294        }
    42954295
     4296        /**
     4297         * Filters whether the Media Library grid has infinite scrolling. Default `false`.
     4298         *
     4299         * @since 5.7.0
     4300         *
     4301         * @param bool $value The filtered value, defaults to `false`.
     4302         */
     4303        $infinite_scrolling = apply_filters( 'media_library_infinite_scrolling', false );
     4304
    42964305        $settings = array(
    4297                 'tabs'             => $tabs,
    4298                 'tabUrl'           => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ),
    4299                 'mimeTypes'        => wp_list_pluck( get_post_mime_types(), 0 ),
     4306                'tabs'              => $tabs,
     4307                'tabUrl'            => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ),
     4308                'mimeTypes'         => wp_list_pluck( get_post_mime_types(), 0 ),
    43004309                /** This filter is documented in wp-admin/includes/media.php */
    4301                 'captions'         => ! apply_filters( 'disable_captions', '' ),
    4302                 'nonce'            => array(
     4310                'captions'          => ! apply_filters( 'disable_captions', '' ),
     4311                'nonce'             => array(
    43034312                        'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ),
    43044313                ),
    4305                 'post'             => array(
     4314                'post'              => array(
    43064315                        'id' => 0,
    43074316                ),
    4308                 'defaultProps'     => $props,
    4309                 'attachmentCounts' => array(
     4317                'defaultProps'      => $props,
     4318                'attachmentCounts'  => array(
    43104319                        'audio' => ( $show_audio_playlist ) ? 1 : 0,
    43114320                        'video' => ( $show_video_playlist ) ? 1 : 0,
    43124321                ),
    4313                 'oEmbedProxyUrl'   => rest_url( 'oembed/1.0/proxy' ),
    4314                 'embedExts'        => $exts,
    4315                 'embedMimes'       => $ext_mimes,
    4316                 'contentWidth'     => $content_width,
    4317                 'months'           => $months,
    4318                 'mediaTrash'       => MEDIA_TRASH ? 1 : 0,
     4322                'oEmbedProxyUrl'    => rest_url( 'oembed/1.0/proxy' ),
     4323                'embedExts'         => $exts,
     4324                'embedMimes'        => $ext_mimes,
     4325                'contentWidth'      => $content_width,
     4326                'months'            => $months,
     4327                'mediaTrash'        => MEDIA_TRASH ? 1 : 0,
     4328                'infiniteScrolling' => ( $infinite_scrolling ) ? 1 : 0,
    43194329        );
    43204330
    43214331        $post = null;
     
    43994409                'searchLabel'                 => __( 'Search' ),
    44004410                'searchMediaLabel'            => __( 'Search media' ),          // Backward compatibility pre-5.3.
    44014411                'searchMediaPlaceholder'      => __( 'Search media items...' ), // Placeholder (no ellipsis), backward compatibility pre-5.3.
     4412                /* translators: %d: Number of attachments found in a search. */
    44024413                'mediaFound'                  => __( 'Number of media items found: %d' ),
    4403                 'mediaFoundHasMoreResults'    => __( 'Number of media items displayed: %d. Scroll the page for more results.' ),
    44044414                'noMedia'                     => __( 'No media items found.' ),
    44054415                'noMediaTryNewSearch'         => __( 'No media items found. Try a different search.' ),
    44064416