Ticket #50105: 50105.3.diff
File 50105.3.diff, 18.6 KB (added by , 4 years ago) |
---|
-
src/js/media/models/attachments.js
348 348 return this.mirroring ? this.mirroring.hasMore() : false; 349 349 }, 350 350 /** 351 * Holds the total number of attachments. 352 * 353 * @since 5.5.0 354 */ 355 totalAttachments: 0, 356 357 /** 358 * Gets the total number of attachments. 359 * 360 * @since 5.5.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 /** 351 369 * A custom Ajax-response parser. 352 370 * 353 371 * See trac ticket #24753 354 372 * 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. 356 382 * @param {Object} xhr 357 383 * @return {Array} The array of model attributes to be added to the collection 358 384 */ 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]; 362 388 } 363 389 364 return _.map( resp, function( attrs ) { 390 this.totalAttachments = parseInt( response.totalAttachments, 10 ); 391 392 return _.map( response.attachments, function( attrs ) { 365 393 var id, attachment, newAttributes; 366 394 367 395 if ( attrs instanceof Backbone.Model ) { -
src/js/media/models/query.js
112 112 options = options || {}; 113 113 options.remove = false; 114 114 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 ) { 117 120 query._hasMore = false; 118 121 } 119 122 }); -
src/js/media/views/attachments/browser.js
2 2 mediaTrash = wp.media.view.settings.mediaTrash, 3 3 l10n = wp.media.view.l10n, 4 4 $ = jQuery, 5 AttachmentsBrowser; 5 AttachmentsBrowser, 6 infiniteScrolling = wp.media.view.settings.infiniteScrolling, 7 __ = wp.i18n.__, 8 sprintf = wp.i18n.sprintf; 6 9 7 10 /** 8 11 * wp.media.view.AttachmentsBrowser … … 68 71 this.createUploader(); 69 72 } 70 73 71 72 74 // Add a heading before the attachments list. 73 75 this.createAttachmentsHeading(); 74 76 75 // Create the list of attachments.76 this.createAttachments ();77 // Create the attachments wrapper view. 78 this.createAttachmentsWrapperView(); 77 79 80 if ( ! infiniteScrolling ) { 81 this.$el.addClass( 'has-load-more' ); 82 this.createLoadMoreView(); 83 } 84 78 85 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. 79 86 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { 80 87 this.createSidebar(); … … 92 99 93 100 this.collection.on( 'add remove reset', this.updateContent, this ); 94 101 102 if ( ! infiniteScrolling ) { 103 this.collection.on( 'add remove reset', this.updateLoadMoreView, this ); 104 } 105 95 106 // The non-cached or cached attachments query has completed. 96 107 this.collection.on( 'attachments:received', this.announceSearchResults, this ); 97 108 }, … … 106 117 * @return {void} 107 118 */ 108 119 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.' ); 110 123 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 111 129 if ( this.collection.mirroring.args.s ) { 112 130 count = this.collection.length; 113 131 … … 117 135 } 118 136 119 137 if ( this.collection.hasMore() ) { 120 wp.a11y.speak( l10n.mediaFoundHasMoreResults.replace( '%d', count ) );138 wp.a11y.speak( mediaFoundHasMoreResultsMessage.replace( '%d', count ) ); 121 139 return; 122 140 } 123 141 … … 392 410 noItemsView; 393 411 394 412 if ( this.controller.isModeActive( 'grid' ) ) { 413 // Usually the media library. 395 414 noItemsView = view.attachmentsNoResults; 396 415 } else { 416 // Usually the media modal. 397 417 noItemsView = view.uploader; 398 418 } 399 419 … … 433 453 } 434 454 }, 435 455 456 /** 457 * Creates the Attachments wrapper view. 458 * 459 * @since 5.5.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 436 473 createAttachments: function() { 437 474 this.attachments = new wp.media.view.Attachments({ 438 475 controller: this.controller, … … 451 488 this.controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) ); 452 489 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) ); 453 490 454 this.views.add( this.attachments );491 this.views.add( '.attachments-wrapper', this.attachments ); 455 492 456 457 493 if ( this.controller.isModeActive( 'grid' ) ) { 458 494 this.attachmentsNoResults = new View({ 459 495 controller: this.controller, … … 467 503 } 468 504 }, 469 505 506 /** 507 * Creates the load more button and attachments counter view. 508 * 509 * @since 5.5.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: 'hero', 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 visually-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.5.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( 'visually-hidden' ); 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 on focus. 599 if ( this.firstAddedMediaItem.length ) { 600 this.loadMoreJumpToFirst.$el.removeClass( 'visually-hidden' ); 601 } 602 }, 10 ), 603 604 /** 605 * Loads more attachments. 606 * 607 * @since 5.5.0 608 * 609 * @return {void} 610 */ 611 loadMoreAttachments: function() { 612 var view = this; 613 614 if ( ! this.collection.hasMore() ) { 615 return; 616 } 617 618 /* 619 * The collection index is zero-based while the length counts the actual 620 * amount of items. Thus the length is equivaent to the position of the 621 * first added item. 622 */ 623 this.firstAddedMediaItemIndex = this.collection.length; 624 625 view.loadMoreSpinner.show(); 626 627 this.collection.more().done( function() { 628 // Within done(), `this` is the returned collection. 629 view.loadMoreSpinner.hide(); 630 } ); 631 }, 632 633 /** 634 * Moves focus to the first new added item. . 635 * 636 * @since 5.5.0 637 * 638 * @return {void} 639 */ 640 jumpToFirstAddedItem: function() { 641 // Set focus on first added item. 642 this.firstAddedMediaItem.focus(); 643 }, 644 470 645 createAttachmentsHeading: function() { 471 646 this.attachmentsHeading = new wp.media.view.Heading( { 472 647 text: l10n.attachmentsList, -
src/js/media/views/attachments.js
1 1 var View = wp.media.View, 2 2 $ = jQuery, 3 Attachments; 3 Attachments, 4 infiniteScrolling = wp.media.view.settings.infiniteScrolling; 4 5 5 6 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 6 7 tagName: 'ul', … … 35 36 this.el.id = _.uniqueId('__attachments-view-'); 36 37 37 38 /** 39 * @param infiniteScrolling Whether to enable infinite scrolling or use 40 * the default "load more" button. 38 41 * @param refreshSensitivity The time in milliseconds to throttle the scroll 39 42 * handler. 40 43 * @param refreshThreshold The amount of pixels that should be scrolled before … … 49 52 * calculating the total number of columns. 50 53 */ 51 54 _.defaults( this.options, { 55 infiniteScrolling: infiniteScrolling || false, 52 56 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 53 57 refreshThreshold: 3, 54 58 AttachmentView: wp.media.view.Attachment, … … 84 88 85 89 this.controller.on( 'library:selection:add', this.attachmentFocus, this ); 86 90 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(); 89 94 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 } 92 98 93 99 this.initSortable(); 94 100 … … 387 393 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 388 394 } else { 389 395 this.views.unset(); 390 this.collection.more().done( this.scroll ); 396 if ( this.options.infiniteScrolling ) { 397 this.collection.more().done( this.scroll ); 398 } 391 399 } 392 400 }, 393 401 … … 400 408 * @return {void} 401 409 */ 402 410 ready: function() { 403 this.scroll(); 411 if ( this.options.infiniteScrolling ) { 412 this.scroll(); 413 } 404 414 }, 405 415 406 416 /** -
src/wp-admin/css/media.css
420 420 421 421 .media-frame.mode-grid, 422 422 .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, 424 425 .media-frame.mode-grid .uploader-inline-content { 425 426 position: static; 426 427 } … … 498 499 border: 4px dashed #b4b9be; 499 500 } 500 501 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 { 502 504 position: relative; 503 505 top: 94px; /* prevent jumping up when the toolbar becomes fixed */ 504 506 padding-bottom: 94px; /* offset for above so the bottom doesn't get cut off */ -
src/wp-admin/includes/ajax-actions.php
2994 2994 $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts ); 2995 2995 $posts = array_filter( $posts ); 2996 2996 2997 wp_send_json_success( $posts ); 2997 $result = array( 2998 'attachments' => $posts, 2999 'totalAttachments' => $query->found_posts, 3000 ); 3001 3002 wp_send_json_success( $result ); 2998 3003 } 2999 3004 3000 3005 /** -
src/wp-includes/css/media-views.css
1192 1192 padding: 2px 8px 8px; 1193 1193 } 1194 1194 1195 .attachments-browser .attachments, 1195 .attachments-browser:not(.has-load-more) .attachments, 1196 .attachments-browser.has-load-more .attachments-wrapper, 1196 1197 .attachments-browser .uploader-inline { 1197 1198 position: absolute; 1198 1199 top: 72px; … … 1271 1272 padding: 2em 0 0 2em; 1272 1273 } 1273 1274 1275 .load-more-wrapper { 1276 clear: both; 1277 display: flex; 1278 flex-wrap: wrap; 1279 align-items: center; 1280 justify-content: center; 1281 padding: 1em 0; 1282 } 1283 1284 .load-more-wrapper .load-more-count { 1285 min-width: 100%; 1286 margin: 0 0 1em; 1287 text-align: center; 1288 } 1289 1290 .load-more-wrapper .load-more { 1291 margin: 0; 1292 } 1293 1294 /* Needs high specificity. */ 1295 .media-frame .load-more-wrapper .load-more + .spinner { 1296 float: none; 1297 margin: 0 -30px 0 10px; 1298 } 1299 1300 /* Reset spinner margin when the button is hidden to avoid horizontal scrollbar. */ 1301 .media-frame .load-more-wrapper .load-more.hidden + .spinner { 1302 margin: 0; 1303 } 1304 1305 /* Force a new row within the flex container. */ 1306 .load-more-wrapper::after { 1307 content: ""; 1308 min-width: 100%; 1309 order: 1; 1310 } 1311 1312 .load-more-wrapper .load-more-jump { 1313 /* Force a new row within the flex container. */ 1314 order: 2; 1315 /* Place the button out of screen. */ 1316 position: relative; 1317 left: -1000em; 1318 margin: 1em 0 0; 1319 } 1320 1321 .load-more-wrapper .load-more-jump.visually-hidden { 1322 visibility: hidden; 1323 } 1324 1325 .load-more-wrapper .load-more-jump:focus { 1326 /* Reveal the button on focus. */ 1327 left: auto; 1328 } 1329 1274 1330 /** 1275 1331 * Progress Bar 1276 1332 */ -
src/wp-includes/media.php
4276 4276 ); 4277 4277 } 4278 4278 4279 /** 4280 * Filters whether the Media Library grid has infinite scrolling. Default `false`. 4281 * 4282 * @since 5.5.0 4283 * 4284 * @param bool $value The filtered value, defaults to `false`. 4285 */ 4286 $infinite_scrolling = apply_filters( 'media_library_infinite_scrolling', false ); 4287 4279 4288 $settings = array( 4280 'tabs' => $tabs,4281 'tabUrl' => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ),4282 'mimeTypes' => wp_list_pluck( get_post_mime_types(), 0 ),4289 'tabs' => $tabs, 4290 'tabUrl' => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ), 4291 'mimeTypes' => wp_list_pluck( get_post_mime_types(), 0 ), 4283 4292 /** This filter is documented in wp-admin/includes/media.php */ 4284 'captions' => ! apply_filters( 'disable_captions', '' ),4285 'nonce' => array(4293 'captions' => ! apply_filters( 'disable_captions', '' ), 4294 'nonce' => array( 4286 4295 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), 4287 4296 ), 4288 'post' => array(4297 'post' => array( 4289 4298 'id' => 0, 4290 4299 ), 4291 'defaultProps' => $props,4292 'attachmentCounts' => array(4300 'defaultProps' => $props, 4301 'attachmentCounts' => array( 4293 4302 'audio' => ( $show_audio_playlist ) ? 1 : 0, 4294 4303 'video' => ( $show_video_playlist ) ? 1 : 0, 4295 4304 ), 4296 'oEmbedProxyUrl' => rest_url( 'oembed/1.0/proxy' ), 4297 'embedExts' => $exts, 4298 'embedMimes' => $ext_mimes, 4299 'contentWidth' => $content_width, 4300 'months' => $months, 4301 'mediaTrash' => MEDIA_TRASH ? 1 : 0, 4305 'oEmbedProxyUrl' => rest_url( 'oembed/1.0/proxy' ), 4306 'embedExts' => $exts, 4307 'embedMimes' => $ext_mimes, 4308 'contentWidth' => $content_width, 4309 'months' => $months, 4310 'mediaTrash' => MEDIA_TRASH ? 1 : 0, 4311 'infiniteScrolling' => ( $infinite_scrolling ) ? 1 : 0, 4302 4312 ); 4303 4313 4304 4314 $post = null; … … 4382 4392 'searchLabel' => __( 'Search' ), 4383 4393 'searchMediaLabel' => __( 'Search media' ), // Backward compatibility pre-5.3. 4384 4394 'searchMediaPlaceholder' => __( 'Search media items...' ), // Placeholder (no ellipsis), backward compatibility pre-5.3. 4395 /* translators: %d: Number of attachments found in a search. */ 4385 4396 'mediaFound' => __( 'Number of media items found: %d' ), 4386 'mediaFoundHasMoreResults' => __( 'Number of media items displayed: %d. Scroll the page for more results.' ),4387 4397 'noMedia' => __( 'No media items found.' ), 4388 4398 'noMediaTryNewSearch' => __( 'No media items found. Try a different search.' ), 4389 4399