Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f1bc9ac
Media: Add a settings dialog to the Media Library toolbar.
t-hamano Aug 7, 2026
a98eea4
Adjust search input width
joedolson Aug 9, 2026
9251623
Switch label from implicit to explicit
joedolson Aug 9, 2026
b0bb18a
Adjust paragraph alignment & and cover narrow viewports
joedolson Aug 9, 2026
2b58600
Cast media_library_infinite_scrolling filter result to boolean
t-hamano Aug 10, 2026
a10841e
Tests: Add coverage for the Media Library settings Ajax handler
t-hamano Aug 10, 2026
3aac1c6
Tests: Follow the ajax test docblock conventions for the Media Librar…
t-hamano Aug 10, 2026
d280e56
Media: Make the infinite scrolling checkbox label state the resulting…
t-hamano Aug 10, 2026
837d3b4
Media: Scope the search field max-width to the grid mode toolbar
t-hamano Aug 10, 2026
ee61aef
Media: Drop the redundant comment on the library settings media data
t-hamano Aug 10, 2026
5421a07
Media: Send the library settings data only to users who can upload files
t-hamano Aug 10, 2026
d22db3c
Media: Let the library settings Ajax error messages reach the dialog
t-hamano Aug 10, 2026
382cdfe
Docs: Correct the Media Library settings since tags to 7.1.0
t-hamano Aug 10, 2026
e70b2c5
Media: Sync the library settings dialog with the stored value on each…
t-hamano Aug 10, 2026
f407c53
Media: Show the library settings toggle regardless of the infinite sc…
t-hamano Aug 10, 2026
49ba43d
Media: Move the library settings dialog out of the toolbar and into t…
t-hamano Aug 10, 2026
961d5b7
Docs: Trim the Media Library settings view docblocks to their summaries
t-hamano Aug 10, 2026
b67cddb
Media: Adjust the Media Library settings toggle and dialog styles
t-hamano Aug 10, 2026
dd4fe91
Media: Use Boolean() in the library settings filter check to satisfy …
t-hamano Aug 10, 2026
eb9a031
Media: Drop the redundant comment on the library settings user meta u…
t-hamano Aug 10, 2026
739c979
Media: Restore the library settings status min-height to keep the dia…
t-hamano Aug 10, 2026
2b23278
Improve button alignment on sub 782px viewport
joedolson Aug 10, 2026
c8617bc
Fix button sizing between 782 and 900 px
joedolson Aug 10, 2026
2ca898f
Media: Adjust search field and settings toggle placement in the media…
t-hamano Aug 11, 2026
431a1b9
Media: Bail out of the attachments scroll handler when infinite scrol…
t-hamano Aug 11, 2026
cf528d3
Media: Report the infinite scrolling filter explicitly so it always t…
t-hamano Aug 11, 2026
93997ba
Media: Serialize library settings saves so the stored value matches t…
t-hamano Aug 11, 2026
8562d2a
Media: Clear the Load more highlighting when switching to infinite sc…
t-hamano Aug 11, 2026
cd6074a
Media: Drop the settings toggle margins left over from before it was …
t-hamano Aug 11, 2026
155999b
Media: Swap to the scrolling layout before enabling infinite scrollin…
t-hamano Aug 11, 2026
fe09da6
Merge branch 'trunk' into add/media-library-settings-dialog
joedolson Aug 11, 2026
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
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,7 @@ module.exports = function(grunt) {
'src/wp-includes/js/media/views/iframe.js' : 'src/js/media/views/iframe.js',
'src/wp-includes/js/media/views/image-details.js' : 'src/js/media/views/image-details.js',
'src/wp-includes/js/media/views/label.js' : 'src/js/media/views/label.js',
'src/wp-includes/js/media/views/library-settings.js' : 'src/js/media/views/library-settings.js',
'src/wp-includes/js/media/views/media-details.js' : 'src/js/media/views/media-details.js',
'src/wp-includes/js/media/views/media-frame.js' : 'src/js/media/views/media-frame.js',
'src/wp-includes/js/media/views/menu-item.js' : 'src/js/media/views/menu-item.js',
Expand Down
1 change: 1 addition & 0 deletions src/js/_enqueues/wp/media/views.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ media.view.AttachmentFilters = require( '../../../media/views/attachment-filters
media.view.DateFilter = require( '../../../media/views/attachment-filters/date.js' );
media.view.AttachmentFilters.Uploaded = require( '../../../media/views/attachment-filters/uploaded.js' );
media.view.AttachmentFilters.All = require( '../../../media/views/attachment-filters/all.js' );
media.view.LibrarySettings = require( '../../../media/views/library-settings.js' );
media.view.AttachmentsBrowser = require( '../../../media/views/attachments/browser.js' );
media.view.Selection = require( '../../../media/views/selection.js' );
media.view.Attachment.Selection = require( '../../../media/views/attachment/selection.js' );
Expand Down
40 changes: 32 additions & 8 deletions src/js/media/views/attachments.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
var View = wp.media.View,
$ = jQuery,
Attachments,
infiniteScrolling = wp.media.view.settings.infiniteScrolling;
Attachments;

Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
tagName: 'ul',
Expand Down Expand Up @@ -54,7 +53,7 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
* calculating the total number of columns.
*/
_.defaults( this.options, {
infiniteScrolling: infiniteScrolling || false,
infiniteScrolling: !! wp.media.view.settings.infiniteScrolling,
refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
refreshThreshold: 3,
AttachmentView: wp.media.view.Attachment,
Expand Down Expand Up @@ -90,11 +89,12 @@ 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. Done even when infinite
// scrolling is off, since it can be turned on at any time.
this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
this.options.scrollElement = this.options.scrollElement || this.el;

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

Expand All @@ -114,6 +114,28 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
}
},

/**
* Turns infinite scrolling on or off after the view has been created.
*
* @since 7.1.0
*
* @param {boolean} enabled Whether to load more attachments on scroll.
*
* @return {void}
*/
setInfiniteScrolling: function( enabled ) {
this.options.infiniteScrolling = enabled;

$( this.options.scrollElement ).off( 'scroll', this.scroll );

if ( enabled ) {
$( this.options.scrollElement ).on( 'scroll', this.scroll );

// The list may already be scrolled past the point where more are loaded.
this.scroll();

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.

Noting that this.scroll() never checks this.options.infiniteScrolling so if there's already a queued throttled callback or pending collection.more(), they might call scroll() again, which will run unchecked. We might want to add a this.options.infiniteScrolling check to scroll() itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 431a1b9

}
},

/**
* Listens to the resizeEvent on the window.
*
Expand Down Expand Up @@ -231,6 +253,8 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
*/
dispose: function() {
this.collection.props.off( null, null, this );
$( this.options.scrollElement ).off( 'scroll', this.scroll );

if ( this.options.resize ) {
this.$window.off( this.resizeEvent );
}
Expand Down Expand Up @@ -440,7 +464,7 @@ Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
scrollTop = $(document).scrollTop();
}

if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
if ( ! this.options.infiniteScrolling || ! $(el).is(':visible') || ! this.collection.hasMore() ) {
return;
}

Expand Down
52 changes: 50 additions & 2 deletions src/js/media/views/attachments/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ var View = wp.media.View,
l10n = wp.media.view.l10n,
$ = jQuery,
AttachmentsBrowser,
infiniteScrolling = wp.media.view.settings.infiniteScrolling,
settings = wp.media.view.settings,
librarySettings = wp.media.view.settings.librarySettings,
__ = wp.i18n.__,
sprintf = wp.i18n.sprintf;

Expand Down Expand Up @@ -34,6 +35,8 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
className: 'attachments-browser',

initialize: function() {
var infiniteScrolling = !! settings.infiniteScrolling;

_.defaults( this.options, {
filters: false,
search: true,
Expand All @@ -45,6 +48,7 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro

this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
this.controller.on( 'edit:selection', this.editSelection );
this.controller.on( 'library:infinite-scrolling', this.setInfiniteScrolling, this );

// In the Media Library, the sidebar is used to display errors before the attachments grid.
if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
Expand Down Expand Up @@ -111,6 +115,42 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
this.collection.on( 'attachments:received', this.announceSearchResults, this );
},

/**
* Switches between infinite scrolling and the Load more button in place.
*
* The Load more view is created on demand and then kept, hidden by the
* `has-load-more` class, so that switching back and forth is cheap.
*
* @since 7.1.0
*
* @param {boolean} enabled Whether to load more attachments on scroll.
*
* @return {void}
*/
setInfiniteScrolling: function( enabled ) {
if ( enabled === Boolean( this.attachments.options.infiniteScrolling ) ) {
return;
}

this.$el.toggleClass( 'has-load-more', ! enabled );

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.

We might need to cover a few more items/classes based on this logic, like .more-loaded, .found-media, and .new-media for example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8562d2a

this.attachments.setInfiniteScrolling( enabled );

if ( enabled ) {
this.collection.off( 'add remove reset', this.updateLoadMoreView, this );
this.$el.removeClass( 'more-loaded' );
this.$el.find( '.found-media' ).removeClass( 'found-media' );
this.$el.find( '.new-media' ).removeClass( 'new-media' );
return;
}

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

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

/**
* Updates the `wp.a11y.speak()` ARIA live region with a message to communicate
* the number of search results to screen reader users. This function is
Expand All @@ -125,7 +165,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.attachments.options.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 @@ -393,6 +433,14 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
model: this.collection.props,
priority: 60
}).render() );

// Only for users allowed to save the preference, see `wp_enqueue_media()`.
if ( librarySettings ) {
this.toolbar.set( 'librarySettings', new wp.media.view.LibrarySettings({
controller: this.controller,
priority: 70
}).render() );
}
}

if ( this.options.dragInfo ) {
Expand Down
194 changes: 194 additions & 0 deletions src/js/media/views/library-settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
var View = wp.media.View,
settings = wp.media.view.settings,
$ = jQuery,
__ = wp.i18n.__,
LibrarySettings;

/**
* wp.media.view.LibrarySettings
*
* A toolbar control opening a modal dialog with the personal options for the
* Media Library.
*
* @since 7.1.0

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.

Considering the 7.1 branch is only for bug fixes at this point, I doubt if this will make it. Should we consider it for 7.2?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This will need to be agreed upon and decided among the release leads.

*
* @memberOf wp.media.view
*
* @class
* @augments wp.media.View
* @augments wp.Backbone.View
* @augments Backbone.View
*/
LibrarySettings = View.extend(/** @lends wp.media.view.LibrarySettings.prototype */{
tagName: 'button',
className: 'button button-compact media-library-settings__toggle',
template: wp.template( 'media-library-settings-toggle' ),

attributes: {
type: 'button',
'aria-haspopup': 'dialog'
},

events: {
'click': 'open'
},

/**
* Whether a request is in progress.
*
* @type {boolean}
*/
isSaving: false,

initialize: function() {
// Several media frames can be attached at once, so IDs are per instance.
this.uid = _.uniqueId( 'media-library-settings-' );
},

prepare: function() {
return {
titleId: this.uid + '-title',
infiniteScrollingId: this.uid + '-infinite-scrolling'
};
},

/**
* Removes the dialog along with the view.
*
* @return {wp.media.view.LibrarySettings} Returns itself to allow chaining.
*/
dispose: function() {
if ( this.dialog ) {
$( this.dialog ).remove();
}

return View.prototype.dispose.apply( this, arguments );
},

/**
* Inserts the dialog into the frame.
*
* @return {void}
*/
createDialog: function() {
var $dialog = $( wp.template( 'media-library-settings-dialog' )( this.prepare() ) );

this.controller.$el.append( $dialog );

this.dialog = $dialog[0];
this.status = $dialog.find( '.media-library-settings__status' )[0];
this.checkbox = $dialog.find( '.media-library-settings__checkbox' )[0];

$dialog.on( 'change', '.media-library-settings__checkbox', _.bind( this.updateInfiniteScrolling, this ) );

/*
* In the media modal, `wp.media.view.Modal` closes on Escape and
* `wp.media.view.FocusManager` constrains Tab. Neither must run while the
* dialog is open: it handles both itself, and the rest of the modal is inert.
*/
$dialog.on( 'keydown', function( event ) {
event.stopPropagation();
} );
},

/**
* Opens the dialog.
*
* @return {void}
*/
open: function() {
if ( ! this.dialog ) {
this.createDialog();
}

if ( ! this.isSaving ) {
this.checkbox.checked = !! settings.librarySettings.infiniteScrolling;
this.setStatus( '' );
}

this.dialog.showModal();
},

/**
* Whether a `media_library_infinite_scrolling` filter callback overrides the
* personal option.
*
* @type {boolean}
*/
isFiltered: !! settings.librarySettings && !! settings.librarySettings.isFiltered,

/**
* Saves the "Infinite scrolling" personal option for the current user.
*
* @return {void}
*/
updateInfiniteScrolling: function() {
if ( ! this.isSaving ) {
this.save();
}
},

/**
* Sends the state of the checkbox to the server, then whatever it was toggled
* to in the meantime.
*
* @return {void}
*/
save: function() {
var view = this,
enabled = this.checkbox.checked;

this.isSaving = true;

this.setStatus( __( 'Saving…' ) );

wp.ajax.post( 'set-media-library-settings', {

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.

Multiple concurrent saves can result in saving the wrong value. Might need some throttling or disabling if a request is in progress here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 93997ba

_ajax_nonce: settings.librarySettings.nonce,
infinite_scrolling: enabled ? 'true' : 'false'
} ).done( function() {
view.isSaving = false;

settings.librarySettings.infiniteScrolling = enabled ? 1 : 0;

if ( enabled !== view.checkbox.checked ) {
view.save();
return;
}

/*
* A filter callback takes precedence over the preference, so the Media
* Library keeps the filtered behavior. Otherwise the browser this toggle
* belongs to is updated without a reload.
*/
if ( ! view.isFiltered ) {
settings.infiniteScrolling = enabled ? 1 : 0;

view.controller.trigger( 'library:infinite-scrolling', enabled );
}

view.setStatus( enabled ?
__( 'Infinite scrolling is on.' ) :
__( 'Infinite scrolling is off.' )
);

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.

Is this yet another place that might be out of sync with isFiltered? Looks like we're not covering the case when a filter has forced that infinite scroll is "on".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am considering this, and I have two proposals.

  • Notify that the filter is enabled when settings are updated. For example, Setting saved. Infinite scrolling is currently controlled by a plugin or theme.
  • When filtered, the toggle button should not be displayed at all.

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.

To me, the first option is more clear.

} ).fail( function( response ) {
view.isSaving = false;

// Put the checkbox back in sync with the stored value.
view.checkbox.checked = !! settings.librarySettings.infiniteScrolling;

view.setStatus( ( response && response.message ) || __( 'The setting could not be saved.' ) );
} );
},

/**
* Updates the message below the controls.
*
* @param {string} message The message to display. An empty string clears it.
* @return {void}
*/
setStatus: function( message ) {
this.status.textContent = message;
}
});

module.exports = LibrarySettings;
1 change: 1 addition & 0 deletions src/wp-admin/admin-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
'closed-postboxes',
'hidden-columns',
'update-welcome-panel',
'set-media-library-settings',
'menu-get-metabox',
'wp-link-ajax',
'menu-locations-save',
Expand Down
Loading
Loading