Skip to content
Draft
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
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
38 changes: 31 additions & 7 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.2.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();
}
},

/**
* 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
49 changes: 47 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,39 @@ 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.2.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.attachments.setInfiniteScrolling( enabled );
this.$el.toggleClass( 'has-load-more', ! enabled );

if ( enabled ) {
this.collection.off( 'add remove reset', this.updateLoadMoreView, this );
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 +162,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 +430,14 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
model: this.collection.props,
priority: 60
}).render() );

// Pointless when a `media_library_infinite_scrolling` filter callback overrides the user preference.
if ( librarySettings && ! librarySettings.isFiltered ) {
this.toolbar.set( 'librarySettings', new wp.media.view.LibrarySettings({
controller: this.controller,
priority: 70
}).render() );
}
}

if ( this.options.dragInfo ) {
Expand Down
172 changes: 172 additions & 0 deletions src/js/media/views/library-settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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. Each toggle is saved over Ajax, so there is no submit button.
*
* @since 7.2.0
*
* @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'
},

/**
* Identifies the most recent request, so a slow response cannot overwrite the
* outcome of a later toggle.
*
* @type {number}
*/
requestId: 0,

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

prepare: function() {
return {
infiniteScrolling: !! settings.librarySettings.infiniteScrolling,
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 next to the toggle.
*
* Done on the first open, when the toggle is known to be in the document. A
* closed dialog is `display: none`, so it does not take part in the layout.
*
* @return {void}
*/
createDialog: function() {
var $dialog = $( wp.template( 'media-library-settings-dialog' )( this.prepare() ) );

this.$el.after( $dialog );

this.dialog = $dialog[0];
this.status = $dialog.find( '.media-library-settings__status' )[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.
*
* `showModal()` moves it to the top layer, out of the toolbar's clipping, and
* brings the focus trap, Escape handling and focus restore a modal needs.
*
* @return {void}
*/
open: function() {
if ( ! this.dialog ) {
this.createDialog();
}

this.setStatus( '' );
this.dialog.showModal();
},

/**
* Saves the "Infinite scrolling" personal option for the current user.
*
* @param {Event} event The change event of the checkbox.
* @return {void}
*/
updateInfiniteScrolling: function( event ) {
var view = this,
checkbox = event.target,
enabled = checkbox.checked,
requestId = ++this.requestId;

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

wp.ajax.post( 'set-media-library-settings', {
_ajax_nonce: settings.librarySettings.nonce,
infinite_scrolling: enabled ? 'true' : 'false'
} ).done( function() {
if ( requestId !== view.requestId ) {
return;
}

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

// Applied to the browser this toggle belongs to, without a reload.
view.controller.trigger( 'library:infinite-scrolling', enabled );

view.setStatus( enabled ?
__( 'Infinite scrolling is on.' ) :
__( 'Infinite scrolling is off.' )
);
} ).fail( function( response ) {
if ( requestId !== view.requestId ) {
return;
}

// Put the checkbox back in sync with the stored value.
checkbox.checked = ! enabled;

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

/**
* Updates the message below the controls.
*
* It sits in a `role="status"` region, so it is shown and announced at once.
*
* @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
4 changes: 3 additions & 1 deletion src/wp-admin/css/media.css
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,9 @@ border color while dragging a file over the uploader drop area */
min-height: 80px;
}

.media-frame.mode-grid .media-toolbar label:not(.media-search-input-label) {
/* Hides the filter labels. Child selectors only, so labels inside toolbar views keep their styling. */
.media-frame.mode-grid .media-toolbar-primary > label:not(.media-search-input-label),
.media-frame.mode-grid .media-toolbar-secondary > label:not(.media-search-input-label) {
border: 0;
clip-path: inset(50%);
height: 1px;
Expand Down
Loading
Loading