| 1 |
(function($){ // Open closure |
| 2 |
// Local vars |
| 3 |
var Scroller, ajaxurl, stats, type, text, totop; |
| 4 |
|
| 5 |
// IE requires special handling |
| 6 |
var isIE = ( -1 != navigator.userAgent.search( 'MSIE' ) ); |
| 7 |
if ( isIE ) { |
| 8 |
var IEVersion = navigator.userAgent.match(/MSIE\s?(\d+)\.?\d*;/); |
| 9 |
var IEVersion = parseInt( IEVersion[1] ); |
| 10 |
} |
| 11 |
|
| 12 |
// HTTP ajaxurl when site is HTTPS causes Access-Control-Allow-Origin failure in Desktop and iOS Safari |
| 13 |
if ( "https:" == document.location.protocol ) { |
| 14 |
infiniteScroll.settings.ajaxurl = infiniteScroll.settings.ajaxurl.replace( "http://", "https://" ); |
| 15 |
} |
| 16 |
|
| 17 |
/** |
| 18 |
* Loads new posts when users scroll near the bottom of the page. |
| 19 |
*/ |
| 20 |
Scroller = function( settings ) { |
| 21 |
var self = this; |
| 22 |
|
| 23 |
// Initialize our variables |
| 24 |
this.id = settings.id; |
| 25 |
this.body = $( document.body ); |
| 26 |
this.window = $( window ); |
| 27 |
this.element = $( '#' + settings.id ); |
| 28 |
this.wrapperClass = settings.wrapper_class; |
| 29 |
this.ready = true; |
| 30 |
this.disabled = false; |
| 31 |
this.page = 1; |
| 32 |
this.offset = settings.offset; |
| 33 |
this.currentday = settings.currentday; |
| 34 |
this.order = settings.order; |
| 35 |
this.throttle = false; |
| 36 |
this.handle = '<div id="infinite-handle"><span><button>' + text.replace( '\\', '' ) + '</button></span></div>'; |
| 37 |
this.click_handle = settings.click_handle; |
| 38 |
this.google_analytics = settings.google_analytics; |
| 39 |
this.history = settings.history; |
| 40 |
this.origURL = window.location.href; |
| 41 |
this.pageCache = {}; |
| 42 |
|
| 43 |
// Footer settings |
| 44 |
this.footer = $( '#infinite-footer' ); |
| 45 |
this.footer.wrap = settings.footer; |
| 46 |
|
| 47 |
// Core's native MediaElement.js implementation needs special handling |
| 48 |
this.wpMediaelement = null; |
| 49 |
|
| 50 |
// We have two type of infinite scroll |
| 51 |
// cases 'scroll' and 'click' |
| 52 |
|
| 53 |
if ( type == 'scroll' ) { |
| 54 |
// Bind refresh to the scroll event |
| 55 |
// Throttle to check for such case every 300ms |
| 56 |
|
| 57 |
// On event the case becomes a fact |
| 58 |
this.window.bind( 'scroll.infinity', function() { |
| 59 |
this.throttle = true; |
| 60 |
}); |
| 61 |
|
| 62 |
// Go back top method |
| 63 |
self.gotop(); |
| 64 |
|
| 65 |
setInterval( function() { |
| 66 |
if ( this.throttle ) { |
| 67 |
// Once the case is the case, the action occurs and the fact is no more |
| 68 |
this.throttle = false; |
| 69 |
// Reveal or hide footer |
| 70 |
self.thefooter(); |
| 71 |
// Fire the refresh |
| 72 |
self.refresh(); |
| 73 |
self.determineURL(); // determine the url |
| 74 |
} |
| 75 |
}, 250 ); |
| 76 |
|
| 77 |
// Ensure that enough posts are loaded to fill the initial viewport, to compensate for short posts and large displays. |
| 78 |
self.ensureFilledViewport(); |
| 79 |
this.body.bind( 'post-load', { self: self }, self.checkViewportOnLoad ); |
| 80 |
} else if ( type == 'click' ) { |
| 81 |
if ( this.click_handle ) { |
| 82 |
this.element.append( this.handle ); |
| 83 |
} |
| 84 |
|
| 85 |
this.body.delegate( '#infinite-handle', 'click.infinity', function() { |
| 86 |
// Handle the handle |
| 87 |
if ( self.click_handle ) { |
| 88 |
$( '#infinite-handle' ).remove(); |
| 89 |
} |
| 90 |
|
| 91 |
// Fire the refresh |
| 92 |
self.refresh(); |
| 93 |
}); |
| 94 |
} |
| 95 |
|
| 96 |
// Initialize any Core audio or video players loaded via IS |
| 97 |
this.body.bind( 'post-load', { self: self }, self.initializeMejs ); |
| 98 |
}; |
| 99 |
|
| 100 |
/** |
| 101 |
* Check whether we should fetch any additional posts. |
| 102 |
*/ |
| 103 |
Scroller.prototype.check = function() { |
| 104 |
var container = this.element.offset(); |
| 105 |
|
| 106 |
// If the container can't be found, stop otherwise errors result |
| 107 |
if ( 'object' !== typeof container ) { |
| 108 |
return false; |
| 109 |
} |
| 110 |
|
| 111 |
var bottom = this.window.scrollTop() + this.window.height(), |
| 112 |
threshold = container.top + this.element.outerHeight(false) - (this.window.height() * 2); |
| 113 |
|
| 114 |
return bottom > threshold; |
| 115 |
}; |
| 116 |
|
| 117 |
/** |
| 118 |
* Renders the results from a successful response. |
| 119 |
*/ |
| 120 |
Scroller.prototype.render = function( response ) { |
| 121 |
this.body.addClass( 'infinity-success' ); |
| 122 |
|
| 123 |
// Check if we can wrap the html |
| 124 |
this.element.append( response.html ); |
| 125 |
this.body.trigger( 'post-load', response ); |
| 126 |
this.ready = true; |
| 127 |
}; |
| 128 |
|
| 129 |
/** |
| 130 |
* Returns the object used to query for new posts. |
| 131 |
*/ |
| 132 |
Scroller.prototype.query = function() { |
| 133 |
return { |
| 134 |
page : this.page + this.offset, // Load the next page. |
| 135 |
currentday : this.currentday, |
| 136 |
order : this.order, |
| 137 |
scripts : window.infiniteScroll.settings.scripts, |
| 138 |
styles : window.infiniteScroll.settings.styles, |
| 139 |
query_args : window.infiniteScroll.settings.query_args, |
| 140 |
query_before : window.infiniteScroll.settings.query_before, |
| 141 |
last_post_date: window.infiniteScroll.settings.last_post_date |
| 142 |
}; |
| 143 |
}; |
| 144 |
|
| 145 |
/** |
| 146 |
* Scroll back to top. |
| 147 |
*/ |
| 148 |
Scroller.prototype.gotop = function() { |
| 149 |
var blog = $( '#infinity-blog-title' ); |
| 150 |
|
| 151 |
blog.attr( 'title', totop ); |
| 152 |
|
| 153 |
// Scroll to top on blog title |
| 154 |
blog.bind( 'click', function( e ) { |
| 155 |
$( 'html, body' ).animate( { scrollTop: 0 }, 'fast' ); |
| 156 |
e.preventDefault(); |
| 157 |
}); |
| 158 |
}; |
| 159 |
|
| 160 |
|
| 161 |
/** |
| 162 |
* The infinite footer. |
| 163 |
*/ |
| 164 |
Scroller.prototype.thefooter = function() { |
| 165 |
var self = this, |
| 166 |
width; |
| 167 |
|
| 168 |
// Check if we have an id for the page wrapper |
| 169 |
if ( $.type( this.footer.wrap ) === "string" ) { |
| 170 |
width = $( 'body #' + this.footer.wrap ).outerWidth( false ); |
| 171 |
|
| 172 |
// Make the footer match the width of the page |
| 173 |
if ( width > 479 ) |
| 174 |
this.footer.find( '.container' ).css( 'width', width ); |
| 175 |
} |
| 176 |
|
| 177 |
// Reveal footer |
| 178 |
if ( this.window.scrollTop() >= 350 ) |
| 179 |
self.footer.animate( { 'bottom': 0 }, 'fast' ); |
| 180 |
else if ( this.window.scrollTop() < 350 ) |
| 181 |
self.footer.animate( { 'bottom': '-50px' }, 'fast' ); |
| 182 |
}; |
| 183 |
|
| 184 |
|
| 185 |
/** |
| 186 |
* Controls the flow of the refresh. Don't mess. |
| 187 |
*/ |
| 188 |
Scroller.prototype.refresh = function() { |
| 189 |
var self = this, |
| 190 |
query, jqxhr, load, loader, color, customized; |
| 191 |
|
| 192 |
// If we're disabled, ready, or don't pass the check, bail. |
| 193 |
if ( this.disabled || ! this.ready || ! this.check() ) |
| 194 |
return; |
| 195 |
|
| 196 |
// Let's get going -- set ready to false to prevent |
| 197 |
// multiple refreshes from occurring at once. |
| 198 |
this.ready = false; |
| 199 |
|
| 200 |
// Create a loader element to show it's working. |
| 201 |
if ( this.click_handle ) { |
| 202 |
loader = '<span class="infinite-loader"></span>'; |
| 203 |
this.element.append( loader ); |
| 204 |
|
| 205 |
loader = this.element.find( '.infinite-loader' ); |
| 206 |
color = loader.css( 'color' ); |
| 207 |
|
| 208 |
try { |
| 209 |
loader.spin( 'medium-left', color ); |
| 210 |
} catch ( error ) { } |
| 211 |
} |
| 212 |
|
| 213 |
// Generate our query vars. |
| 214 |
query = $.extend({ |
| 215 |
action: 'infinite_scroll' |
| 216 |
}, this.query() ); |
| 217 |
|
| 218 |
// Inject Customizer state. |
| 219 |
if ( 'undefined' !== typeof wp && wp.customize && wp.customize.settings.theme ) { |
| 220 |
customized = {}; |
| 221 |
query.wp_customize = 'on'; |
| 222 |
query.theme = wp.customize.settings.theme.stylesheet; |
| 223 |
wp.customize.each( function( setting ) { |
| 224 |
if ( setting._dirty ) { |
| 225 |
customized[ setting.id ] = setting(); |
| 226 |
} |
| 227 |
} ); |
| 228 |
query.customized = JSON.stringify( customized ); |
| 229 |
query.nonce = wp.customize.settings.nonce.preview; |
| 230 |
} |
| 231 |
|
| 232 |
// Fire the ajax request. |
| 233 |
jqxhr = $.post( infiniteScroll.settings.ajaxurl, query ); |
| 234 |
|
| 235 |
// Allow refreshes to occur again if an error is triggered. |
| 236 |
jqxhr.fail( function() { |
| 237 |
if ( self.click_handle ) { |
| 238 |
loader.hide(); |
| 239 |
} |
| 240 |
|
| 241 |
self.ready = true; |
| 242 |
}); |
| 243 |
|
| 244 |
// Success handler |
| 245 |
jqxhr.done( function( response ) { |
| 246 |
// On success, let's hide the loader circle. |
| 247 |
if ( self.click_handle ) { |
| 248 |
loader.hide(); |
| 249 |
} |
| 250 |
|
| 251 |
// Check for and parse our response. |
| 252 |
if ( ! response || ! response.type ) { |
| 253 |
return; |
| 254 |
} |
| 255 |
|
| 256 |
// If we've succeeded... |
| 257 |
if ( response.type == 'success' ) { |
| 258 |
// If additional scripts are required by the incoming set of posts, parse them |
| 259 |
if ( response.scripts ) { |
| 260 |
$( response.scripts ).each( function() { |
| 261 |
var elementToAppendTo = this.footer ? 'body' : 'head'; |
| 262 |
|
| 263 |
// Add script handle to list of those already parsed |
| 264 |
window.infiniteScroll.settings.scripts.push( this.handle ); |
| 265 |
|
| 266 |
// Output extra data, if present |
| 267 |
if ( this.extra_data ) { |
| 268 |
var data = document.createElement('script'), |
| 269 |
dataContent = document.createTextNode( "//<![CDATA[ \n" + this.extra_data + "\n//]]>" ); |
| 270 |
|
| 271 |
data.type = 'text/javascript'; |
| 272 |
data.appendChild( dataContent ); |
| 273 |
|
| 274 |
document.getElementsByTagName( elementToAppendTo )[0].appendChild(data); |
| 275 |
} |
| 276 |
|
| 277 |
// Build script tag and append to DOM in requested location |
| 278 |
var script = document.createElement('script'); |
| 279 |
script.type = 'text/javascript'; |
| 280 |
script.src = this.src; |
| 281 |
script.id = this.handle; |
| 282 |
|
| 283 |
// If MediaElement.js is loaded in by this set of posts, don't initialize the players a second time as it breaks them all |
| 284 |
if ( 'wp-mediaelement' === this.handle ) { |
| 285 |
self.body.unbind( 'post-load', self.initializeMejs ); |
| 286 |
} |
| 287 |
|
| 288 |
if ( 'wp-mediaelement' === this.handle && 'undefined' === typeof mejs ) { |
| 289 |
self.wpMediaelement = {}; |
| 290 |
self.wpMediaelement.tag = script; |
| 291 |
self.wpMediaelement.element = elementToAppendTo; |
| 292 |
setTimeout( self.maybeLoadMejs.bind( self ), 250 ); |
| 293 |
} else { |
| 294 |
document.getElementsByTagName( elementToAppendTo )[0].appendChild(script); |
| 295 |
} |
| 296 |
} ); |
| 297 |
} |
| 298 |
|
| 299 |
// If additional stylesheets are required by the incoming set of posts, parse them |
| 300 |
if ( response.styles ) { |
| 301 |
$( response.styles ).each( function() { |
| 302 |
// Add stylesheet handle to list of those already parsed |
| 303 |
window.infiniteScroll.settings.styles.push( this.handle ); |
| 304 |
|
| 305 |
// Build link tag |
| 306 |
var style = document.createElement('link'); |
| 307 |
style.rel = 'stylesheet'; |
| 308 |
style.href = this.src; |
| 309 |
style.id = this.handle + '-css'; |
| 310 |
|
| 311 |
// Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true |
| 312 |
if ( this.conditional && ( ! isIE || ! eval( this.conditional.replace( /%ver/g, IEVersion ) ) ) ) |
| 313 |
var style = false; |
| 314 |
|
| 315 |
// Append link tag if necessary |
| 316 |
if ( style ) |
| 317 |
document.getElementsByTagName('head')[0].appendChild(style); |
| 318 |
} ); |
| 319 |
} |
| 320 |
|
| 321 |
// stash the response in the page cache |
| 322 |
self.pageCache[self.page+self.offset] = response; |
| 323 |
|
| 324 |
// Increment the page number |
| 325 |
self.page++; |
| 326 |
|
| 327 |
// Record pageview in WP Stats, if available. |
| 328 |
if ( stats ) |
| 329 |
new Image().src = document.location.protocol + '//pixel.wp.com/g.gif?' + stats + '&post=0&baba=' + Math.random(); |
| 330 |
|
| 331 |
// Add new posts to the postflair object |
| 332 |
if ( 'object' == typeof response.postflair && 'object' == typeof WPCOM_sharing_counts ) |
| 333 |
WPCOM_sharing_counts = $.extend( WPCOM_sharing_counts, response.postflair ); |
| 334 |
|
| 335 |
// Render the results |
| 336 |
self.render.apply( self, arguments ); |
| 337 |
|
| 338 |
// If 'click' type and there are still posts to fetch, add back the handle |
| 339 |
if ( type == 'click' ) { |
| 340 |
if ( response.lastbatch ) { |
| 341 |
if ( self.click_handle ) { |
| 342 |
$( '#infinite-handle' ).remove(); |
| 343 |
// Update body classes |
| 344 |
self.body.addClass( 'infinity-end' ).removeClass( 'infinity-success' ); |
| 345 |
} else { |
| 346 |
self.body.trigger( 'infinite-scroll-posts-end' ); |
| 347 |
} |
| 348 |
} else { |
| 349 |
if ( self.click_handle ) { |
| 350 |
self.element.append( self.handle ); |
| 351 |
} else { |
| 352 |
self.body.trigger( 'infinite-scroll-posts-more' ); |
| 353 |
} |
| 354 |
} |
| 355 |
} else if ( response.lastbatch ) { |
| 356 |
self.disabled = true; |
| 357 |
self.body.addClass( 'infinity-end' ).removeClass( 'infinity-success' ); |
| 358 |
} |
| 359 |
|
| 360 |
// Update currentday to the latest value returned from the server |
| 361 |
if ( response.currentday ) { |
| 362 |
self.currentday = response.currentday; |
| 363 |
} |
| 364 |
|
| 365 |
// Fire Google Analytics pageview |
| 366 |
if ( self.google_analytics ) { |
| 367 |
var ga_url = self.history.path.replace( /%d/, self.page ); |
| 368 |
if ( 'object' === typeof _gaq ) { |
| 369 |
_gaq.push( [ '_trackPageview', ga_url ] ); |
| 370 |
} |
| 371 |
if ( 'function' === typeof ga ) { |
| 372 |
ga( 'send', 'pageview', ga_url ); |
| 373 |
} |
| 374 |
} |
| 375 |
} |
| 376 |
}); |
| 377 |
|
| 378 |
return jqxhr; |
| 379 |
}; |
| 380 |
|
| 381 |
/** |
| 382 |
* Core's native media player uses MediaElement.js |
| 383 |
* 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. |
| 384 |
*/ |
| 385 |
Scroller.prototype.maybeLoadMejs = function() { |
| 386 |
if ( null === this.wpMediaelement ) { |
| 387 |
return; |
| 388 |
} |
| 389 |
|
| 390 |
if ( 'undefined' === typeof mejs ) { |
| 391 |
setTimeout( this.maybeLoadMejs, 250 ); |
| 392 |
} else { |
| 393 |
document.getElementsByTagName( this.wpMediaelement.element )[0].appendChild( this.wpMediaelement.tag ); |
| 394 |
this.wpMediaelement = null; |
| 395 |
|
| 396 |
// Ensure any subsequent IS loads initialize the players |
| 397 |
this.body.bind( 'post-load', { self: this }, this.initializeMejs ); |
| 398 |
} |
| 399 |
} |
| 400 |
|
| 401 |
/** |
| 402 |
* Initialize the MediaElement.js player for any posts not previously initialized |
| 403 |
*/ |
| 404 |
Scroller.prototype.initializeMejs = function( ev, response ) { |
| 405 |
// Are there media players in the incoming set of posts? |
| 406 |
if ( ! response.html || -1 === response.html.indexOf( 'wp-audio-shortcode' ) && -1 === response.html.indexOf( 'wp-video-shortcode' ) ) { |
| 407 |
return; |
| 408 |
} |
| 409 |
|
| 410 |
// Don't bother if mejs isn't loaded for some reason |
| 411 |
if ( 'undefined' === typeof mejs ) { |
| 412 |
return; |
| 413 |
} |
| 414 |
|
| 415 |
// Adapted from wp-includes/js/mediaelement/wp-mediaelement.js |
| 416 |
// Modified to not initialize already-initialized players, as Mejs doesn't handle that well |
| 417 |
$(function () { |
| 418 |
var settings = {}; |
| 419 |
|
| 420 |
if ( typeof _wpmejsSettings !== 'undefined' ) { |
| 421 |
settings.pluginPath = _wpmejsSettings.pluginPath; |
| 422 |
} |
| 423 |
|
| 424 |
settings.success = function (mejs) { |
| 425 |
var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay; |
| 426 |
if ( 'flash' === mejs.pluginType && autoplay ) { |
| 427 |
mejs.addEventListener( 'canplay', function () { |
| 428 |
mejs.play(); |
| 429 |
}, false ); |
| 430 |
} |
| 431 |
}; |
| 432 |
|
| 433 |
$('.wp-audio-shortcode, .wp-video-shortcode').not( '.mejs-container' ).mediaelementplayer( settings ); |
| 434 |
}); |
| 435 |
} |
| 436 |
|
| 437 |
/** |
| 438 |
* Trigger IS to load additional posts if the initial posts don't fill the window. |
| 439 |
* 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. |
| 440 |
*/ |
| 441 |
Scroller.prototype.ensureFilledViewport = function() { |
| 442 |
var self = this, |
| 443 |
windowHeight = self.window.height(), |
| 444 |
postsHeight = self.element.height(), |
| 445 |
aveSetHeight = 0, |
| 446 |
wrapperQty = 0; |
| 447 |
|
| 448 |
// Account for situations where postsHeight is 0 because child list elements are floated |
| 449 |
if ( postsHeight === 0 ) { |
| 450 |
$( self.element.selector + ' > li' ).each( function() { |
| 451 |
postsHeight += $( this ).height(); |
| 452 |
} ); |
| 453 |
|
| 454 |
if ( postsHeight === 0 ) { |
| 455 |
self.body.unbind( 'post-load', self.checkViewportOnLoad ); |
| 456 |
return; |
| 457 |
} |
| 458 |
} |
| 459 |
|
| 460 |
// Calculate average height of a set of posts to prevent more posts than needed from being loaded. |
| 461 |
$( '.' + self.wrapperClass ).each( function() { |
| 462 |
aveSetHeight += $( this ).height(); |
| 463 |
wrapperQty++; |
| 464 |
} ); |
| 465 |
|
| 466 |
if ( wrapperQty > 0 ) |
| 467 |
aveSetHeight = aveSetHeight / wrapperQty; |
| 468 |
else |
| 469 |
aveSetHeight = 0; |
| 470 |
|
| 471 |
// Load more posts if space permits, otherwise stop checking for a full viewport |
| 472 |
if ( postsHeight < windowHeight && ( postsHeight + aveSetHeight < windowHeight ) ) { |
| 473 |
self.ready = true; |
| 474 |
self.refresh(); |
| 475 |
} |
| 476 |
else { |
| 477 |
self.body.unbind( 'post-load', self.checkViewportOnLoad ); |
| 478 |
} |
| 479 |
} |
| 480 |
|
| 481 |
/** |
| 482 |
* Event handler for ensureFilledViewport(), tied to the post-load trigger. |
| 483 |
* 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. |
| 484 |
*/ |
| 485 |
Scroller.prototype.checkViewportOnLoad = function( ev ) { |
| 486 |
ev.data.self.ensureFilledViewport(); |
| 487 |
} |
| 488 |
|
| 489 |
function fullscreenState() { |
| 490 |
return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement |
| 491 |
? 1 |
| 492 |
: 0; |
| 493 |
} |
| 494 |
|
| 495 |
var previousFullScrenState = fullscreenState(); |
| 496 |
|
| 497 |
/** |
| 498 |
* Identify archive page that corresponds to majority of posts shown in the current browser window. |
| 499 |
*/ |
| 500 |
Scroller.prototype.determineURL = function () { |
| 501 |
var self = this, |
| 502 |
windowTop = $( window ).scrollTop(), |
| 503 |
windowBottom = windowTop + $( window ).height(), |
| 504 |
windowSize = windowBottom - windowTop, |
| 505 |
setsInView = [], |
| 506 |
setsHidden = [], |
| 507 |
pageNum = false, |
| 508 |
currentFullScreenState = fullscreenState(); |
| 509 |
|
| 510 |
// xor - check if the state has changed |
| 511 |
if ( previousFullScrenState ^ currentFullScreenState ) { |
| 512 |
// If we just switched to/from fullscreen, |
| 513 |
// don't do the div clearing/caching or the |
| 514 |
// URL setting. Doing so can break video playback |
| 515 |
// if the video goes to fullscreen. |
| 516 |
|
| 517 |
previousFullScrenState = currentFullScreenState; |
| 518 |
return; |
| 519 |
} |
| 520 |
previousFullScrenState = currentFullScreenState; |
| 521 |
|
| 522 |
// Find out which sets are in view |
| 523 |
$( '.' + self.wrapperClass ).each( function() { |
| 524 |
var id = $( this ).attr( 'id' ), |
| 525 |
setTop = $( this ).offset().top, |
| 526 |
setHeight = $( this ).outerHeight( false ), |
| 527 |
setBottom = 0, |
| 528 |
setPageNum = $( this ).data( 'page-num' ); |
| 529 |
|
| 530 |
// Account for containers that have no height because their children are floated elements. |
| 531 |
if ( 0 === setHeight ) { |
| 532 |
$( '> *', this ).each( function() { |
| 533 |
setHeight += $( this ).outerHeight( false ); |
| 534 |
} ); |
| 535 |
} |
| 536 |
|
| 537 |
// Determine position of bottom of set by adding its height to the scroll position of its top. |
| 538 |
setBottom = setTop + setHeight; |
| 539 |
|
| 540 |
// Populate setsInView object. While this logic could all be combined into a single conditional statement, this is easier to understand. |
| 541 |
if ( setTop < windowTop && setBottom > windowBottom ) { // top of set is above window, bottom is below |
| 542 |
setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum }); |
| 543 |
} |
| 544 |
else if( setTop > windowTop && setTop < windowBottom ) { // top of set is between top (gt) and bottom (lt) |
| 545 |
setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum }); |
| 546 |
} |
| 547 |
else if( setBottom > windowTop && setBottom < windowBottom ) { // bottom of set is between top (gt) and bottom (lt) |
| 548 |
setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum }); |
| 549 |
} else { |
| 550 |
setsHidden.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum }); |
| 551 |
} |
| 552 |
} ); |
| 553 |
|
| 554 |
$.each(setsHidden, function() { |
| 555 |
var $set = $('#' + this.id); |
| 556 |
if( $set.hasClass( 'is--replaced' ) ) { |
| 557 |
return; |
| 558 |
} |
| 559 |
|
| 560 |
self.pageCache[ this.pageNum].html = $set.html(); |
| 561 |
|
| 562 |
$set.css('min-height', ( this.bottom - this.top ) + 'px' ) |
| 563 |
.addClass('is--replaced') |
| 564 |
.empty(); |
| 565 |
}); |
| 566 |
|
| 567 |
$.each(setsInView, function() { |
| 568 |
var $set = $('#' + this.id); |
| 569 |
|
| 570 |
if( $set.hasClass('is--replaced') ) { |
| 571 |
$set.css('min-height', '').removeClass('is--replaced'); |
| 572 |
if( this.pageNum in self.pageCache ) { |
| 573 |
$set.html( self.pageCache[this.pageNum].html ); |
| 574 |
self.body.trigger( 'post-load', self.pageCache[this.pageNum] ); |
| 575 |
} |
| 576 |
} |
| 577 |
|
| 578 |
}); |
| 579 |
|
| 580 |
// 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. |
| 581 |
if ( 0 == setsInView.length ) { |
| 582 |
pageNum = -1; |
| 583 |
} |
| 584 |
else if ( 1 == setsInView.length ) { |
| 585 |
var setData = setsInView.pop(); |
| 586 |
|
| 587 |
// 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 |
| 588 |
if ( ( ( windowBottom - setData.top ) / windowSize ) < 0.5 ) |
| 589 |
pageNum = -1; |
| 590 |
else |
| 591 |
pageNum = setData.pageNum; |
| 592 |
} |
| 593 |
else { |
| 594 |
var majorityPercentageInView = 0; |
| 595 |
|
| 596 |
// Identify the IS set that comprises the majority of the current window and set the URL to it. |
| 597 |
$.each( setsInView, function( i, setData ) { |
| 598 |
var topInView = 0, |
| 599 |
bottomInView = 0, |
| 600 |
percentOfView = 0; |
| 601 |
|
| 602 |
// Figure percentage of view the current set represents |
| 603 |
if ( setData.top > windowTop && setData.top < windowBottom ) |
| 604 |
topInView = ( windowBottom - setData.top ) / windowSize; |
| 605 |
|
| 606 |
if ( setData.bottom > windowTop && setData.bottom < windowBottom ) |
| 607 |
bottomInView = ( setData.bottom - windowTop ) / windowSize; |
| 608 |
|
| 609 |
// Figure out largest percentage of view for current set |
| 610 |
if ( topInView >= bottomInView ) |
| 611 |
percentOfView = topInView; |
| 612 |
else if ( bottomInView >= topInView ) |
| 613 |
percentOfView = bottomInView; |
| 614 |
|
| 615 |
// Does current set's percentage of view supplant the largest previously-found set? |
| 616 |
if ( percentOfView > majorityPercentageInView ) { |
| 617 |
pageNum = setData.pageNum; |
| 618 |
majorityPercentageInView = percentOfView; |
| 619 |
} |
| 620 |
} ); |
| 621 |
} |
| 622 |
|
| 623 |
// If a page number could be determined, update the URL |
| 624 |
// -1 indicates that the original requested URL should be used. |
| 625 |
if ( 'number' == typeof pageNum ) { |
| 626 |
self.updateURL( pageNum ); |
| 627 |
} |
| 628 |
} |
| 629 |
|
| 630 |
/** |
| 631 |
* Update address bar to reflect archive page URL for a given page number. |
| 632 |
* Checks if URL is different to prevent pollution of browser history. |
| 633 |
*/ |
| 634 |
Scroller.prototype.updateURL = function( page ) { |
| 635 |
// IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met. |
| 636 |
if ( ! window.history.pushState ) { |
| 637 |
return; |
| 638 |
} |
| 639 |
var self = this, |
| 640 |
pageSlug = -1 == page ? self.origURL : window.location.protocol + '//' + self.history.host + self.history.path.replace( /%d/, page ) + self.history.parameters; |
| 641 |
|
| 642 |
if ( window.location.href != pageSlug ) { |
| 643 |
history.pushState( null, null, pageSlug ); |
| 644 |
} |
| 645 |
} |
| 646 |
|
| 647 |
/** |
| 648 |
* Pause scrolling. |
| 649 |
*/ |
| 650 |
Scroller.prototype.pause = function() { |
| 651 |
this.disabled = true; |
| 652 |
}; |
| 653 |
|
| 654 |
/** |
| 655 |
* Resume scrolling. |
| 656 |
*/ |
| 657 |
Scroller.prototype.resume = function() { |
| 658 |
this.disabled = false; |
| 659 |
}; |
| 660 |
|
| 661 |
/** |
| 662 |
* Ready, set, go! |
| 663 |
*/ |
| 664 |
$( document ).ready( function() { |
| 665 |
// Check for our variables |
| 666 |
if ( 'object' != typeof infiniteScroll ) |
| 667 |
return; |
| 668 |
|
| 669 |
$( document.body ).addClass( infiniteScroll.settings.body_class ); |
| 670 |
|
| 671 |
// Set ajaxurl (for brevity) |
| 672 |
ajaxurl = infiniteScroll.settings.ajaxurl; |
| 673 |
|
| 674 |
// Set stats, used for tracking stats |
| 675 |
stats = infiniteScroll.settings.stats; |
| 676 |
|
| 677 |
// Define what type of infinity we have, grab text for click-handle |
| 678 |
type = infiniteScroll.settings.type; |
| 679 |
text = infiniteScroll.settings.text; |
| 680 |
totop = infiniteScroll.settings.totop; |
| 681 |
|
| 682 |
// Initialize the scroller (with the ID of the element from the theme) |
| 683 |
infiniteScroll.scroller = new Scroller( infiniteScroll.settings ); |
| 684 |
|
| 685 |
/** |
| 686 |
* Monitor user scroll activity to update URL to correspond to archive page for current set of IS posts |
| 687 |
*/ |
| 688 |
if( type == 'click' ) { |
| 689 |
var timer = null; |
| 690 |
$( window ).bind( 'scroll', function() { |
| 691 |
// run the real scroll handler once every 250 ms. |
| 692 |
if ( timer ) { return; } |
| 693 |
timer = setTimeout( function() { |
| 694 |
infiniteScroll.scroller.determineURL(); |
| 695 |
timer = null; |
| 696 |
} , 250 ); |
| 697 |
}); |
| 698 |
} |
| 699 |
|
| 700 |
// Integrate with Selective Refresh in the Customizer. |
| 701 |
if ( 'undefined' !== typeof wp && wp.customize && wp.customize.selectiveRefresh ) { |
| 702 |
|
| 703 |
/** |
| 704 |
* Handle rendering of selective refresh partials. |
| 705 |
* |
| 706 |
* Make sure that when a partial is rendered, the Jetpack post-load event |
| 707 |
* will be triggered so that any dynamic elements will be re-constructed, |
| 708 |
* such as ME.js elements, Photon replacements, social sharing, and more. |
| 709 |
* Note that this is applying here not strictly to posts being loaded. |
| 710 |
* If a widget contains a ME.js element and it is previewed via selective |
| 711 |
* refresh, the post-load would get triggered allowing any dynamic elements |
| 712 |
* therein to also be re-constructed. |
| 713 |
* |
| 714 |
* @param {wp.customize.selectiveRefresh.Placement} placement |
| 715 |
*/ |
| 716 |
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) { |
| 717 |
var content; |
| 718 |
if ( 'string' === typeof placement.addedContent ) { |
| 719 |
content = placement.addedContent; |
| 720 |
} else if ( placement.container ) { |
| 721 |
content = $( placement.container ).html(); |
| 722 |
} |
| 723 |
|
| 724 |
if ( content ) { |
| 725 |
$( document.body ).trigger( 'post-load', { html: content } ); |
| 726 |
} |
| 727 |
} ); |
| 728 |
|
| 729 |
/* |
| 730 |
* Add partials for posts added via infinite scroll. |
| 731 |
* |
| 732 |
* This is unnecessary when MutationObserver is supported by the browser |
| 733 |
* since then this will be handled by Selective Refresh in core. |
| 734 |
*/ |
| 735 |
if ( 'undefined' === typeof MutationObserver ) { |
| 736 |
$( document.body ).on( 'post-load', function( e, response ) { |
| 737 |
var rootElement = null; |
| 738 |
if ( response.html && -1 !== response.html.indexOf( 'data-customize-partial' ) ) { |
| 739 |
if ( infiniteScroll.settings.id ) { |
| 740 |
rootElement = $( '#' + infiniteScroll.settings.id ); |
| 741 |
} |
| 742 |
wp.customize.selectiveRefresh.addPartials( rootElement ); |
| 743 |
} |
| 744 |
} ); |
| 745 |
} |
| 746 |
} |
| 747 |
}); |
| 748 |
|
| 749 |
|
| 750 |
})(jQuery); // Close closure |
| 751 |
|