Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/js/media/views/attachments.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,11 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{

this.controller.on( 'library:selection:add', this.attachmentFocus, this );

if ( this.options.infiniteScrolling ) {
// Throttle the scroll handler and bind this.
this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
// Throttle the scroll handler and bind this.
this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();

this.options.scrollElement = this.options.scrollElement || this.el;
$( this.options.scrollElement ).on( 'scroll', this.scroll );
}
this.options.scrollElement = this.options.scrollElement || this.el;
$( this.options.scrollElement ).on( 'scroll', this.scroll );

this.initSortable();

Expand Down Expand Up @@ -422,6 +420,7 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
* server if we're {refreshThreshold} times away from the bottom.
*
* @since 3.5.0
* @since 7.1.0 Bails out when infinite scrolling is disabled.
*
* @return {void}
*/
Expand All @@ -431,6 +430,10 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
scrollTop = el.scrollTop,
toolbar;

if ( ! this.options.infiniteScrolling ) {
return;
}

/*
* The scroll event occurs on the document, but the element that should be
* checked is the document body.
Expand Down
146 changes: 137 additions & 9 deletions src/js/media/views/attachments/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var View = wp.media.View,
$ = jQuery,
AttachmentsBrowser,
infiniteScrolling = wp.media.view.settings.infiniteScrolling,
canToggleInfiniteScrolling = wp.media.view.settings.canToggleInfiniteScrolling,
__ = wp.i18n.__,
sprintf = wp.i18n.sprintf;

Expand Down Expand Up @@ -43,6 +44,8 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
AttachmentView: wp.media.view.Attachment.Library
});

this.infiniteScrolling = !! infiniteScrolling;

this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
this.controller.on( 'edit:selection', this.editSelection );

Expand Down Expand Up @@ -77,7 +80,7 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
// Create the attachments wrapper view.
this.createAttachmentsWrapperView();

if ( ! infiniteScrolling ) {
if ( ! this.infiniteScrolling ) {
this.$el.addClass( 'has-load-more' );
this.createLoadMoreView();
}
Expand All @@ -89,7 +92,7 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro

this.updateContent();

if ( ! infiniteScrolling ) {
if ( ! this.infiniteScrolling ) {
this.updateLoadMoreView();
}

Expand All @@ -102,10 +105,7 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
}

this.collection.on( 'add remove reset', this.updateContent, this );

if ( ! infiniteScrolling ) {
this.collection.on( 'add remove reset', this.updateLoadMoreView, this );
}
this.collection.on( 'add remove reset', this.updateLoadMoreView, this );

// The non-cached or cached attachments query has completed.
this.collection.on( 'attachments:received', this.announceSearchResults, this );
Expand All @@ -125,7 +125,7 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
/* translators: Accessibility text. %d: Number of attachments found in a search. */
mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Click load more for more results.' );

if ( infiniteScrolling ) {
if ( this.infiniteScrolling ) {
/* translators: Accessibility text. %d: Number of attachments found in a search. */
mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Scroll the page for more results.' );
}
Expand Down Expand Up @@ -472,19 +472,142 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
className: 'attachments-wrapper'
} );

// Create the list of attachments.
this.views.add( this.attachmentsWrapper );

/*
* Place the infinite scrolling toggle before the list of attachments. It
* saves the personal option, so it is only offered when wp_enqueue_media()
* reports that saving that option takes effect.
*/
if ( canToggleInfiniteScrolling ) {
this.createInfiniteScrollingToggle();
}

// Create the list of attachments.
this.createAttachments();
},

/**
* Creates the checkbox that turns infinite scrolling on and off.
*
* @since 7.1.0
*
* @return {void}
*/
createInfiniteScrollingToggle: function() {
var view = this,
id = _.uniqueId( 'media-infinite-scrolling-' ),
checkbox = $( '<input />', {
type: 'checkbox',
id: id,
checked: this.infiniteScrolling

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to feedback that has been given previously, we should make sure to consider newly rendered media browsers, since they seem to discard the saved choice if it differs from the one during page-load.

To test this:

  • Open Featured Image > Media Library with scrolling disabled.
  • Enable it and wait for "Preference saved".
  • Switch to Upload Files, then back to Media Library.
  • The recreated checkbox is disabled again, while the live-region message still says it was enabled and saved.

} ),
label = $( '<label />', {
'for': id,
text: __( 'Infinite scrolling' )
} );

// Not a live region: the same message is sent to `speak()` when it changes.
this.infiniteScrollingStatus = $( '<span />', {
'class': 'media-infinite-scrolling-status'
} );

checkbox.on( 'change', function() {
view.toggleInfiniteScrolling( this.checked );
view.saveInfiniteScrolling( this.checked );
} );
Comment on lines +515 to +518

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed that quick changes can persist the opposite of the final checkbox state. Every change immediately launches an independent AJAX request. If an enable request and a subsequent disable request execute out of order, the old value can become the final value.


this.infiniteScrollingToggle = new View( {
controller: this.controller,
className: 'media-infinite-scrolling'
} );

this.infiniteScrollingToggle.$el.append( checkbox, label, this.infiniteScrollingStatus );

this.views.add( '.attachments-wrapper', this.infiniteScrollingToggle );
},

/**
* Saves the infinite scrolling preference for the current user.
*
* @since 7.1.0
*
* @param {boolean} infiniteScrolling Whether the attachments list has infinite scrolling.
*
* @return {void}
*/
saveInfiniteScrolling: function( infiniteScrolling ) {
var view = this;

wp.ajax.post( 'save-media-infinite-scrolling', {
nonce: wp.media.view.settings.nonce.saveInfiniteScrolling,
infiniteScrolling: infiniteScrolling
} ).done( function() {
view.updateInfiniteScrollingStatus(
infiniteScrolling ?
__( 'Infinite scrolling enabled. Preference saved.' ) :
__( 'Infinite scrolling disabled. Load more button displayed. Preference saved.' )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The success text always announces "Load more button displayed", but the button is kept hidden when the library is empty or hasMore() is false. Can be reproduced with an empty library: no button appeared, but both the visible status and wp.a11y.speak() reported that it did. We may want to use a generic disabled message or condition it on the actual button state.

);
} ).fail( function() {
view.updateInfiniteScrollingStatus( __( 'The infinite scrolling preference could not be saved.' ) );
} );
},

/**
* Displays and announces the result of changing the infinite scrolling preference.
*
* The controls it affects are at the end of the list of attachments, so the
* result is usually out of view when the checkbox changes.
*
* @since 7.1.0
*
* @param {string} message The message to display and announce.
*
* @return {void}
*/
updateInfiniteScrollingStatus: function( message ) {
this.infiniteScrollingStatus.text( message );
wp.a11y.speak( message );
},

/**
* Turns infinite scrolling of the attachments list on and off.
*
* When infinite scrolling is off, the "Load more" button is used instead.
*
* @since 7.1.0
*
* @param {boolean} infiniteScrolling Whether the attachments list has infinite scrolling.
*
* @return {void}
*/
toggleInfiniteScrolling: function( infiniteScrolling ) {
this.infiniteScrolling = infiniteScrolling;
this.attachments.options.infiniteScrolling = infiniteScrolling;
this.$el.toggleClass( 'has-load-more', ! infiniteScrolling );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be helpful to include some kind of visual affordance that confirms that something has happened, since all the control changes happen out of the viewport.

I also think that the control needs to save this preference for the user, so one possibility would be a notice that appears/is spoken saying "Infinite scrolling preference saved"

if ( ! this.loadMoreWrapper ) {
this.createLoadMoreView();
}

this.loadMoreWrapper.$el.toggleClass( 'hidden', infiniteScrolling );

if ( infiniteScrolling ) {
this.attachments.scroll();
} else {
this.updateLoadMoreView();
}
},

createAttachments: function() {
this.attachments = new wp.media.view.Attachments({
controller: this.controller,
collection: this.collection,
selection: this.options.selection,
model: this.model,
sortable: this.options.sortable,
scrollElement: this.options.scrollElement,
infiniteScrolling: this.infiniteScrolling,
scrollElement: this.options.scrollElement || this.attachmentsWrapper.el,
idealColumnWidth: this.options.idealColumnWidth,

// The single `Attachment` view to be used in the `Attachments` view.
Expand Down Expand Up @@ -565,10 +688,15 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
* We need it to run only once, after all attachments are added or removed.
*
* @since 5.8.0
* @since 7.1.0 Bails out when infinite scrolling is enabled.
*
* @return {void}
*/
updateLoadMoreView: _.debounce( function() {
if ( this.infiniteScrolling ) {
return;
}

// Ensure the load more view elements are initially hidden at each update.
this.loadMoreButton.$el.addClass( 'hidden' );
this.loadMoreCount.$el.addClass( 'hidden' );
Expand Down
1 change: 1 addition & 0 deletions src/wp-admin/admin-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
'send-link-to-editor',
'send-attachment-to-editor',
'save-attachment-order',
'save-media-infinite-scrolling',
'media-create-image-subsizes',
'heartbeat',
'get-revision-diffs',
Expand Down
6 changes: 2 additions & 4 deletions src/wp-admin/css/media.css
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,7 @@ border color while dragging a file over the uploader drop area */

.media-frame.mode-grid,
.media-frame.mode-grid .media-frame-content,
.media-frame.mode-grid .attachments-browser:not(.has-load-more) .attachments,
.media-frame.mode-grid .attachments-browser.has-load-more .attachments-wrapper,
.media-frame.mode-grid .attachments-browser .attachments-wrapper,
.media-frame.mode-grid .uploader-inline-content {
position: static;
}
Expand Down Expand Up @@ -518,8 +517,7 @@ border color while dragging a file over the uploader drop area */
border: 4px dashed #c3c4c7;
}

.media-frame.mode-select .attachments-browser.fixed:not(.has-load-more) .attachments,
.media-frame.mode-select .attachments-browser.has-load-more.fixed .attachments-wrapper {
.media-frame.mode-select .attachments-browser.fixed .attachments-wrapper {
position: relative;
top: 94px; /* prevent jumping up when the toolbar becomes fixed */
padding-bottom: 94px; /* offset for above so the bottom doesn't get cut off */
Expand Down
23 changes: 23 additions & 0 deletions src/wp-admin/includes/ajax-actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3317,6 +3317,29 @@ function wp_ajax_save_attachment_order() {
wp_send_json_success();
}

/**
* Handles saving the current user's Media Library infinite scrolling preference via AJAX.
*
* Writes the same personal option as the "Infinite Scrolling" checkbox on the
* profile screen, so that the preference set from the attachments browser
* persists beyond the current view.
*
* @since 7.1.0
*/
function wp_ajax_save_media_infinite_scrolling() {
check_ajax_referer( 'save-media-infinite-scrolling', 'nonce' );

if ( ! isset( $_POST['infiniteScrolling'] ) ) {
wp_send_json_error();
}

$infinite_scrolling = wp_validate_boolean( wp_unslash( $_POST['infiniteScrolling'] ) );

update_user_meta( get_current_user_id(), 'infinite_scrolling', $infinite_scrolling ? 'true' : 'false' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs multisite coverage. get_user_option() prefers a blog-prefixed option before the global user meta, but the AJAX handler updates only global infinite_scrolling meta. If a prefixed value exists, which is particularly plausible on multisite, the endpoint reports success while reload continues using the unchanged prefixed value. I believe we should either use global meta consistently, matching the profile screen, or explicitly handle the higher-priority site option.


wp_send_json_success();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears like the return value of update_user_meta() is ignored and wp_send_json_success() runs unconditionally. A failed write therefore produces "Preference saved".

}

/**
* Handles sending an attachment to the editor via AJAX.
*
Expand Down
31 changes: 28 additions & 3 deletions src/wp-includes/css/media-views.css
Original file line number Diff line number Diff line change
Expand Up @@ -1276,8 +1276,7 @@ select#media-attachment-filters ~ select#media-attachment-date-filters {
padding: 2px 8px 8px;
}

.attachments-browser:not(.has-load-more) .attachments,
.attachments-browser.has-load-more .attachments-wrapper,
.attachments-browser .attachments-wrapper,
.attachments-browser .uploader-inline {
position: absolute;
top: 72px;
Expand All @@ -1288,6 +1287,27 @@ select#media-attachment-filters ~ select#media-attachment-date-filters {
outline: none;
}

.attachments-browser .media-infinite-scrolling {
padding: 8px 16px 12px;
}

.attachments-browser .media-infinite-scrolling-status {
margin-left: 12px;
color: #50575e;
}

/* In the modal, the media items list scrolls: keep the toggle in view. */
.media-modal .attachments-browser .media-infinite-scrolling {
position: sticky;
top: 0;
z-index: 1;
background: #fff;
}

.attachments-browser .load-more-wrapper.hidden {
display: none;
}

.attachments-browser .uploader-inline.hidden {
display: none;
}
Expand Down Expand Up @@ -1341,7 +1361,7 @@ select#media-attachment-filters ~ select#media-attachment-date-filters {
box-shadow: 0 0 3px rgba(var(--wp-admin-theme-color--rgb, 56, 88, 233), 0.8);
}

.attachments-browser.hide-sidebar .attachments,
.attachments-browser.hide-sidebar .attachments-wrapper,
.attachments-browser.hide-sidebar .uploader-inline {
right: 0;
margin-right: 0;
Expand Down Expand Up @@ -2654,6 +2674,10 @@ select#media-attachment-filters ~ select#media-attachment-date-filters {
top: 131px;
}

.attachments-browser .media-infinite-scrolling {
padding: 14px 16px 0px;
}

.media-sidebar .setting,
.attachment-details .setting {
margin: 6px 0;
Expand Down Expand Up @@ -2758,6 +2782,7 @@ select#media-attachment-filters ~ select#media-attachment-date-filters {
font-size: 16px;
line-height: 1.625;
padding: 5px 24px 5px 8px;
margin: 0;
}

.image-details .column-image {
Expand Down
Loading
Loading