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/infinity.js
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/infinity.js')
-rw-r--r--plugins/jetpack/modules/infinite-scroll/infinity.js967
1 files changed, 540 insertions, 427 deletions
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