Ticket #50105: 50105.diff
File 50105.diff, 16.6 KB (added by , 5 years ago) |
---|
-
src/js/media/models/attachments.js
355 355 hasMore: function() { 356 356 return this.mirroring ? this.mirroring.hasMore() : false; 357 357 }, 358 358 359 /** 360 * Holds the total number of attachments. 361 * 362 * @since 5.5.0 363 */ 364 totalAttachments: 0, 365 366 /** 367 * Gets the total number of attachments. 368 * 369 * @since 5.5.0 370 * 371 * @return {number} The total number of attachments. 372 */ 373 getTotalAttachments: function() { 374 return this.mirroring ? this.mirroring.totalAttachments : 0; 375 }, 376 377 /** 359 378 * A custom AJAX-response parser. 360 379 * 361 380 * See trac ticket #24753 362 381 * 363 * @param {Object|Array} resp The raw response Object/Array. 382 * Called automatically by Backbone whenever a collection's models are returned 383 * by the server, in fetch. The default implementation is a no-op, simply 384 * passing through the JSON response. We override this to add attributes to 385 * the collection items. 386 * 387 * Since WordPress 5.5, the response returns the attachments under `response.attachments` 388 * and `response.totalAttachments` holds the total number of attachments found. 389 * 390 * @param {Object|Array} response The raw response Object/Array. 364 391 * @param {Object} xhr 365 392 * @return {Array} The array of model attributes to be added to the collection 366 393 */ 367 parse: function( resp , xhr ) {368 if ( ! _.isArray( resp ) ) {369 resp = [resp];394 parse: function( response, xhr ) { 395 if ( ! _.isArray( response.attachments ) ) { 396 response = [response.attachments]; 370 397 } 371 398 372 return _.map( resp, function( attrs ) { 399 this.totalAttachments = parseInt( response.totalAttachments, 10 ); 400 401 return _.map( response.attachments, function( attrs ) { 373 402 var id, attachment, newAttributes; 374 403 375 404 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 }, … … 392 403 noItemsView; 393 404 394 405 if ( this.controller.isModeActive( 'grid' ) ) { 406 // Usually the media library. 395 407 noItemsView = view.attachmentsNoResults; 396 408 } else { 409 // Usually the media modal. 397 410 noItemsView = view.uploader; 398 411 } 399 412 … … 433 446 } 434 447 }, 435 448 449 /** 450 * Creates the Attachments wrapper view. 451 * 452 * @since 5.5.0 453 * 454 * @return {void} 455 */ 456 createAttachmentsWrapperView: function() { 457 this.attachmentsWrapper = new wp.media.View( { 458 className: 'attachments-wrapper' 459 } ); 460 461 // Create the list of attachments. 462 this.views.add( this.attachmentsWrapper ); 463 this.createAttachments(); 464 }, 465 436 466 createAttachments: function() { 437 467 this.attachments = new wp.media.view.Attachments({ 438 468 controller: this.controller, … … 451 481 this.controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) ); 452 482 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) ); 453 483 454 this.views.add( this.attachments );484 this.views.add( '.attachments-wrapper', this.attachments ); 455 485 456 457 486 if ( this.controller.isModeActive( 'grid' ) ) { 458 487 this.attachmentsNoResults = new View({ 459 488 controller: this.controller, … … 467 496 } 468 497 }, 469 498 499 /** 500 * Creates the load more button and attachments counter view. 501 * 502 * @since 5.5.0 503 * 504 * @return {void} 505 */ 506 createLoadMoreView: function() { 507 var view = this; 508 509 this.loadMoreButton = new wp.media.view.Button( { 510 text: l10n.loadMore, 511 className: 'load-more hidden', 512 style: 'primary', 513 size: 'hero', 514 click: function() { 515 view.loadMoreAttachments(); 516 } 517 } ); 518 519 this.loadMoreCount = new View( { 520 controller: this.controller, 521 tagName: 'p', 522 className: 'load-more-count hidden' 523 } ); 524 525 this.loadMoreSpinner = new wp.media.view.Spinner(); 526 527 this.views.add( '.attachments-wrapper', this.loadMoreCount ); 528 this.views.add( '.attachments-wrapper', this.loadMoreButton ); 529 this.views.add( '.attachments-wrapper', this.loadMoreSpinner ); 530 }, 531 532 /** 533 * Updates the Load More view. This function is debounced because the 534 * collection updates multiple times at the add, remove, and reset events. 535 * We need it to run only once, after all attachments are added or removed. 536 * 537 * @since 5.5.0 538 * 539 * @return {void} 540 */ 541 updateLoadMoreView: _.debounce( function() { 542 // Ensure the load more view elements are initially hidden at each update. 543 this.loadMoreButton.$el.addClass( 'hidden' ); 544 this.loadMoreCount.$el.addClass( 'hidden' ); 545 546 if ( ! this.collection.getTotalAttachments() ) { 547 return; 548 } 549 550 if ( this.collection.length ) { 551 this.loadMoreCount.$el.text( 552 /* translators: 1: Number of displayed attachments, 2: Number of total attachments. */ 553 sprintf( 554 __( 'Media items %1$s of %2$s' ), 555 this.collection.length, 556 this.collection.getTotalAttachments() 557 ) 558 ); 559 560 this.loadMoreCount.$el.removeClass( 'hidden' ); 561 } 562 563 /* 564 * Notice that while the collection updates multiple times hasMore() may 565 * return true when it's actually not true. 566 */ 567 if ( this.collection.hasMore() ) { 568 this.loadMoreButton.$el.removeClass( 'hidden' ); 569 } 570 }, 10 ), 571 572 /** 573 * Loads more attachments. 574 * 575 * @since 5.5.0 576 * 577 * @return {void} 578 */ 579 loadMoreAttachments: function() { 580 var view = this, 581 loadMoreSpinner = this.loadMoreSpinner, 582 firstAddedMediaItemIndex; 583 584 if ( ! this.collection.hasMore() ) { 585 return; 586 } 587 588 // Collection index starts from 0. 589 firstAddedMediaItemIndex = this.collection.length; 590 591 loadMoreSpinner.show(); 592 593 this.collection.more().done( function() { 594 // Within done(), `this` is the returned collection. 595 loadMoreSpinner.hide(); 596 // Set focus on first added item. 597 view.$el.find( 'li' ).eq( firstAddedMediaItemIndex ).focus(); 598 } ); 599 }, 600 470 601 createAttachmentsHeading: function() { 471 602 this.attachmentsHeading = new wp.media.view.Heading( { 472 603 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
416 416 417 417 .media-frame.mode-grid, 418 418 .media-frame.mode-grid .media-frame-content, 419 .media-frame.mode-grid .attachments-browser .attachments, 419 .media-frame.mode-grid .attachments-browser:not(.has-load-more) .attachments, 420 .media-frame.mode-grid .attachments-browser.has-load-more .attachments-wrapper, 420 421 .media-frame.mode-grid .uploader-inline-content { 421 422 position: static; 422 423 } … … 494 495 border: 4px dashed #b4b9be; 495 496 } 496 497 497 .media-frame.mode-select .attachments-browser.fixed .attachments { 498 .media-frame.mode-select .attachments-browser.fixed:not(.has-load-more) .attachments, 499 .media-frame.mode-select .attachments-browser.has-load-more.fixed .attachments-wrapper { 498 500 position: relative; 499 501 top: 94px; /* prevent jumping up when the toolbar becomes fixed */ 500 502 padding-bottom: 94px; /* offset for above so the bottom doesn't get cut off */ -
src/wp-admin/includes/ajax-actions.php
2969 2969 $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts ); 2970 2970 $posts = array_filter( $posts ); 2971 2971 2972 wp_send_json_success( $posts ); 2972 $result = array( 2973 'attachments' => $posts, 2974 'totalAttachments' => $query->found_posts, 2975 ); 2976 2977 wp_send_json_success( $result ); 2973 2978 } 2974 2979 2975 2980 /** -
src/wp-includes/css/media-views.css
1170 1170 padding: 2px 8px 8px; 1171 1171 } 1172 1172 1173 .attachments-browser .attachments, 1173 .attachments-browser:not(.has-load-more) .attachments, 1174 .attachments-browser.has-load-more .attachments-wrapper, 1174 1175 .attachments-browser .uploader-inline { 1175 1176 position: absolute; 1176 1177 top: 72px; … … 1249 1250 padding: 2em 0 0 2em; 1250 1251 } 1251 1252 1253 .attachments-browser .load-more-count { 1254 clear: both; 1255 text-align: center; 1256 } 1257 1258 .attachments-browser .load-more { 1259 display: block; 1260 margin: 1em auto; 1261 } 1262 1263 .media-frame .attachments-browser .load-more + .spinner { 1264 display: block; 1265 float: none; 1266 margin: 8px auto 16px; 1267 } 1268 1252 1269 /** 1253 1270 * Progress Bar 1254 1271 */ -
src/wp-includes/media.php
3969 3969 ); 3970 3970 } 3971 3971 3972 /** 3973 * Filters whether the Media Library grid has infinite scrolling. Default `false`. 3974 * 3975 * @since 5.5.0 3976 * 3977 * @param bool $value The filtered value, defaults to `false`. 3978 */ 3979 $infinite_scrolling = apply_filters( 'media_library_infinite_scrolling', false ); 3980 3972 3981 $settings = array( 3973 'tabs' => $tabs,3974 'tabUrl' => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ),3975 'mimeTypes' => wp_list_pluck( get_post_mime_types(), 0 ),3982 'tabs' => $tabs, 3983 'tabUrl' => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ), 3984 'mimeTypes' => wp_list_pluck( get_post_mime_types(), 0 ), 3976 3985 /** This filter is documented in wp-admin/includes/media.php */ 3977 'captions' => ! apply_filters( 'disable_captions', '' ),3978 'nonce' => array(3986 'captions' => ! apply_filters( 'disable_captions', '' ), 3987 'nonce' => array( 3979 3988 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), 3980 3989 ), 3981 'post' => array(3990 'post' => array( 3982 3991 'id' => 0, 3983 3992 ), 3984 'defaultProps' => $props,3985 'attachmentCounts' => array(3993 'defaultProps' => $props, 3994 'attachmentCounts' => array( 3986 3995 'audio' => ( $show_audio_playlist ) ? 1 : 0, 3987 3996 'video' => ( $show_video_playlist ) ? 1 : 0, 3988 3997 ), 3989 'oEmbedProxyUrl' => rest_url( 'oembed/1.0/proxy' ), 3990 'embedExts' => $exts, 3991 'embedMimes' => $ext_mimes, 3992 'contentWidth' => $content_width, 3993 'months' => $months, 3994 'mediaTrash' => MEDIA_TRASH ? 1 : 0, 3998 'oEmbedProxyUrl' => rest_url( 'oembed/1.0/proxy' ), 3999 'embedExts' => $exts, 4000 'embedMimes' => $ext_mimes, 4001 'contentWidth' => $content_width, 4002 'months' => $months, 4003 'mediaTrash' => MEDIA_TRASH ? 1 : 0, 4004 'infiniteScrolling' => ( $infinite_scrolling ) ? 1 : 0, 3995 4005 ); 3996 4006 3997 4007 $post = null; … … 4023 4033 $post_type_object = get_post_type_object( 'post' ); 4024 4034 } 4025 4035 4036 /* translators: Accessibility text. %d: Number of attachments found in a search. */ 4037 $media_found_has_more_results = __( 'Number of media items displayed: %d. Click load more for more results.' ); 4038 if ( $infinite_scrolling ) { 4039 /* translators: Accessibility text. %d: Number of attachments found in a search. */ 4040 $media_found_has_more_results = __( 'Number of media items displayed: %d. Scroll the page for more results.' ); 4041 } 4042 4026 4043 $strings = array( 4027 4044 // Generic. 4028 4045 'mediaFrameDefaultTitle' => __( 'Media' ), … … 4075 4092 'searchLabel' => __( 'Search' ), 4076 4093 'searchMediaLabel' => __( 'Search Media' ), // Backward compatibility pre-5.3. 4077 4094 'searchMediaPlaceholder' => __( 'Search media items...' ), // Placeholder (no ellipsis), backward compatibility pre-5.3. 4095 /* translators: %d: Number of attachments found in a search. */ 4078 4096 'mediaFound' => __( 'Number of media items found: %d' ), 4079 'mediaFoundHasMoreResults' => __( 'Number of media items displayed: %d. Scroll the page for more results.' ),4097 'mediaFoundHasMoreResults' => $media_found_has_more_results, 4080 4098 'noMedia' => __( 'No media items found.' ), 4081 4099 'noMediaTryNewSearch' => __( 'No media items found. Try a different search.' ), 4100 'loadMore' => __( 'Load more' ), 4082 4101 4083 4102 // Library Details. 4084 4103 'attachmentDetails' => __( 'Attachment Details' ),