| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729 |
- (function($){ // Open closure
- // Local vars
- var Scroller, ajaxurl, stats, type, text, totop;
- // IE requires special handling
- var isIE = ( -1 != navigator.userAgent.search( 'MSIE' ) );
- if ( isIE ) {
- var IEVersion = navigator.userAgent.match(/MSIE\s?(\d+)\.?\d*;/);
- var IEVersion = parseInt( IEVersion[1] );
- }
- // HTTP ajaxurl when site is HTTPS causes Access-Control-Allow-Origin failure in Desktop and iOS Safari
- if ( "https:" == document.location.protocol ) {
- infiniteScroll.settings.ajaxurl = infiniteScroll.settings.ajaxurl.replace( "http://", "https://" );
- }
- /**
- * Loads new posts when users scroll near the bottom of the page.
- */
- 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.wrapperClass = settings.wrapper_class;
- this.ready = true;
- this.disabled = false;
- this.page = 1;
- this.offset = settings.offset;
- 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 = {};
- // Footer settings
- this.footer = $( '#infinite-footer' );
- this.footer.wrap = settings.footer;
- // Core's native MediaElement.js implementation needs special handling
- this.wpMediaelement = null;
- // We have two type of infinite scroll
- // cases 'scroll' and 'click'
- if ( type == 'scroll' ) {
- // Bind refresh to the scroll event
- // Throttle to check for such case every 300ms
- // On event the case becomes a fact
- this.window.bind( 'scroll.infinity', function() {
- this.throttle = true;
- });
- // Go back top method
- self.gotop();
- setInterval( function() {
- if ( this.throttle ) {
- // Once the case is the case, the action occurs and the fact is no more
- this.throttle = false;
- // Reveal or hide footer
- self.thefooter();
- // Fire the refresh
- self.refresh();
- self.determineURL(); // determine the url
- }
- }, 250 );
- // 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 );
- } else if ( type == 'click' ) {
- if ( this.click_handle ) {
- this.element.append( this.handle );
- }
- this.body.delegate( '#infinite-handle', 'click.infinity', function() {
- // Handle the handle
- if ( self.click_handle ) {
- $( '#infinite-handle' ).remove();
- }
- // Fire the refresh
- self.refresh();
- });
- }
- // Initialize any Core audio or video players loaded via IS
- this.body.bind( 'post-load', { self: self }, self.initializeMejs );
- };
- /**
- * Check whether we should fetch any additional posts.
- */
- Scroller.prototype.check = function() {
- var container = this.element.offset();
- // If the container can't be found, stop otherwise errors result
- if ( 'object' !== typeof container ) {
- return false;
- }
- var bottom = this.window.scrollTop() + this.window.height(),
- threshold = container.top + this.element.outerHeight(false) - (this.window.height() * 2);
- return bottom > threshold;
- };
- /**
- * Renders the results from a successful response.
- */
- Scroller.prototype.render = function( response ) {
- this.body.addClass( 'infinity-success' );
- // 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() {
- return {
- page : this.page + this.offset, // Load the next page.
- currentday : this.currentday,
- order : this.order,
- scripts : window.infiniteScroll.settings.scripts,
- styles : window.infiniteScroll.settings.styles,
- query_args : window.infiniteScroll.settings.query_args,
- query_before : window.infiniteScroll.settings.query_before,
- last_post_date: window.infiniteScroll.settings.last_post_date
- };
- };
- /**
- * Scroll back to top.
- */
- Scroller.prototype.gotop = function() {
- var blog = $( '#infinity-blog-title' );
- blog.attr( 'title', totop );
- // Scroll to top on blog title
- blog.bind( 'click', function( e ) {
- $( 'html, body' ).animate( { scrollTop: 0 }, 'fast' );
- e.preventDefault();
- });
- };
- /**
- * The infinite footer.
- */
- Scroller.prototype.thefooter = function() {
- var self = this,
- width;
- // Check if we have an id for the page wrapper
- if ( $.type( this.footer.wrap ) === "string" ) {
- width = $( 'body #' + this.footer.wrap ).outerWidth( false );
- // Make the footer match the width of the page
- if ( width > 479 )
- this.footer.find( '.container' ).css( 'width', width );
- }
- // 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' );
- };
- /**
- * Controls the flow of the refresh. Don't mess.
- */
- Scroller.prototype.refresh = function() {
- var self = this,
- query, jqxhr, load, loader, color, customized;
- // If we're disabled, ready, or don't pass the check, bail.
- if ( this.disabled || ! this.ready || ! this.check() )
- return;
- // Let's get going -- set ready to false to prevent
- // multiple refreshes from occurring at once.
- this.ready = false;
- // 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 ) { }
- }
- // Generate our query vars.
- query = $.extend({
- action: 'infinite_scroll'
- }, this.query() );
- // Inject Customizer state.
- if ( 'undefined' !== typeof wp && wp.customize && wp.customize.settings.theme ) {
- customized = {};
- query.wp_customize = 'on';
- query.theme = wp.customize.settings.theme.stylesheet;
- wp.customize.each( function( setting ) {
- if ( setting._dirty ) {
- customized[ setting.id ] = setting();
- }
- } );
- query.customized = JSON.stringify( customized );
- query.nonce = wp.customize.settings.nonce.preview;
- }
- // Fire the ajax request.
- jqxhr = $.post( infiniteScroll.settings.ajaxurl, query );
- // Allow refreshes to occur again if an error is triggered.
- jqxhr.fail( function() {
- if ( self.click_handle ) {
- loader.hide();
- }
- self.ready = true;
- });
- // Success handler
- jqxhr.done( function( response ) {
- // On success, let's hide the loader circle.
- if ( self.click_handle ) {
- loader.hide();
- }
- // Check for and parse our response.
- if ( ! response || ! response.type ) {
- return;
- }
- // 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);
- }
- } );
- }
- // 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 ) ) ) )
- var style = false;
- // Append link tag if necessary
- if ( style )
- document.getElementsByTagName('head')[0].appendChild(style);
- } );
- }
- // stash the response in the page cache
- self.pageCache[self.page+self.offset] = response;
- // 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 = $.extend( WPCOM_sharing_counts, response.postflair );
- // 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' );
- }
- } else {
- if ( self.click_handle ) {
- self.element.append( self.handle );
- } else {
- self.body.trigger( 'infinite-scroll-posts-more' );
- }
- }
- } else if ( response.lastbatch ) {
- self.disabled = true;
- self.body.addClass( 'infinity-end' ).removeClass( '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;
- };
- /**
- * 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() {
- if ( null === this.wpMediaelement ) {
- return;
- }
- if ( 'undefined' === typeof mejs ) {
- setTimeout( this.maybeLoadMejs, 250 );
- } else {
- document.getElementsByTagName( this.wpMediaelement.element )[0].appendChild( this.wpMediaelement.tag );
- this.wpMediaelement = null;
- // Ensure any subsequent IS loads initialize the players
- this.body.bind( 'post-load', { self: this }, this.initializeMejs );
- }
- }
- /**
- * Initialize the MediaElement.js player for any posts not previously initialized
- */
- Scroller.prototype.initializeMejs = function( ev, response ) {
- // 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' ) ) {
- return;
- }
- // Don't bother if mejs isn't loaded for some reason
- if ( 'undefined' === typeof mejs ) {
- return;
- }
- // 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 = {};
- 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 );
- }
- };
- $('.wp-audio-shortcode, .wp-video-shortcode').not( '.mejs-container' ).mediaelementplayer( 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.
- */
- 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;
- }
- }
- // 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++;
- } );
- if ( wrapperQty > 0 )
- aveSetHeight = aveSetHeight / wrapperQty;
- else
- aveSetHeight = 0;
- // Load more posts if space permits, otherwise stop checking for a full viewport
- if ( postsHeight < windowHeight && ( postsHeight + aveSetHeight < windowHeight ) ) {
- self.ready = true;
- self.refresh();
- }
- else {
- self.body.unbind( 'post-load', self.checkViewportOnLoad );
- }
- }
- /**
- * 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();
- }
- /**
- * Identify archive page that corresponds to majority of posts shown in the current browser window.
- */
- Scroller.prototype.determineURL = function () {
- var self = this,
- windowTop = $( window ).scrollTop(),
- windowBottom = windowTop + $( window ).height(),
- windowSize = windowBottom - windowTop,
- setsInView = [],
- setsHidden = [],
- pageNum = false;
- // 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 );
- } );
- }
- // 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 });
- }
- } );
- $.each(setsHidden, function() {
- var $set = $('#' + this.id);
- if( $set.hasClass( 'is--replaced' ) ) {
- return;
- }
- 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;
- }
- } );
- }
- // 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 );
- }
- }
- /**
- * 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 ) {
- // IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met.
- if ( ! window.history.pushState ) {
- return;
- }
- var self = this,
- pageSlug = -1 == page ? self.origURL : window.location.protocol + '//' + self.history.host + self.history.path.replace( /%d/, page ) + self.history.parameters;
- if ( window.location.href != pageSlug ) {
- history.pushState( null, null, pageSlug );
- }
- }
- /**
- * Pause scrolling.
- */
- Scroller.prototype.pause = function() {
- this.disabled = true;
- };
- /**
- * Resume scrolling.
- */
- Scroller.prototype.resume = function() {
- this.disabled = false;
- };
- /**
- * Ready, set, go!
- */
- $( document ).ready( function() {
- // Check for our variables
- if ( 'object' != typeof infiniteScroll )
- return;
- $( document.body ).addClass( infiniteScroll.settings.body_class );
- // Set ajaxurl (for brevity)
- ajaxurl = infiniteScroll.settings.ajaxurl;
- // Set stats, used for tracking stats
- stats = infiniteScroll.settings.stats;
- // Define what type of infinity we have, grab text for click-handle
- type = infiniteScroll.settings.type;
- text = infiniteScroll.settings.text;
- totop = infiniteScroll.settings.totop;
- // Initialize the scroller (with the ID of the element from the theme)
- infiniteScroll.scroller = new Scroller( infiniteScroll.settings );
- /**
- * Monitor user scroll activity to update URL to correspond to archive page for current set of IS posts
- */
- if( type == 'click' ) {
- var timer = null;
- $( window ).bind( 'scroll', function() {
- // run the real scroll handler once every 250 ms.
- if ( timer ) { return; }
- 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
|