summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYury German <blueknight@gentoo.org>2022-01-23 18:37:36 -0500
committerYury German <blueknight@gentoo.org>2022-01-23 18:37:36 -0500
commitf18b23a3a9378fb0a98856d436aa9ebf94e47429 (patch)
treee418433e22854ebd2d77eaa869d5d0470a973317 /plugins/jetpack/modules/infinite-scroll
parentAdd classic-editor 1.5 (diff)
downloadblogs-gentoo-f18b23a3a9378fb0a98856d436aa9ebf94e47429.tar.gz
blogs-gentoo-f18b23a3a9378fb0a98856d436aa9ebf94e47429.tar.bz2
blogs-gentoo-f18b23a3a9378fb0a98856d436aa9ebf94e47429.zip
Updating Classic Editor, Google Authenticatior, Jetpack, Public Post Preview, Table of Contents, Wordpress Importer
Signed-off-by: Yury German <blueknight@gentoo.org>
Diffstat (limited to 'plugins/jetpack/modules/infinite-scroll')
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity-customizer.js54
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.css126
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.js967
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.php524
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css4
-rw-r--r--plugins/jetpack/modules/infinite-scroll/themes/twentyfourteen.php4
6 files changed, 1180 insertions, 499 deletions
diff --git a/plugins/jetpack/modules/infinite-scroll/infinity-customizer.js b/plugins/jetpack/modules/infinite-scroll/infinity-customizer.js
new file mode 100644
index 00000000..9d5937b1
--- /dev/null
+++ b/plugins/jetpack/modules/infinite-scroll/infinity-customizer.js
@@ -0,0 +1,54 @@
+/* globals wp */
+( function ( $ ) {
+ /**
+ * Ready, set, go!
+ */
+ $( document ).ready( function () {
+ // Integrate with Selective Refresh in the Customizer.
+ if ( 'undefined' !== typeof wp && wp.customize && wp.customize.selectiveRefresh ) {
+ /**
+ * Handle rendering of selective refresh partials.
+ *
+ * Make sure that when a partial is rendered, the Jetpack post-load event
+ * will be triggered so that any dynamic elements will be re-constructed,
+ * such as ME.js elements, Photon replacements, social sharing, and more.
+ * Note that this is applying here not strictly to posts being loaded.
+ * If a widget contains a ME.js element and it is previewed via selective
+ * refresh, the post-load would get triggered allowing any dynamic elements
+ * therein to also be re-constructed.
+ *
+ * @param {wp.customize.selectiveRefresh.Placement} placement
+ */
+ wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
+ var content;
+ if ( 'string' === typeof placement.addedContent ) {
+ content = placement.addedContent;
+ } else if ( placement.container ) {
+ content = $( placement.container ).html();
+ }
+
+ if ( content ) {
+ $( document.body ).trigger( 'post-load', { html: content } );
+ }
+ } );
+
+ /*
+ * Add partials for posts added via infinite scroll.
+ *
+ * This is unnecessary when MutationObserver is supported by the browser
+ * since then this will be handled by Selective Refresh in core.
+ */
+ if ( 'undefined' === typeof MutationObserver ) {
+ $( document.body ).on( 'post-load', function ( e, response ) {
+ var rootElement = null;
+ if ( response.html && -1 !== response.html.indexOf( 'data-customize-partial' ) ) {
+ if ( window.infiniteScroll.settings.id ) {
+ rootElement = $( '#' + window.infiniteScroll.settings.id );
+ }
+ wp.customize.selectiveRefresh.addPartials( rootElement );
+ }
+ } );
+ }
+ }
+ } );
+} )( jQuery ); // Close closure
diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.css b/plugins/jetpack/modules/infinite-scroll/infinity.css
index 4c84e294..8b4dec2e 100644
--- a/plugins/jetpack/modules/infinite-scroll/infinity.css
+++ b/plugins/jetpack/modules/infinite-scroll/infinity.css
@@ -1,24 +1,120 @@
/* =Infinity Styles
-------------------------------------------------------------- */
-.infinite-wrap {
-}
.infinite-loader {
color: #000;
display: block;
height: 28px;
- text-indent: -9999px;
+ text-align: center;
}
#infinite-handle span {
background: #333;
border-radius: 1px;
- color: #eee;
+ color: #f0f0f1;
cursor: pointer;
font-size: 13px;
padding: 6px 16px;
}
/**
+ * CSS Spinner Styles
+ */
+@keyframes spinner-inner {
+ 0% { opacity: 1 }
+ 100% { opacity: 0 }
+}
+.infinite-loader .spinner-inner div {
+ left: 47px;
+ top: 24px;
+ position: absolute;
+ animation: spinner-inner linear 1s infinite;
+ background: #000000;
+ outline: 1px solid white;
+ width: 6px;
+ height: 12px;
+ border-radius: 3px / 6px;
+ transform-origin: 3px 26px;
+}
+.infinite-loader .spinner-inner div:nth-child(1) {
+ transform: rotate(0deg);
+ animation-delay: -0.9166666666666666s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(2) {
+ transform: rotate(30deg);
+ animation-delay: -0.8333333333333334s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(3) {
+ transform: rotate(60deg);
+ animation-delay: -0.75s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(4) {
+ transform: rotate(90deg);
+ animation-delay: -0.6666666666666666s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(5) {
+ transform: rotate(120deg);
+ animation-delay: -0.5833333333333334s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(6) {
+ transform: rotate(150deg);
+ animation-delay: -0.5s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(7) {
+ transform: rotate(180deg);
+ animation-delay: -0.4166666666666667s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(8) {
+ transform: rotate(210deg);
+ animation-delay: -0.3333333333333333s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(9) {
+ transform: rotate(240deg);
+ animation-delay: -0.25s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(10) {
+ transform: rotate(270deg);
+ animation-delay: -0.16666666666666666s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(11) {
+ transform: rotate(300deg);
+ animation-delay: -0.08333333333333333s;
+ background: #000000;
+}
+.infinite-loader .spinner-inner div:nth-child(12) {
+ transform: rotate(330deg);
+ animation-delay: 0s;
+ background: #000000;
+}
+.infinite-loader .spinner {
+ width: 28px;
+ height: 28px;
+ display: inline-block;
+ overflow: hidden;
+ background: none;
+}
+.infinite-loader .spinner-inner {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ transform: translateZ(0) scale(0.28);
+ backface-visibility: hidden;
+ transform-origin: 0 0; /* see note above */
+}
+.infinite-loader .spinner-inner div {
+ box-sizing: content-box;
+}
+
+/**
* Using a highly-specific rule to make sure that all button styles
* will be reset
*/
@@ -122,7 +218,7 @@
text-align: right;
}
#infinite-footer .blog-credits a {
- color: #666;
+ color: #646970;
}
/**
@@ -161,4 +257,22 @@
#infinite-footer {
position: static;
}
-} \ No newline at end of file
+}
+
+/**
+ * Hide infinite aria feedback visually
+ */
+#infinite-aria {
+ position: absolute;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ height: 1px; width: 1px;
+ margin: -1px; padding: 0; border: 0;
+}
+
+/**
+ * Hide focus on infinite wrappers
+ */
+.infinite-wrap:focus {
+ outline: 0 !important;
+}
diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.js b/plugins/jetpack/modules/infinite-scroll/infinity.js
index 24dd413e..01d83d07 100644
--- a/plugins/jetpack/modules/infinite-scroll/infinity.js
+++ b/plugins/jetpack/modules/infinite-scroll/infinity.js
@@ -1,8 +1,8 @@
-/* globals infiniteScroll, _wpmejsSettings, ga, _gaq, WPCOM_sharing_counts */
-( function( $ ) {
- // Open closure
- // Local vars
- var Scroller, ajaxurl, stats, type, text, totop;
+/* globals infiniteScroll, _wpmejsSettings, ga, _gaq, WPCOM_sharing_counts, MediaElementPlayer */
+( function () {
+ // Open closure.
+ // Local vars.
+ var Scroller, ajaxurl, stats, type, text, totop, loading_text;
// IE requires special handling
var isIE = -1 != navigator.userAgent.search( 'MSIE' );
@@ -22,14 +22,14 @@
/**
* Loads new posts when users scroll near the bottom of the page.
*/
- Scroller = function( settings ) {
+ Scroller = function ( settings ) {
var self = this;
// Initialize our variables
this.id = settings.id;
- this.body = $( document.body );
- this.window = $( window );
- this.element = $( '#' + settings.id );
+ this.body = document.body;
+ this.window = window;
+ this.element = document.getElementById( settings.id );
this.wrapperClass = settings.wrapper_class;
this.ready = true;
this.disabled = false;
@@ -38,19 +38,24 @@
this.currentday = settings.currentday;
this.order = settings.order;
this.throttle = false;
- this.handle =
- '<div id="infinite-handle"><span><button>' +
- text.replace( '\\', '' ) +
- '</button></span></div>';
this.click_handle = settings.click_handle;
this.google_analytics = settings.google_analytics;
this.history = settings.history;
this.origURL = window.location.href;
- this.pageCache = {};
+
+ // Handle element
+ this.handle = document.createElement( 'div' );
+ this.handle.setAttribute( 'id', 'infinite-handle' );
+ this.handle.innerHTML = '<span><button>' + text.replace( '\\', '' ) + '</button></span>';
// Footer settings
- this.footer = $( '#infinite-footer' );
- this.footer.wrap = settings.footer;
+ this.footer = {
+ el: document.getElementById( 'infinite-footer' ),
+ wrap: settings.footer,
+ };
+
+ // Bind methods used as callbacks
+ this.checkViewportOnLoadBound = self.checkViewportOnLoad.bind( this );
// Core's native MediaElement.js implementation needs special handling
this.wpMediaelement = null;
@@ -63,17 +68,17 @@
// Throttle to check for such case every 300ms
// On event the case becomes a fact
- this.window.bind( 'scroll.infinity', function() {
- this.throttle = true;
+ this.window.addEventListener( 'scroll', function () {
+ self.throttle = true;
} );
// Go back top method
self.gotop();
- setInterval( function() {
- if ( this.throttle ) {
+ setInterval( function () {
+ if ( self.throttle ) {
// Once the case is the case, the action occurs and the fact is no more
- this.throttle = false;
+ self.throttle = false;
// Reveal or hide footer
self.thefooter();
// Fire the refresh
@@ -84,16 +89,16 @@
// Ensure that enough posts are loaded to fill the initial viewport, to compensate for short posts and large displays.
self.ensureFilledViewport();
- this.body.bind( 'post-load', { self: self }, self.checkViewportOnLoad );
+ this.body.addEventListener( 'is.post-load', self.checkViewportOnLoadBound );
} else if ( type == 'click' ) {
if ( this.click_handle ) {
- this.element.append( this.handle );
+ this.element.appendChild( this.handle );
}
- this.body.delegate( '#infinite-handle', 'click.infinity', function() {
+ this.handle.addEventListener( 'click', function () {
// Handle the handle
if ( self.click_handle ) {
- $( '#infinite-handle' ).remove();
+ self.handle.parentNode.removeChild( self.handle );
}
// Fire the refresh
@@ -102,42 +107,71 @@
}
// Initialize any Core audio or video players loaded via IS
- this.body.bind( 'post-load', { self: self }, self.initializeMejs );
+ this.body.addEventListener( 'is.post-load', self.initializeMejs );
};
/**
- * Check whether we should fetch any additional posts.
+ * Normalize the access to the document scrollTop value.
+ */
+ Scroller.prototype.getScrollTop = function () {
+ return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
+ };
+
+ /**
+ * Polyfill jQuery.extend.
*/
- Scroller.prototype.check = function() {
- var container = this.element.offset();
+ Scroller.prototype.extend = function ( out ) {
+ out = out || {};
+
+ for ( var i = 1; i < arguments.length; i++ ) {
+ if ( ! arguments[ i ] ) {
+ continue;
+ }
- // If the container can't be found, stop otherwise errors result
- if ( 'object' !== typeof container ) {
- return false;
+ for ( var key in arguments[ i ] ) {
+ if ( arguments[ i ].hasOwnProperty( key ) ) {
+ out[ key ] = arguments[ i ][ key ];
+ }
+ }
}
+ return out;
+ };
- var bottom = this.window.scrollTop() + this.window.height(),
- threshold = container.top + this.element.outerHeight( false ) - this.window.height() * 2;
+ /**
+ * Check whether we should fetch any additional posts.
+ */
+ Scroller.prototype.check = function () {
+ var wrapperMeasurements = this.measure( this.element, [ this.wrapperClass ] );
- return bottom > threshold;
+ // Fetch more posts when we're less than 2 screens away from the bottom.
+ return wrapperMeasurements.bottom < 2 * this.window.innerHeight;
};
/**
* Renders the results from a successful response.
*/
- Scroller.prototype.render = function( response ) {
- this.body.addClass( 'infinity-success' );
+ Scroller.prototype.render = function ( response ) {
+ var childrenToAppend = Array.prototype.slice.call( response.fragment.childNodes );
+ this.body.classList.add( 'infinity-success' );
+
+ // Render the retrieved nodes.
+ while ( childrenToAppend.length > 0 ) {
+ var currentNode = childrenToAppend.shift();
+ this.element.appendChild( currentNode );
+ }
+
+ this.trigger( this.body, 'is.post-load', {
+ jqueryEventName: 'post-load',
+ data: response,
+ } );
- // Check if we can wrap the html
- this.element.append( response.html );
- this.body.trigger( 'post-load', response );
this.ready = true;
};
/**
* Returns the object used to query for new posts.
*/
- Scroller.prototype.query = function() {
+ Scroller.prototype.query = function () {
return {
page: this.page + this.offset, // Load the next page.
currentday: this.currentday,
@@ -150,56 +184,127 @@
};
};
+ Scroller.prototype.animate = function ( cb, duration ) {
+ var start = performance.now();
+
+ requestAnimationFrame( function animate( time ) {
+ var timeFraction = Math.min( 1, ( time - start ) / duration );
+ cb( timeFraction );
+
+ if ( timeFraction < 1 ) {
+ requestAnimationFrame( animate );
+ }
+ } );
+ };
+
/**
* Scroll back to top.
*/
- Scroller.prototype.gotop = function() {
- var blog = $( '#infinity-blog-title' );
+ Scroller.prototype.gotop = function () {
+ var blog = document.getElementById( 'infinity-blog-title' );
+ var self = this;
- blog.attr( 'title', totop );
+ if ( ! blog ) {
+ return;
+ }
- // Scroll to top on blog title
- blog.bind( 'click', function( e ) {
- $( 'html, body' ).animate( { scrollTop: 0 }, 'fast' );
+ blog.setAttribute( 'title', totop );
+ blog.addEventListener( 'click', function ( e ) {
+ var sourceScroll = self.window.pageYOffset;
e.preventDefault();
+
+ self.animate( function ( progress ) {
+ var currentScroll = sourceScroll - sourceScroll * progress;
+ document.documentElement.scrollTop = document.body.scrollTop = currentScroll;
+ }, 200 );
} );
};
/**
* The infinite footer.
*/
- Scroller.prototype.thefooter = function() {
+ Scroller.prototype.thefooter = function () {
var self = this,
- width;
+ pageWrapper,
+ footerContainer,
+ width,
+ sourceBottom,
+ targetBottom,
+ footerEnabled = this.footer && this.footer.el;
+
+ if ( ! footerEnabled ) {
+ return;
+ }
// Check if we have an id for the page wrapper
- if ( $.type( this.footer.wrap ) === 'string' ) {
- width = $( 'body #' + this.footer.wrap ).outerWidth( false );
+ if ( 'string' === typeof this.footer.wrap ) {
+ try {
+ pageWrapper = document.getElementById( this.footer.wrap );
+ width = pageWrapper.getBoundingClientRect();
+ width = width.width;
+ } catch ( err ) {
+ width = 0;
+ }
// Make the footer match the width of the page
if ( width > 479 ) {
- this.footer.find( '.container' ).css( 'width', width );
+ footerContainer = this.footer.el.querySelector( '.container' );
+ if ( footerContainer ) {
+ footerContainer.style.width = width + 'px';
+ }
}
}
// Reveal footer
- if ( this.window.scrollTop() >= 350 ) {
- self.footer.animate( { bottom: 0 }, 'fast' );
- } else if ( this.window.scrollTop() < 350 ) {
- self.footer.animate( { bottom: '-50px' }, 'fast' );
+ sourceBottom = parseInt( self.footer.el.style.bottom || -50, 10 );
+ targetBottom = this.window.pageYOffset >= 350 ? 0 : -50;
+
+ if ( sourceBottom !== targetBottom ) {
+ self.animate( function ( progress ) {
+ var currentBottom = sourceBottom + ( targetBottom - sourceBottom ) * progress;
+ self.footer.el.style.bottom = currentBottom + 'px';
+
+ if ( 1 === progress ) {
+ sourceBottom = targetBottom;
+ }
+ }, 200 );
+ }
+ };
+
+ /**
+ * Recursively convert a JS object into URL encoded data.
+ */
+ Scroller.prototype.urlEncodeJSON = function ( obj, prefix ) {
+ var params = [],
+ encodedKey,
+ newPrefix;
+
+ for ( var key in obj ) {
+ encodedKey = encodeURIComponent( key );
+ newPrefix = prefix ? prefix + '[' + encodedKey + ']' : encodedKey;
+
+ if ( 'object' === typeof obj[ key ] ) {
+ if ( ! Array.isArray( obj[ key ] ) || obj[ key ].length > 0 ) {
+ params.push( this.urlEncodeJSON( obj[ key ], newPrefix ) );
+ } else {
+ // Explicitly expose empty arrays with no values
+ params.push( newPrefix + '[]=' );
+ }
+ } else {
+ params.push( newPrefix + '=' + encodeURIComponent( obj[ key ] ) );
+ }
}
+ return params.join( '&' );
};
/**
* Controls the flow of the refresh. Don't mess.
*/
- Scroller.prototype.refresh = function() {
+ Scroller.prototype.refresh = function () {
var self = this,
query,
- jqxhr,
- load,
+ xhr,
loader,
- color,
customized;
// If we're disabled, ready, or don't pass the check, bail.
@@ -213,19 +318,19 @@
// Create a loader element to show it's working.
if ( this.click_handle ) {
- loader = '<span class="infinite-loader"></span>';
- this.element.append( loader );
-
- loader = this.element.find( '.infinite-loader' );
- color = loader.css( 'color' );
-
- try {
- loader.spin( 'medium-left', color );
- } catch ( error ) {}
+ if ( ! loader ) {
+ document.getElementById( 'infinite-aria' ).textContent = loading_text;
+ loader = document.createElement( 'div' );
+ loader.classList.add( 'infinite-loader' );
+ loader.setAttribute( 'role', 'progress' );
+ loader.innerHTML =
+ '<div class="spinner"><div class="spinner-inner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div></div>';
+ }
+ this.element.appendChild( loader );
}
// Generate our query vars.
- query = $.extend(
+ query = self.extend(
{
action: 'infinite_scroll',
},
@@ -237,7 +342,7 @@
customized = {};
query.wp_customize = 'on';
query.theme = wp.customize.settings.theme.stylesheet;
- wp.customize.each( function( setting ) {
+ wp.customize.each( function ( setting ) {
if ( setting._dirty ) {
customized[ setting.id ] = setting();
}
@@ -247,179 +352,227 @@
}
// Fire the ajax request.
- jqxhr = $.post( infiniteScroll.settings.ajaxurl, query );
+ xhr = new XMLHttpRequest();
+ xhr.open( 'POST', infiniteScroll.settings.ajaxurl, true );
+ xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
+ xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
+ xhr.send( self.urlEncodeJSON( query ) );
// Allow refreshes to occur again if an error is triggered.
- jqxhr.fail( function() {
+ xhr.onerror = function () {
if ( self.click_handle ) {
- loader.hide();
+ loader.parentNode.removeChild( loader );
}
self.ready = true;
- } );
+ };
// Success handler
- jqxhr.done( function( response ) {
+ xhr.onload = function () {
+ var response = JSON.parse( xhr.responseText ),
+ httpCheck = xhr.status >= 200 && xhr.status < 300,
+ responseCheck = 'undefined' !== typeof response.html;
+
+ if ( ! response || ! httpCheck || ! responseCheck ) {
+ if ( self.click_handle ) {
+ loader.parentNode.removeChild( loader );
+ }
+ return;
+ }
+
// On success, let's hide the loader circle.
if ( self.click_handle ) {
- loader.hide();
+ loader.parentNode.removeChild( loader );
}
- // Check for and parse our response.
- if ( ! response || ! response.type ) {
- return;
- }
+ // If additional scripts are required by the incoming set of posts, parse them
+ if ( response.scripts && Array.isArray( response.scripts ) ) {
+ response.scripts.forEach( function ( item ) {
+ var elementToAppendTo = item.footer ? 'body' : 'head';
- // If we've succeeded...
- if ( response.type == 'success' ) {
- // If additional scripts are required by the incoming set of posts, parse them
- if ( response.scripts ) {
- $( response.scripts ).each( function() {
- var elementToAppendTo = this.footer ? 'body' : 'head';
-
- // Add script handle to list of those already parsed
- window.infiniteScroll.settings.scripts.push( this.handle );
-
- // Output extra data, if present
- if ( this.extra_data ) {
- var data = document.createElement( 'script' ),
- dataContent = document.createTextNode(
- '//<![CDATA[ \n' + this.extra_data + '\n//]]>'
- );
-
- data.type = 'text/javascript';
- data.appendChild( dataContent );
-
- document.getElementsByTagName( elementToAppendTo )[ 0 ].appendChild( data );
- }
-
- // Build script tag and append to DOM in requested location
- var script = document.createElement( 'script' );
- script.type = 'text/javascript';
- script.src = this.src;
- script.id = this.handle;
-
- // If MediaElement.js is loaded in by this set of posts, don't initialize the players a second time as it breaks them all
- if ( 'wp-mediaelement' === this.handle ) {
- self.body.unbind( 'post-load', self.initializeMejs );
- }
-
- if ( 'wp-mediaelement' === this.handle && 'undefined' === typeof mejs ) {
- self.wpMediaelement = {};
- self.wpMediaelement.tag = script;
- self.wpMediaelement.element = elementToAppendTo;
- setTimeout( self.maybeLoadMejs.bind( self ), 250 );
- } else {
- document.getElementsByTagName( elementToAppendTo )[ 0 ].appendChild( script );
- }
- } );
- }
+ // Add script handle to list of those already parsed
+ window.infiniteScroll.settings.scripts.push( item.handle );
- // If additional stylesheets are required by the incoming set of posts, parse them
- if ( response.styles ) {
- $( response.styles ).each( function() {
- // Add stylesheet handle to list of those already parsed
- window.infiniteScroll.settings.styles.push( this.handle );
-
- // Build link tag
- var style = document.createElement( 'link' );
- style.rel = 'stylesheet';
- style.href = this.src;
- style.id = this.handle + '-css';
-
- // Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true
- if (
- this.conditional &&
- ( ! isIE || ! eval( this.conditional.replace( /%ver/g, IEVersion ) ) )
- ) {
- style = false;
- }
-
- // Append link tag if necessary
- if ( style ) {
- document.getElementsByTagName( 'head' )[ 0 ].appendChild( style );
- }
- } );
- }
+ // Output extra data, if present
+ if ( item.extra_data ) {
+ self.appendInlineScript( item.extra_data, elementToAppendTo );
+ }
- // stash the response in the page cache
- self.pageCache[ self.page + self.offset ] = response;
+ if ( item.before_handle ) {
+ self.appendInlineScript( item.before_handle, elementToAppendTo );
+ }
- // Increment the page number
- self.page++;
+ // Build script tag and append to DOM in requested location
+ var script = document.createElement( 'script' );
+ script.type = 'text/javascript';
+ script.src = item.src;
+ script.id = item.handle;
- // Record pageview in WP Stats, if available.
- if ( stats ) {
- new Image().src =
- document.location.protocol +
- '//pixel.wp.com/g.gif?' +
- stats +
- '&post=0&baba=' +
- Math.random();
- }
+ // Dynamically loaded scripts are async by default.
+ // We don't want that, it breaks stuff, e.g. wp-mediaelement init.
+ script.async = false;
- // Add new posts to the postflair object
- if ( 'object' === typeof response.postflair && 'object' === typeof WPCOM_sharing_counts ) {
- WPCOM_sharing_counts = $.extend( WPCOM_sharing_counts, response.postflair ); // eslint-disable-line no-global-assign
- }
+ if ( item.after_handle ) {
+ script.onload = function () {
+ self.appendInlineScript( item.after_handle, elementToAppendTo );
+ };
+ }
- // Render the results
- self.render.apply( self, arguments );
-
- // If 'click' type and there are still posts to fetch, add back the handle
- if ( type == 'click' ) {
- if ( response.lastbatch ) {
- if ( self.click_handle ) {
- $( '#infinite-handle' ).remove();
- // Update body classes
- self.body.addClass( 'infinity-end' ).removeClass( 'infinity-success' );
- } else {
- self.body.trigger( 'infinite-scroll-posts-end' );
- }
+ // If MediaElement.js is loaded in by item set of posts, don't initialize the players a second time as it breaks them all
+ if ( 'wp-mediaelement' === item.handle ) {
+ self.body.removeEventListener( 'is.post-load', self.initializeMejs );
+ }
+
+ if ( 'wp-mediaelement' === item.handle && 'undefined' === typeof mejs ) {
+ self.wpMediaelement = {};
+ self.wpMediaelement.tag = script;
+ self.wpMediaelement.element = elementToAppendTo;
+ setTimeout( self.maybeLoadMejs.bind( self ), 250 );
} else {
- if ( self.click_handle ) {
- self.element.append( self.handle );
- } else {
- self.body.trigger( 'infinite-scroll-posts-more' );
- }
+ document.getElementsByTagName( elementToAppendTo )[ 0 ].appendChild( script );
}
- } else if ( response.lastbatch ) {
- self.disabled = true;
- self.body.addClass( 'infinity-end' ).removeClass( 'infinity-success' );
- }
+ } );
+ }
+
+ // If additional stylesheets are required by the incoming set of posts, parse them
+ if ( response.styles && Array.isArray( response.styles ) ) {
+ response.styles.forEach( function ( item ) {
+ // Add stylesheet handle to list of those already parsed
+ window.infiniteScroll.settings.styles.push( item.handle );
+
+ // Build link tag
+ var style = document.createElement( 'link' );
+ style.rel = 'stylesheet';
+ style.href = item.src;
+ style.id = item.handle + '-css';
+
+ // Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true
+ if (
+ item.conditional &&
+ ( ! isIE || ! eval( item.conditional.replace( /%ver/g, IEVersion ) ) )
+ ) {
+ style = false;
+ }
+
+ // Append link tag if necessary
+ if ( style ) {
+ document.getElementsByTagName( 'head' )[ 0 ].appendChild( style );
+ }
+ } );
+ }
+
+ // Convert the response.html to a fragment element.
+ // Using a div instead of DocumentFragment, because the latter doesn't support innerHTML.
+ response.fragment = document.createElement( 'div' );
+ response.fragment.innerHTML = response.html;
+
+ // Increment the page number
+ self.page++;
+
+ // Record pageview in WP Stats, if available.
+ if ( stats ) {
+ new Image().src =
+ document.location.protocol +
+ '//pixel.wp.com/g.gif?' +
+ stats +
+ '&post=0&baba=' +
+ Math.random();
+ }
+
+ // Add new posts to the postflair object
+ if ( 'object' === typeof response.postflair && 'object' === typeof WPCOM_sharing_counts ) {
+ WPCOM_sharing_counts = self.extend( WPCOM_sharing_counts, response.postflair ); // eslint-disable-line no-global-assign
+ }
- // Update currentday to the latest value returned from the server
- if ( response.currentday ) {
- self.currentday = response.currentday;
+ // Render the results
+ self.render.call( self, response );
+
+ // If 'click' type and there are still posts to fetch, add back the handle
+ if ( type == 'click' ) {
+ // add focus to new posts, only in button mode as we know where page focus currently is and only if we have a wrapper
+ if ( infiniteScroll.settings.wrapper ) {
+ document
+ .querySelector(
+ '#infinite-view-' + ( self.page + self.offset - 1 ) + ' a:first-of-type'
+ )
+ .focus( {
+ preventScroll: true,
+ } );
}
- // Fire Google Analytics pageview
- if ( self.google_analytics ) {
- var ga_url = self.history.path.replace( /%d/, self.page );
- if ( 'object' === typeof _gaq ) {
- _gaq.push( [ '_trackPageview', ga_url ] );
+ if ( response.lastbatch ) {
+ if ( self.click_handle ) {
+ // Update body classes
+ self.body.classList.add( 'infinity-end' );
+ self.body.classList.remove( 'infinity-success' );
+ } else {
+ self.trigger( this.body, 'infinite-scroll-posts-end' );
}
- if ( 'function' === typeof ga ) {
- ga( 'send', 'pageview', ga_url );
+ } else {
+ if ( self.click_handle ) {
+ self.element.appendChild( self.handle );
+ } else {
+ self.trigger( this.body, 'infinite-scroll-posts-more' );
}
}
+ } else if ( response.lastbatch ) {
+ self.disabled = true;
+
+ self.body.classList.add( 'infinity-end' );
+ self.body.classList.remove( 'infinity-success' );
+ }
+
+ // Update currentday to the latest value returned from the server
+ if ( response.currentday ) {
+ self.currentday = response.currentday;
+ }
+
+ // Fire Google Analytics pageview
+ if ( self.google_analytics ) {
+ var ga_url = self.history.path.replace( /%d/, self.page );
+ if ( 'object' === typeof _gaq ) {
+ _gaq.push( [ '_trackPageview', ga_url ] );
+ }
+ if ( 'function' === typeof ga ) {
+ ga( 'send', 'pageview', ga_url );
+ }
}
- } );
+ };
- return jqxhr;
+ return xhr;
+ };
+
+ /**
+ * Given JavaScript blob and the name of a parent tag, this helper function will
+ * generate a script tag, insert the JavaScript blob, and append it to the parent.
+ *
+ * It's important to note that the JavaScript blob will be evaluated immediately. If
+ * you need a parent script to load first, use that script element's onload handler.
+ *
+ * @param {string} script The blob of JavaScript to run.
+ * @param {string} parentTag The tag name of the parent element.
+ */
+ Scroller.prototype.appendInlineScript = function ( script, parentTag ) {
+ var element = document.createElement( 'script' ),
+ scriptContent = document.createTextNode( '//<![CDATA[ \n' + script + '\n//]]>' );
+
+ element.type = 'text/javascript';
+ element.appendChild( scriptContent );
+
+ document.getElementsByTagName( parentTag )[ 0 ].appendChild( element );
};
/**
* Core's native media player uses MediaElement.js
* The library's size is sufficient that it may not be loaded in time for Core's helper to invoke it, so we need to delay until `mejs` exists.
*/
- Scroller.prototype.maybeLoadMejs = function() {
+ Scroller.prototype.maybeLoadMejs = function () {
if ( null === this.wpMediaelement ) {
return;
}
if ( 'undefined' === typeof mejs ) {
- setTimeout( this.maybeLoadMejs, 250 );
+ setTimeout( this.maybeLoadMejs.bind( this ), 250 );
} else {
document
.getElementsByTagName( this.wpMediaelement.element )[ 0 ]
@@ -427,19 +580,20 @@
this.wpMediaelement = null;
// Ensure any subsequent IS loads initialize the players
- this.body.bind( 'post-load', { self: this }, this.initializeMejs );
+ this.body.addEventListener( 'is.post-load', this.initializeMejs );
}
};
/**
* Initialize the MediaElement.js player for any posts not previously initialized
*/
- Scroller.prototype.initializeMejs = function( ev, response ) {
+ Scroller.prototype.initializeMejs = function ( e ) {
// Are there media players in the incoming set of posts?
if (
- ! response.html ||
- ( -1 === response.html.indexOf( 'wp-audio-shortcode' ) &&
- -1 === response.html.indexOf( 'wp-video-shortcode' ) )
+ ! e.detail ||
+ ! e.detail.html ||
+ ( -1 === e.detail.html.indexOf( 'wp-audio-shortcode' ) &&
+ -1 === e.detail.html.indexOf( 'wp-video-shortcode' ) )
) {
return;
}
@@ -451,73 +605,118 @@
// Adapted from wp-includes/js/mediaelement/wp-mediaelement.js
// Modified to not initialize already-initialized players, as Mejs doesn't handle that well
- $( function() {
- var settings = {};
+ var settings = {};
+ var audioVideoElements;
+
+ if ( typeof _wpmejsSettings !== 'undefined' ) {
+ settings.pluginPath = _wpmejsSettings.pluginPath;
+ }
- if ( typeof _wpmejsSettings !== 'undefined' ) {
- settings.pluginPath = _wpmejsSettings.pluginPath;
+ settings.success = function ( mejs ) {
+ var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
+ if ( 'flash' === mejs.pluginType && autoplay ) {
+ mejs.addEventListener(
+ 'canplay',
+ function () {
+ mejs.play();
+ },
+ false
+ );
}
+ };
- settings.success = function( mejs ) {
- var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
- if ( 'flash' === mejs.pluginType && autoplay ) {
- mejs.addEventListener(
- 'canplay',
- function() {
- mejs.play();
- },
- false
- );
- }
- };
+ audioVideoElements = document.querySelectorAll( '.wp-audio-shortcode, .wp-video-shortcode' );
+ audioVideoElements = Array.prototype.slice.call( audioVideoElements );
- $( '.wp-audio-shortcode, .wp-video-shortcode' )
- .not( '.mejs-container' )
- .mediaelementplayer( settings );
+ // Only process already unprocessed shortcodes.
+ audioVideoElements = audioVideoElements.filter( function ( el ) {
+ while ( el.parentNode ) {
+ if ( el.classList.contains( 'mejs-container' ) ) {
+ return false;
+ }
+ el = el.parentNode;
+ }
+ return true;
} );
+
+ for ( var i = 0; i < audioVideoElements.length; i++ ) {
+ new MediaElementPlayer( audioVideoElements[ i ], settings );
+ }
};
/**
- * Trigger IS to load additional posts if the initial posts don't fill the window.
- * On large displays, or when posts are very short, the viewport may not be filled with posts, so we overcome this by loading additional posts when IS initializes.
+ * Get element measurements relative to the viewport.
+ *
+ * @returns {object}
*/
- Scroller.prototype.ensureFilledViewport = function() {
- var self = this,
- windowHeight = self.window.height(),
- postsHeight = self.element.height(),
- aveSetHeight = 0,
- wrapperQty = 0;
-
- // Account for situations where postsHeight is 0 because child list elements are floated
- if ( postsHeight === 0 ) {
- $( self.element.selector + ' > li' ).each( function() {
- postsHeight += $( this ).height();
- } );
-
- if ( postsHeight === 0 ) {
- self.body.unbind( 'post-load', self.checkViewportOnLoad );
- return;
+ Scroller.prototype.measure = function ( element, expandClasses ) {
+ expandClasses = expandClasses || [];
+
+ var childrenToTest = Array.prototype.slice.call( element.children );
+ var currentChild,
+ minTop = Number.MAX_VALUE,
+ maxBottom = 0,
+ currentChildRect,
+ i;
+
+ while ( childrenToTest.length > 0 ) {
+ currentChild = childrenToTest.shift();
+
+ for ( i = 0; i < expandClasses.length; i++ ) {
+ // Expand (= measure) child elements of nodes with class names from expandClasses.
+ if ( currentChild.classList.contains( expandClasses[ i ] ) ) {
+ childrenToTest = childrenToTest.concat(
+ Array.prototype.slice.call( currentChild.children )
+ );
+ break;
+ }
}
+ currentChildRect = currentChild.getBoundingClientRect();
+
+ minTop = Math.min( minTop, currentChildRect.top );
+ maxBottom = Math.max( maxBottom, currentChildRect.bottom );
}
- // Calculate average height of a set of posts to prevent more posts than needed from being loaded.
- $( '.' + self.wrapperClass ).each( function() {
- aveSetHeight += $( this ).height();
- wrapperQty++;
- } );
+ var viewportMiddle = Math.round( window.innerHeight / 2 );
- if ( wrapperQty > 0 ) {
- aveSetHeight = aveSetHeight / wrapperQty;
- } else {
- aveSetHeight = 0;
- }
+ // isActive = does the middle of the viewport cross the element?
+ var isActive = minTop <= viewportMiddle && maxBottom >= viewportMiddle;
+
+ /**
+ * Factor = percentage of viewport above the middle line occupied by the element.
+ *
+ * Negative factors are assigned for elements below the middle line. That's on purpose
+ * to only allow "page 2" to change the URL once it's in the middle of the viewport.
+ */
+ var factor = ( Math.min( maxBottom, viewportMiddle ) - Math.max( minTop, 0 ) ) / viewportMiddle;
+
+ return {
+ top: minTop,
+ bottom: maxBottom,
+ height: maxBottom - minTop,
+ factor: factor,
+ isActive: isActive,
+ };
+ };
+
+ /**
+ * Trigger IS to load additional posts if the initial posts don't fill the window.
+ *
+ * On large displays, or when posts are very short, the viewport may not be filled with posts,
+ * so we overcome this by loading additional posts when IS initializes.
+ */
+ Scroller.prototype.ensureFilledViewport = function () {
+ var self = this,
+ windowHeight = self.window.innerHeight,
+ wrapperMeasurements = self.measure( self.element, [ self.wrapperClass ] );
+
+ // Only load more posts once. This prevents infinite loops when there are no more posts.
+ self.body.removeEventListener( 'is.post-load', self.checkViewportOnLoadBound );
- // Load more posts if space permits, otherwise stop checking for a full viewport
- if ( postsHeight < windowHeight && postsHeight + aveSetHeight < windowHeight ) {
+ // Load more posts if space permits, otherwise stop checking for a full viewport.
+ if ( wrapperMeasurements.bottom < windowHeight ) {
self.ready = true;
self.refresh();
- } else {
- self.body.unbind( 'post-load', self.checkViewportOnLoad );
}
};
@@ -525,8 +724,8 @@
* Event handler for ensureFilledViewport(), tied to the post-load trigger.
* Necessary to ensure that the variable `this` contains the scroller when used in ensureFilledViewport(). Since this function is tied to an event, `this` becomes the DOM element the event is tied to.
*/
- Scroller.prototype.checkViewportOnLoad = function( ev ) {
- ev.data.self.ensureFilledViewport();
+ Scroller.prototype.checkViewportOnLoad = function () {
+ this.ensureFilledViewport();
};
function fullscreenState() {
@@ -543,15 +742,12 @@
/**
* Identify archive page that corresponds to majority of posts shown in the current browser window.
*/
- Scroller.prototype.determineURL = function() {
+ Scroller.prototype.determineURL = function () {
var self = this,
- windowTop = $( window ).scrollTop(),
- windowBottom = windowTop + $( window ).height(),
- windowSize = windowBottom - windowTop,
- setsInView = [],
- setsHidden = [],
- pageNum = false,
- currentFullScreenState = fullscreenState();
+ pageNum = -1,
+ currentFullScreenState = fullscreenState(),
+ wrapperEls,
+ maxFactor = 0;
// xor - check if the state has changed
if ( previousFullScrenState ^ currentFullScreenState ) {
@@ -564,123 +760,35 @@
return;
}
previousFullScrenState = currentFullScreenState;
+ wrapperEls = document.querySelectorAll( '.' + self.wrapperClass );
- // Find out which sets are in view
- $( '.' + self.wrapperClass ).each( function() {
- var id = $( this ).attr( 'id' ),
- setTop = $( this ).offset().top,
- setHeight = $( this ).outerHeight( false ),
- setBottom = 0,
- setPageNum = $( this ).data( 'page-num' );
-
- // Account for containers that have no height because their children are floated elements.
- if ( 0 === setHeight ) {
- $( '> *', this ).each( function() {
- setHeight += $( this ).outerHeight( false );
- } );
- }
+ for ( var i = 0; i < wrapperEls.length; i++ ) {
+ var setMeasurements = self.measure( wrapperEls[ i ] );
- // Determine position of bottom of set by adding its height to the scroll position of its top.
- setBottom = setTop + setHeight;
-
- // Populate setsInView object. While this logic could all be combined into a single conditional statement, this is easier to understand.
- if ( setTop < windowTop && setBottom > windowBottom ) {
- // top of set is above window, bottom is below
- setsInView.push( { id: id, top: setTop, bottom: setBottom, pageNum: setPageNum } );
- } else if ( setTop > windowTop && setTop < windowBottom ) {
- // top of set is between top (gt) and bottom (lt)
- setsInView.push( { id: id, top: setTop, bottom: setBottom, pageNum: setPageNum } );
- } else if ( setBottom > windowTop && setBottom < windowBottom ) {
- // bottom of set is between top (gt) and bottom (lt)
- setsInView.push( { id: id, top: setTop, bottom: setBottom, pageNum: setPageNum } );
- } else {
- setsHidden.push( { id: id, top: setTop, bottom: setBottom, pageNum: setPageNum } );
+ // If it exists, pick a set that is crossed by the middle of the viewport.
+ if ( setMeasurements.isActive ) {
+ pageNum = parseInt( wrapperEls[ i ].dataset.pageNum, 10 );
+ break;
}
- } );
- $.each( setsHidden, function() {
- var $set = $( '#' + this.id );
- if ( $set.hasClass( 'is--replaced' ) ) {
- return;
+ // If there is such a set, pick the one that occupies the most space
+ // above the middle of the viewport.
+ if ( setMeasurements.factor > maxFactor ) {
+ pageNum = parseInt( wrapperEls[ i ].dataset.pageNum, 10 );
+ maxFactor = setMeasurements.factor;
}
- self.pageCache[ this.pageNum ].html = $set.html();
-
- $set
- .css( 'min-height', this.bottom - this.top + 'px' )
- .addClass( 'is--replaced' )
- .empty();
- } );
-
- $.each( setsInView, function() {
- var $set = $( '#' + this.id );
-
- if ( $set.hasClass( 'is--replaced' ) ) {
- $set.css( 'min-height', '' ).removeClass( 'is--replaced' );
- if ( this.pageNum in self.pageCache ) {
- $set.html( self.pageCache[ this.pageNum ].html );
- self.body.trigger( 'post-load', self.pageCache[ this.pageNum ] );
- }
- }
- } );
-
- // Parse number of sets found in view in an attempt to update the URL to match the set that comprises the majority of the window.
- if ( 0 == setsInView.length ) {
- pageNum = -1;
- } else if ( 1 == setsInView.length ) {
- var setData = setsInView.pop();
-
- // If the first set of IS posts is in the same view as the posts loaded in the template by WordPress, determine how much of the view is comprised of IS-loaded posts
- if ( ( windowBottom - setData.top ) / windowSize < 0.5 ) {
- pageNum = -1;
- } else {
- pageNum = setData.pageNum;
- }
- } else {
- var majorityPercentageInView = 0;
-
- // Identify the IS set that comprises the majority of the current window and set the URL to it.
- $.each( setsInView, function( i, setData ) {
- var topInView = 0,
- bottomInView = 0,
- percentOfView = 0;
-
- // Figure percentage of view the current set represents
- if ( setData.top > windowTop && setData.top < windowBottom ) {
- topInView = ( windowBottom - setData.top ) / windowSize;
- }
-
- if ( setData.bottom > windowTop && setData.bottom < windowBottom ) {
- bottomInView = ( setData.bottom - windowTop ) / windowSize;
- }
-
- // Figure out largest percentage of view for current set
- if ( topInView >= bottomInView ) {
- percentOfView = topInView;
- } else if ( bottomInView >= topInView ) {
- percentOfView = bottomInView;
- }
-
- // Does current set's percentage of view supplant the largest previously-found set?
- if ( percentOfView > majorityPercentageInView ) {
- pageNum = setData.pageNum;
- majorityPercentageInView = percentOfView;
- }
- } );
+ // Otherwise default to -1
}
- // If a page number could be determined, update the URL
- // -1 indicates that the original requested URL should be used.
- if ( 'number' === typeof pageNum ) {
- self.updateURL( pageNum );
- }
+ self.updateURL( pageNum );
};
/**
* Update address bar to reflect archive page URL for a given page number.
* Checks if URL is different to prevent pollution of browser history.
*/
- Scroller.prototype.updateURL = function( page ) {
+ Scroller.prototype.updateURL = function ( page ) {
// IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met.
if ( ! window.history.pushState ) {
return;
@@ -705,27 +813,67 @@
/**
* Pause scrolling.
*/
- Scroller.prototype.pause = function() {
+ Scroller.prototype.pause = function () {
this.disabled = true;
};
/**
* Resume scrolling.
*/
- Scroller.prototype.resume = function() {
+ Scroller.prototype.resume = function () {
this.disabled = false;
};
/**
+ * Emits custom JS events.
+ *
+ * @param {Node} el
+ * @param {string} eventName
+ * @param {*} data
+ */
+ Scroller.prototype.trigger = function ( el, eventName, opts ) {
+ opts = opts || {};
+
+ /**
+ * Emit the event in a jQuery way for backwards compatibility where necessary.
+ */
+ if ( opts.jqueryEventName && 'undefined' !== typeof jQuery ) {
+ jQuery( el ).trigger( opts.jqueryEventName, opts.data || null );
+ }
+
+ /**
+ * Emit the event in a standard way.
+ */
+ var e;
+ try {
+ e = new CustomEvent( eventName, {
+ bubbles: true,
+ cancelable: true,
+ detail: opts.data || null,
+ } );
+ } catch ( err ) {
+ e = document.createEvent( 'CustomEvent' );
+ e.initCustomEvent( eventName, true, true, opts.data || null );
+ }
+ el.dispatchEvent( e );
+ };
+
+ /**
* Ready, set, go!
*/
- $( document ).ready( function() {
+ var jetpackInfinityModule = function () {
+ var bodyClasses = infiniteScroll.settings.body_class.split( ' ' );
+
// Check for our variables
if ( 'object' !== typeof infiniteScroll ) {
return;
}
- $( document.body ).addClass( infiniteScroll.settings.body_class );
+ bodyClasses.forEach( function ( className ) {
+ if ( className ) {
+ document.body.classList.add( className );
+ }
+ } );
// Set ajaxurl (for brevity)
ajaxurl = infiniteScroll.settings.ajaxurl;
@@ -738,6 +886,9 @@
text = infiniteScroll.settings.text;
totop = infiniteScroll.settings.totop;
+ // aria text
+ loading_text = infiniteScroll.settings.loading_text;
+
// Initialize the scroller (with the ID of the element from the theme)
infiniteScroll.scroller = new Scroller( infiniteScroll.settings );
@@ -746,63 +897,25 @@
*/
if ( type == 'click' ) {
var timer = null;
- $( window ).bind( 'scroll', function() {
+ window.addEventListener( 'scroll', function () {
// run the real scroll handler once every 250 ms.
if ( timer ) {
return;
}
- timer = setTimeout( function() {
+ timer = setTimeout( function () {
infiniteScroll.scroller.determineURL();
timer = null;
}, 250 );
} );
}
+ };
- // Integrate with Selective Refresh in the Customizer.
- if ( 'undefined' !== typeof wp && wp.customize && wp.customize.selectiveRefresh ) {
- /**
- * Handle rendering of selective refresh partials.
- *
- * Make sure that when a partial is rendered, the Jetpack post-load event
- * will be triggered so that any dynamic elements will be re-constructed,
- * such as ME.js elements, Photon replacements, social sharing, and more.
- * Note that this is applying here not strictly to posts being loaded.
- * If a widget contains a ME.js element and it is previewed via selective
- * refresh, the post-load would get triggered allowing any dynamic elements
- * therein to also be re-constructed.
- *
- * @param {wp.customize.selectiveRefresh.Placement} placement
- */
- wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
- var content;
- if ( 'string' === typeof placement.addedContent ) {
- content = placement.addedContent;
- } else if ( placement.container ) {
- content = $( placement.container ).html();
- }
-
- if ( content ) {
- $( document.body ).trigger( 'post-load', { html: content } );
- }
- } );
-
- /*
- * Add partials for posts added via infinite scroll.
- *
- * This is unnecessary when MutationObserver is supported by the browser
- * since then this will be handled by Selective Refresh in core.
- */
- if ( 'undefined' === typeof MutationObserver ) {
- $( document.body ).on( 'post-load', function( e, response ) {
- var rootElement = null;
- if ( response.html && -1 !== response.html.indexOf( 'data-customize-partial' ) ) {
- if ( infiniteScroll.settings.id ) {
- rootElement = $( '#' + infiniteScroll.settings.id );
- }
- wp.customize.selectiveRefresh.addPartials( rootElement );
- }
- } );
- }
- }
- } );
-} )( jQuery ); // Close closure
+ /**
+ * Ready, set, go!
+ */
+ if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
+ jetpackInfinityModule();
+ } else {
+ document.addEventListener( 'DOMContentLoaded', jetpackInfinityModule );
+ }
+} )(); // Close closure
diff --git a/plugins/jetpack/modules/infinite-scroll/infinity.php b/plugins/jetpack/modules/infinite-scroll/infinity.php
index 6b8d3775..a26419d7 100644
--- a/plugins/jetpack/modules/infinite-scroll/infinity.php
+++ b/plugins/jetpack/modules/infinite-scroll/infinity.php
@@ -1,6 +1,7 @@
<?php
use Automattic\Jetpack\Assets;
+use Automattic\Jetpack\Redirect;
/*
Plugin Name: The Neverending Home Page.
@@ -18,6 +19,10 @@ License URI: https://www.gnu.org/licenses/gpl-2.0.html
* styling from each theme; including fixed footer.
*/
class The_Neverending_Home_Page {
+ /**
+ * Maximum allowed number of posts per page in $_REQUEST.
+ */
+ const MAX_ALLOWED_POSTS_PER_PAGE_ΙΝ_REQUEST = 5000;
/**
* Register actions and filters, plus parse IS settings
@@ -26,20 +31,25 @@ class The_Neverending_Home_Page {
* @return null
*/
function __construct() {
- add_action( 'pre_get_posts', array( $this, 'posts_per_page_query' ) );
-
- add_action( 'admin_init', array( $this, 'settings_api_init' ) );
- add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
- add_action( 'template_redirect', array( $this, 'ajax_response' ) );
- add_action( 'custom_ajax_infinite_scroll', array( $this, 'query' ) );
- add_filter( 'infinite_scroll_query_args', array( $this, 'inject_query_args' ) );
- add_filter( 'infinite_scroll_allowed_vars', array( $this, 'allowed_query_vars' ) );
- add_action( 'the_post', array( $this, 'preserve_more_tag' ) );
- add_action( 'wp_footer', array( $this, 'footer' ) );
+ add_action( 'pre_get_posts', array( $this, 'posts_per_page_query' ) );
+ add_action( 'admin_init', array( $this, 'settings_api_init' ) );
+ add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
+ add_action( 'customize_preview_init', array( $this, 'init_customizer_assets' ) );
+ add_action( 'template_redirect', array( $this, 'ajax_response' ) );
+ add_action( 'custom_ajax_infinite_scroll', array( $this, 'query' ) );
+ add_filter( 'infinite_scroll_query_args', array( $this, 'inject_query_args' ) );
+ add_filter( 'infinite_scroll_allowed_vars', array( $this, 'allowed_query_vars' ) );
+ add_action( 'the_post', array( $this, 'preserve_more_tag' ) );
+ add_action( 'wp_footer', array( $this, 'footer' ) );
+ add_filter( 'infinite_scroll_additional_scripts', array( $this, 'add_mejs_config' ) );
// Plugin compatibility
add_filter( 'grunion_contact_form_redirect_url', array( $this, 'filter_grunion_redirect_url' ) );
+ // AMP compatibility
+ // needs to happen after parse_query so that Jetpack_AMP_Support::is_amp_request() is ready.
+ add_action( 'wp', array( $this, 'amp_load_hooks' ) );
+
// Parse IS settings from theme
self::get_settings();
}
@@ -247,11 +257,23 @@ class The_Neverending_Home_Page {
* @return int
*/
static function posts_per_page() {
- $posts_per_page = self::get_settings()->posts_per_page ? self::get_settings()->posts_per_page : self::wp_query()->get( 'posts_per_page' );
+ $posts_per_page = self::get_settings()->posts_per_page ? self::get_settings()->posts_per_page : self::wp_query()->get( 'posts_per_page' );
+ $posts_per_page_core_option = get_option( 'posts_per_page' );
+
+ // If Infinite Scroll is set to click, and if the site owner changed posts_per_page, let's use that.
+ if (
+ 'click' === self::get_settings()->type
+ && ( '10' !== $posts_per_page_core_option )
+ ) {
+ $posts_per_page = $posts_per_page_core_option;
+ }
- // Take JS query into consideration here
- if ( true === isset( $_REQUEST['query_args']['posts_per_page'] ) ) {
- $posts_per_page = $_REQUEST['query_args']['posts_per_page'];
+ // Take JS query into consideration here.
+ $posts_per_page_in_request = isset( $_REQUEST['query_args']['posts_per_page'] ) ? (int) $_REQUEST['query_args']['posts_per_page'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( $posts_per_page_in_request > 0 &&
+ self::MAX_ALLOWED_POSTS_PER_PAGE_ΙΝ_REQUEST >= $posts_per_page_in_request
+ ) {
+ $posts_per_page = $posts_per_page_in_request;
}
/**
@@ -389,11 +411,12 @@ class The_Neverending_Home_Page {
}
function infinite_setting_html_calypso_placeholder() {
- $details = get_blog_details();
+ $details = get_blog_details();
+ $writing_url = Redirect::get_url( 'calypso-settings-writing', array( 'site' => $details->domain ) );
echo '<span>' . sprintf(
/* translators: Variables are the enclosing link to the settings page */
- esc_html__( 'This option has moved. You can now manage it %1$shere%2$s.' ),
- '<a href="' . esc_url( 'https://wordpress.com/settings/writing/' . $details->domain ) . '">',
+ esc_html__( 'This option has moved. You can now manage it %1$shere%2$s.', 'jetpack' ),
+ '<a href="' . esc_url( $writing_url ) . '">',
'</a>'
) . '</span>';
}
@@ -432,6 +455,11 @@ class The_Neverending_Home_Page {
if ( empty( $id ) )
return;
+ // AMP infinite scroll functionality will start on amp_load_hooks().
+ if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
+ return;
+ }
+
// Add our scripts.
wp_register_script(
'the-neverending-homepage',
@@ -439,8 +467,8 @@ class The_Neverending_Home_Page {
'_inc/build/infinite-scroll/infinity.min.js',
'modules/infinite-scroll/infinity.js'
),
- array( 'jquery' ),
- '4.0.0',
+ array(),
+ JETPACK__VERSION . '-is5.0.1', // Added for ability to cachebust on WP.com.
true
);
@@ -458,8 +486,6 @@ class The_Neverending_Home_Page {
// Add our default styles.
wp_enqueue_style( 'the-neverending-homepage' );
- add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_spinner_scripts' ) );
-
add_action( 'wp_footer', array( $this, 'action_wp_footer_settings' ), 2 );
add_action( 'wp_footer', array( $this, 'action_wp_footer' ), 21 ); // Core prints footer scripts at priority 20, so we just need to be one later than that
@@ -468,10 +494,24 @@ class The_Neverending_Home_Page {
}
/**
- * Enqueue spinner scripts.
+ * Initialize the Customizer logic separately from the main JS.
+ *
+ * @since 8.4.0
*/
- function enqueue_spinner_scripts() {
- wp_enqueue_script( 'jquery.spin' );
+ public function init_customizer_assets() {
+ // Add our scripts.
+ wp_register_script(
+ 'the-neverending-homepage-customizer',
+ Assets::get_file_url_for_environment(
+ '_inc/build/infinite-scroll/infinity-customizer.min.js',
+ 'modules/infinite-scroll/infinity-customizer.js'
+ ),
+ array( 'customize-base' ),
+ JETPACK__VERSION . '-is5.0.0', // Added for ability to cachebust on WP.com.
+ true
+ );
+
+ wp_enqueue_script( 'the-neverending-homepage-customizer' );
}
/**
@@ -814,7 +854,11 @@ class The_Neverending_Home_Page {
// Check if the taxonomy is attached to one post type only and use its plural name.
// If not, use "Posts" without confusing the users.
- if ( count( $taxonomy->object_type ) < 2 ) {
+ if (
+ is_a( $taxonomy, 'WP_Taxonomy' )
+ && is_countable( $taxonomy->object_type )
+ && count( $taxonomy->object_type ) < 2
+ ) {
$post_type = $taxonomy->object_type[0];
}
}
@@ -866,6 +910,7 @@ class The_Neverending_Home_Page {
'query_before' => current_time( 'mysql' ),
'last_post_date' => self::get_last_post_date(),
'body_class' => self::body_class(),
+ 'loading_text' => esc_js( __( 'Loading new page', 'jetpack' ) ),
);
// Optional order param
@@ -999,9 +1044,34 @@ class The_Neverending_Home_Page {
$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
?><script type="text/javascript">
- jQuery.extend( infiniteScroll.settings.scripts, <?php echo json_encode( $scripts ); ?> );
- jQuery.extend( infiniteScroll.settings.styles, <?php echo json_encode( $styles ); ?> );
- </script><?php
+ (function() {
+ var extend = function(out) {
+ out = out || {};
+
+ for (var i = 1; i < arguments.length; i++) {
+ if (!arguments[i])
+ continue;
+
+ for (var key in arguments[i]) {
+ if (arguments[i].hasOwnProperty(key))
+ out[key] = arguments[i][key];
+ }
+ }
+
+ return out;
+ };
+ extend( window.infiniteScroll.settings.scripts, <?php echo wp_json_encode( $scripts ); ?> );
+ extend( window.infiniteScroll.settings.styles, <?php echo wp_json_encode( $styles ); ?> );
+ })();
+ </script>
+ <?php
+ $aria_live = 'assertive';
+ if ( 'scroll' === self::get_settings()->type ) {
+ $aria_live = 'polite';
+ }
+ ?>
+ <span id="infinite-aria" aria-live="<?php echo esc_attr( $aria_live ); ?>"></span>
+ <?php
}
/**
@@ -1024,7 +1094,19 @@ class The_Neverending_Home_Page {
global $wp_scripts;
// Identify new scripts needed by the latest set of IS posts
- $new_scripts = array_diff( $wp_scripts->done, $initial_scripts );
+ $new_scripts = array_filter(
+ $wp_scripts->done,
+ function ( $script_name ) use ( $initial_scripts ) {
+ // Jetpack block scripts should always be sent, even if they've been
+ // sent before. These scripts only run once on when loaded, they don't
+ // watch for new blocks being added.
+ if ( 0 === strpos( $script_name, 'jetpack-block-' ) ) {
+ return true;
+ }
+
+ return ! in_array( $script_name, $initial_scripts, true );
+ }
+ );
// If new scripts are needed, extract relevant data from $wp_scripts
if ( ! empty( $new_scripts ) ) {
@@ -1032,14 +1114,20 @@ class The_Neverending_Home_Page {
foreach ( $new_scripts as $handle ) {
// Abort if somehow the handle doesn't correspond to a registered script
- if ( ! isset( $wp_scripts->registered[ $handle ] ) )
+ // or if the script doesn't have `src` set.
+ $script_not_registered = ! isset( $wp_scripts->registered[ $handle ] );
+ $empty_src = empty( $wp_scripts->registered[ $handle ]->src );
+ if ( $script_not_registered || $empty_src ) {
continue;
+ }
// Provide basic script data
$script_data = array(
- 'handle' => $handle,
- 'footer' => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer ) ),
- 'extra_data' => $wp_scripts->print_extra_script( $handle, false )
+ 'handle' => $handle,
+ 'footer' => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer, true ) ),
+ 'extra_data' => $wp_scripts->print_extra_script( $handle, false ),
+ 'before_handle' => $wp_scripts->print_inline_script( $handle, 'before', false ),
+ 'after_handle' => $wp_scripts->print_inline_script( $handle, 'after', false ),
);
// Base source
@@ -1280,39 +1368,49 @@ class The_Neverending_Home_Page {
$results['type'] = 'success';
/**
- * Gather renderer callbacks. These will be called in order and allow multiple callbacks to be queued. Once content is found, no futher callbacks will run.
+ * Fires when rendering Infinite Scroll posts.
*
* @module infinite-scroll
*
- * @since 6.0.0
+ * @since 2.0.0
*/
- $callbacks = apply_filters( 'infinite_scroll_render_callbacks', array(
- self::get_settings()->render, // This is the setting callback e.g. from add theme support.
- ) );
-
- // Append fallback callback. That rhymes.
- $callbacks[] = array( $this, 'render' );
-
- foreach ( $callbacks as $callback ) {
- if ( false !== $callback && is_callable( $callback ) ) {
- rewind_posts();
- ob_start();
- add_action( 'infinite_scroll_render', $callback );
-
- /**
- * Fires when rendering Infinite Scroll posts.
- *
- * @module infinite-scroll
- *
- * @since 2.0.0
- */
- do_action( 'infinite_scroll_render' );
-
- $results['html'] = ob_get_clean();
- remove_action( 'infinite_scroll_render', $callback );
- }
- if ( ! empty( $results['html'] ) ) {
- break;
+ do_action( 'infinite_scroll_render' );
+ $results['html'] = ob_get_clean();
+ if ( empty( $results['html'] ) ) {
+ /**
+ * Gather renderer callbacks. These will be called in order and allow multiple callbacks to be queued. Once content is found, no futher callbacks will run.
+ *
+ * @module infinite-scroll
+ *
+ * @since 6.0.0
+ */
+ $callbacks = apply_filters(
+ 'infinite_scroll_render_callbacks',
+ array( self::get_settings()->render ) // This is the setting callback e.g. from add theme support.
+ );
+
+ // Append fallback callback. That rhymes.
+ $callbacks[] = array( $this, 'render' );
+
+ foreach ( $callbacks as $callback ) {
+ if ( false !== $callback && is_callable( $callback ) ) {
+ rewind_posts();
+ ob_start();
+ add_action( 'infinite_scroll_render', $callback );
+
+ /**
+ * This action is already documented above.
+ * See https://github.com/Automattic/jetpack/pull/16317/
+ * for more details as to why it was introduced.
+ */
+ do_action( 'infinite_scroll_render' );
+
+ $results['html'] = ob_get_clean();
+ remove_action( 'infinite_scroll_render', $callback );
+ }
+ if ( ! empty( $results['html'] ) ) {
+ break;
+ }
}
}
@@ -1332,8 +1430,13 @@ class The_Neverending_Home_Page {
$wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
$wrapper_classes .= ' infinite-view-' . $page;
$wrapper_classes = trim( $wrapper_classes );
+ $aria_label = sprintf(
+ /* translators: %1$s is the page count */
+ __( 'Page: %1$d.', 'jetpack' ),
+ $page
+ );
- $results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '">' . $results['html'] . '</div>';
+ $results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '" role="region" aria-label="' . esc_attr( $aria_label ) . '">' . $results['html'] . '</div>';
}
// Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
@@ -1510,6 +1613,10 @@ class The_Neverending_Home_Page {
* @return string or null
*/
function footer() {
+ if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
+ return;
+ }
+
// Bail if theme requested footer not show
if ( false == self::get_settings()->footer )
return;
@@ -1602,6 +1709,297 @@ class The_Neverending_Home_Page {
return $url;
}
+
+ /**
+ * When the MediaElement is loaded in dynamically, we need to enforce that
+ * its settings are added to the page as well.
+ *
+ * @param array $scripts_data New scripts exposed to the infinite scroll.
+ *
+ * @since 8.4.0
+ */
+ public function add_mejs_config( $scripts_data ) {
+ foreach ( $scripts_data as $key => $data ) {
+ if ( 'mediaelement-core' === $data['handle'] ) {
+ $mejs_settings = array(
+ 'pluginPath' => includes_url( 'js/mediaelement/', 'relative' ),
+ 'classPrefix' => 'mejs-',
+ 'stretching' => 'responsive',
+ );
+
+ $scripts_data[ $key ]['extra_data'] = sprintf(
+ 'window.%s = %s',
+ '_wpmejsSettings',
+ wp_json_encode( apply_filters( 'mejs_settings', $mejs_settings ) )
+ );
+ }
+ }
+ return $scripts_data;
+ }
+
+ /**
+ * Determines whether the legacy AMP Reader post templates are being used.
+ *
+ * @return bool
+ */
+ private function is_exempted_amp_page() {
+ if ( is_singular( 'web-story' ) ) {
+ // Ensure that <amp-next-page> is not injected after <amp-story> as generated by the Web Stories plugin.
+ return true;
+ }
+ if ( function_exists( 'amp_is_legacy' ) ) {
+ // Available since AMP v2.0, this will return false if a theme like Twenty Twenty is selected as the Reader theme.
+ return amp_is_legacy();
+ }
+ if ( method_exists( 'AMP_Options_Manager', 'get_option' ) ) {
+ // In versions prior to v2.0, checking the template mode as being 'reader' is sufficient.
+ return 'reader' === AMP_Options_Manager::get_option( 'theme_support' );
+ }
+ return false;
+ }
+
+ /**
+ * Load AMP specific hooks.
+ *
+ * @return void
+ */
+ public function amp_load_hooks() {
+ if ( $this->is_exempted_amp_page() ) {
+ return;
+ }
+
+ if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
+ $template = self::get_settings()->render;
+
+ add_filter( 'jetpack_infinite_scroll_load_scripts_and_styles', '__return_false' );
+
+ add_action( 'template_redirect', array( $this, 'amp_start_output_buffering' ), 0 );
+ add_action( 'shutdown', array( $this, 'amp_output_buffer' ), 1 );
+
+ if ( is_callable( "amp_{$template}_hooks" ) ) {
+ call_user_func( "amp_{$template}_hooks" );
+ }
+
+ // Warms up the amp next page markup.
+ // This should be done outside the output buffering callback started in the template_redirect.
+ $this->amp_get_footer_template();
+ }
+ }
+
+ /**
+ * Start the AMP output buffering.
+ *
+ * @return void
+ */
+ public function amp_start_output_buffering() {
+ ob_start( array( $this, 'amp_finish_output_buffering' ) );
+ }
+
+ /**
+ * Flush the AMP output buffer.
+ *
+ * @return void
+ */
+ public function amp_output_buffer() {
+ if ( ob_get_contents() ) {
+ ob_end_flush();
+ }
+ }
+
+ /**
+ * Filter the AMP output buffer contents.
+ *
+ * @param string $buffer Contents of the output buffer.
+ *
+ * @return string|false
+ */
+ public function amp_finish_output_buffering( $buffer ) {
+ // Hide WordPress admin bar on next page load.
+ $buffer = preg_replace(
+ '/id="wpadminbar"/',
+ '$0 next-page-hide',
+ $buffer
+ );
+
+ /**
+ * Get the theme footers.
+ *
+ * @module infinite-scroll
+ *
+ * @since 9.0.0
+ *
+ * @param array array() An array to store multiple markup entries to be added to the footer.
+ * @param string $buffer The contents of the output buffer.
+ */
+ $footers = apply_filters( 'jetpack_amp_infinite_footers', array(), $buffer );
+
+ /**
+ * Filter the output buffer.
+ * Themes can leverage this hook to add custom markup on next page load.
+ *
+ * @module infinite-scroll
+ *
+ * @since 9.0.0
+ *
+ * @param string $buffer The contents of the output buffer.
+ */
+ $buffer = apply_filters( 'jetpack_amp_infinite_output', $buffer );
+
+ // Add the amp next page markup.
+ $buffer = preg_replace(
+ '~</body>~',
+ $this->amp_get_footer_template( $footers ) . '$0',
+ $buffer
+ );
+
+ return $buffer;
+ }
+
+ /**
+ * Get AMP next page markup with the custom footers.
+ *
+ * @param string[] $footers The theme footers.
+ *
+ * @return string
+ */
+ protected function amp_get_footer_template( $footers = array() ) {
+ static $template = null;
+
+ if ( null === $template ) {
+ $template = $this->amp_footer_template();
+ }
+
+ if ( empty( $footers ) ) {
+ return $template;
+ }
+
+ return preg_replace(
+ '/%%footer%%/',
+ implode( '', $footers ),
+ $template
+ );
+ }
+
+ /**
+ * AMP Next Page markup.
+ *
+ * @return string
+ */
+ protected function amp_footer_template() {
+ ob_start();
+ ?>
+<amp-next-page max-pages="<?php echo esc_attr( $this->amp_get_max_pages() ); ?>">
+ <script type="application/json">
+ [
+ <?php echo wp_json_encode( $this->amp_next_page() ); ?>
+ ]
+ </script>
+ <div separator>
+ <?php
+ echo wp_kses_post(
+ /**
+ * AMP infinite scroll separator.
+ *
+ * @module infinite-scroll
+ *
+ * @since 9.0.0
+ *
+ * @param string '' The markup for the next page separator.
+ */
+ apply_filters( 'jetpack_amp_infinite_separator', '' )
+ );
+ ?>
+ </div>
+ <div recommendation-box class="recommendation-box">
+ <template type="amp-mustache">
+ {{#pages}}
+ <?php
+ echo wp_kses_post(
+ /**
+ * AMP infinite scroll older posts markup.
+ *
+ * @module infinite-scroll
+ *
+ * @since 9.0.0
+ *
+ * @param string '' The markup for the older posts/next page.
+ */
+ apply_filters( 'jetpack_amp_infinite_older_posts', '' )
+ );
+ ?>
+ {{/pages}}
+ </template>
+ </div>
+ <div footer>
+ %%footer%%
+ </div>
+</amp-next-page>
+ <?php
+ return ob_get_clean();
+ }
+
+ /**
+ * Get the AMP next page information.
+ *
+ * @return array
+ */
+ protected function amp_next_page() {
+ $title = '';
+ $url = '';
+ $image = '';
+
+ if ( ! static::amp_is_last_page() ) {
+ $title = sprintf(
+ '%s - %s %d - %s',
+ wp_title( '', false ),
+ __( 'Page', 'jetpack' ),
+ max( get_query_var( 'paged', 1 ), 1 ) + 1,
+ get_bloginfo( 'name' )
+ );
+ $url = get_next_posts_page_link();
+ }
+
+ $next_page = array(
+ 'title' => $title,
+ 'url' => $url,
+ 'image' => $image,
+ );
+
+ /**
+ * The next page settings.
+ * An array containing:
+ * - title => The title to be featured on the browser tab.
+ * - url => The URL of next page.
+ * - image => The image URL. A required AMP setting, not in use currently. Themes are welcome to leverage.
+ *
+ * @module infinite-scroll
+ *
+ * @since 9.0.0
+ *
+ * @param array $next_page The contents of the output buffer.
+ */
+ return apply_filters( 'jetpack_amp_infinite_next_page_data', $next_page );
+ }
+
+ /**
+ * Get the number of pages left.
+ *
+ * @return int
+ */
+ protected static function amp_get_max_pages() {
+ global $wp_query;
+
+ return (int) $wp_query->max_num_pages - $wp_query->query_vars['paged'];
+ }
+
+ /**
+ * Is the last page.
+ *
+ * @return bool
+ */
+ protected static function amp_is_last_page() {
+ return 0 === static::amp_get_max_pages();
+ }
};
/**
diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css b/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css
index cc232785..9f2612e5 100644
--- a/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css
+++ b/plugins/jetpack/modules/infinite-scroll/themes/twentyeleven.css
@@ -16,7 +16,7 @@
padding-top: 0;
}
.infinite-scroll .infinite-wrap .hentry:last-child {
- border-bottom: 1px solid #ddd;
+ border-bottom: 1px solid #dcdcde;
}
.infinite-scroll .infinite-wrap:last-of-type .hentry:last-child {
border-bottom: none;
@@ -42,4 +42,4 @@
.infinite-scroll #infinite-handle {
padding-bottom: 40px;
}
-} \ No newline at end of file
+}
diff --git a/plugins/jetpack/modules/infinite-scroll/themes/twentyfourteen.php b/plugins/jetpack/modules/infinite-scroll/themes/twentyfourteen.php
index 54a1fbc8..c9710abd 100644
--- a/plugins/jetpack/modules/infinite-scroll/themes/twentyfourteen.php
+++ b/plugins/jetpack/modules/infinite-scroll/themes/twentyfourteen.php
@@ -5,6 +5,8 @@
* Register support for Twenty Fourteen.
*/
+use Automattic\Jetpack\Device_Detection\User_Agent_Info;
+
/**
* Add theme support for infinite scroll
*/
@@ -27,7 +29,7 @@ add_action( 'after_setup_theme', 'jetpack_twentyfourteen_infinite_scroll_init' )
*/
function jetpack_twentyfourteen_has_footer_widgets() {
if ( function_exists( 'jetpack_is_mobile' ) ) {
- if ( ( Jetpack_User_Agent_Info::is_ipad() && is_active_sidebar( 'sidebar-1' ) )
+ if ( ( User_Agent_Info::is_ipad() && is_active_sidebar( 'sidebar-1' ) )
|| ( jetpack_is_mobile( '', true ) && ( is_active_sidebar( 'sidebar-1' ) || is_active_sidebar( 'sidebar-2' ) ) )
|| is_active_sidebar( 'sidebar-3' ) )