PageSourceSearch

https://st-botolphs.org/wp-content/plugins/jetpack/modules/infinite-scroll/infinity-ver-20141016.js

js st-botolphs.org collected 2026-10-07 15:45:37 UTC 20,845 bytes, 660 lines download raw bytes

1(function($){ // Open closure
2// Local vars
3var Scroller, ajaxurl, stats, type, text, totop;
4
5// IE requires special handling
6var isIE = ( -1 != navigator.userAgent.search( 'MSIE' ) );
7if ( 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
13if ( "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 */
20Scroller = 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 */
103Scroller.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 */
120Scroller.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 */
132Scroller.prototype.query = function() {
133	return {
134		page           : this.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		last_post_date : window.infiniteScroll.settings.last_post_date,
141	};
142};
143
144/**
145 * Scroll back to top.
146 */
147Scroller.prototype.gotop = function() {
148	var blog = $( '#infinity-blog-title' );
149
150	blog.attr( 'title', totop );
151
152	// Scroll to top on blog title
153	blog.bind( 'click', function( e ) {
154		$( 'html, body' ).animate( { scrollTop: 0 }, 'fast' );
155		e.preventDefault();
156	});
157};
158
159
160/**
161 * The infinite footer.
162 */
163Scroller.prototype.thefooter = function() {
164	var self  = this,
165		width;
166
167	// Check if we have an id for the page wrapper
168	if ( $.type( this.footer.wrap ) === "string" ) {
169		width = $( 'body #' + this.footer.wrap ).outerWidth( false );
170
171		// Make the footer match the width of the page
172		if ( width > 479 )
173			this.footer.find( '.container' ).css( 'width', width );
174	}
175
176	// Reveal footer
177	if ( this.window.scrollTop() >= 350 )
178		self.footer.animate( { 'bottom': 0 }, 'fast' );
179	else if ( this.window.scrollTop() < 350 )
180		self.footer.animate( { 'bottom': '-50px' }, 'fast' );
181};
182
183
184/**
185 * Controls the flow of the refresh. Don't mess.
186 */
187Scroller.prototype.refresh = function() {
188	var	self   = this,
189		query, jqxhr, load, loader, color;
190
191	// If we're disabled, ready, or don't pass the check, bail.
192	if ( this.disabled || ! this.ready || ! this.check() )
193		return;
194
195	// Let's get going -- set ready to false to prevent
196	// multiple refreshes from occurring at once.
197	this.ready = false;
198
199	// Create a loader element to show it's working.
200	if ( this.click_handle ) {
201		loader = '<span class="infinite-loader"></span>';
202		this.element.append( loader );
203
204		loader = this.element.find( '.infinite-loader' );
205		color = loader.css( 'color' );
206
207		try {
208			loader.spin( 'medium-left', color );
209		} catch ( error ) { }
210	}
211
212	// Generate our query vars.
213	query = $.extend({
214		action: 'infinite_scroll'
215	}, this.query() );
216
217	// Fire the ajax request.
218	jqxhr = $.post( infiniteScroll.settings.ajaxurl, query );
219
220	// Allow refreshes to occur again if an error is triggered.
221	jqxhr.fail( function() {
222		if ( self.click_handle ) {
223			loader.hide();
224		}
225
226		self.ready = true;
227	});
228
229	// Success handler
230	jqxhr.done( function( response ) {
231			// On success, let's hide the loader circle.
232			if ( self.click_handle ) {
233				loader.hide();
234			}
235
236			// Check for and parse our response.
237			if ( ! response )
238				return;
239
240			response = $.parseJSON( response );
241
242			if ( ! response || ! response.type )
243				return;
244
245			// If there are no remaining posts...
246			if ( response.type == 'empty' ) {
247				// Disable the scroller.
248				self.disabled = true;
249				// Update body classes, allowing the footer to return to static positioning
250				self.body.addClass( 'infinity-end' ).removeClass( 'infinity-success' );
251
252			// If we've succeeded...
253			} else if ( response.type == 'success' ) {
254				// If additional scripts are required by the incoming set of posts, parse them
255				if ( response.scripts ) {
256					$( response.scripts ).each( function() {
257						var elementToAppendTo = this.footer ? 'body' : 'head';
258
259						// Add script handle to list of those already parsed
260						window.infiniteScroll.settings.scripts.push( this.handle );
261
262						// Output extra data, if present
263						if ( this.extra_data ) {
264							var data = document.createElement('script'),
265								dataContent = document.createTextNode( "//<![CDATA[ \n" + this.extra_data + "\n//]]>" );
266
267							data.type = 'text/javascript';
268							data.appendChild( dataContent );
269
270							document.getElementsByTagName( elementToAppendTo )[0].appendChild(data);
271						}
272
273						// Build script tag and append to DOM in requested location
274						var script = document.createElement('script');
275						script.type = 'text/javascript';
276						script.src = this.src;
277						script.id = this.handle;
278
279						// If MediaElement.js is loaded in by this set of posts, don't initialize the players a second time as it breaks them all
280						if ( 'wp-mediaelement' === this.handle ) {
281							self.body.unbind( 'post-load', self.initializeMejs );
282						}
283
284						if ( 'wp-mediaelement' === this.handle && 'undefined' === typeof mejs ) {
285							self.wpMediaelement = {};
286							self.wpMediaelement.tag = script;
287							self.wpMediaelement.element = elementToAppendTo;
288							setTimeout( self.maybeLoadMejs.bind( self ), 250 );
289						} else {
290							document.getElementsByTagName( elementToAppendTo )[0].appendChild(script);
291						}
292					} );
293				}
294
295				// If additional stylesheets are required by the incoming set of posts, parse them
296				if ( response.styles ) {
297					$( response.styles ).each( function() {
298						// Add stylesheet handle to list of those already parsed
299						window.infiniteScroll.settings.styles.push( this.handle );
300
301						// Build link tag
302						var style = document.createElement('link');
303						style.rel = 'stylesheet';
304						style.href = this.src;
305						style.id = this.handle + '-css';
306
307						// Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true
308						if ( this.conditional && ( ! isIE || ! eval( this.conditional.replace( /%ver/g, IEVersion ) ) ) )
309							var style = false;
310
311						// Append link tag if necessary
312						if ( style )
313							document.getElementsByTagName('head')[0].appendChild(style);
314					} );
315				}
316
317				// stash the response in the page cache
318               			self.pageCache[self.page] = response;
319
320				// Increment the page number
321				self.page++;
322
323				// Record pageview in WP Stats, if available.
324				if ( stats )
325					new Image().src = document.location.protocol + '../g.gif' + stats + '&post=0&baba=' + Math.random();
326
327				// Add new posts to the postflair object
328				if ( 'object' == typeof response.postflair && 'object' == typeof WPCOM_sharing_counts )
329					WPCOM_sharing_counts = $.extend( WPCOM_sharing_counts, response.postflair );
330
331				// Render the results
332				self.render.apply( self, arguments );
333
334				// If 'click' type and there are still posts to fetch, add back the handle
335				if ( type == 'click' ) {
336					if ( response.lastbatch ) {
337						if ( self.click_handle ) {
338							$( '#infinite-handle' ).remove();
339						} else {
340							self.body.trigger( 'infinite-scroll-posts-end' );
341						}
342					} else {
343						if ( self.click_handle ) {
344							self.element.append( self.handle );
345						} else {
346							self.body.trigger( 'infinite-scroll-posts-more' );
347						}
348					}
349				}
350
351				// Update currentday to the latest value returned from the server
352				if ( response.currentday ) {
353					self.currentday = response.currentday;
354				}
355
356				// Fire Google Analytics pageview
357				if ( self.google_analytics ) {
358					var ga_url = self.history.path.replace( /%d/, self.page );
359					if ( 'object' === typeof _gaq ) {
360						_gaq.push( [ '_trackPageview', ga_url ] );
361					}
362					if ( 'function' === typeof ga ) {
363						ga( 'send', 'pageview', ga_url );
364					}
365				}
366			}
367		});
368
369	return jqxhr;
370};
371
372/**
373 * Core's native media player uses MediaElement.js
374 * 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.
375 */
376Scroller.prototype.maybeLoadMejs = function() {
377	if ( null === this.wpMediaelement ) {
378		return;
379	}
380
381	if ( 'undefined' === typeof mejs ) {
382		setTimeout( this.maybeLoadMejs, 250 );
383	} else {
384		document.getElementsByTagName( this.wpMediaelement.element )[0].appendChild( this.wpMediaelement.tag );
385		this.wpMediaelement = null;
386
387		// Ensure any subsequent IS loads initialize the players
388		this.body.bind( 'post-load', { self: this }, this.initializeMejs );
389	}
390}
391
392/**
393 * Initialize the MediaElement.js player for any posts not previously initialized
394 */
395Scroller.prototype.initializeMejs = function( ev, response ) {
396	// Are there media players in the incoming set of posts?
397	if ( ! response.html || -1 === response.html.indexOf( 'wp-audio-shortcode' ) && -1 === response.html.indexOf( 'wp-video-shortcode' ) ) {
398		return;
399	}
400
401	// Don't bother if mejs isn't loaded for some reason
402	if ( 'undefined' === typeof mejs ) {
403		return;
404	}
405
406	// Adapted from wp-includes/js/mediaelement/wp-mediaelement.js
407	// Modified to not initialize already-initialized players, as Mejs doesn't handle that well
408	$(function () {
409		var settings = {};
410
411		if ( typeof _wpmejsSettings !== 'undefined' ) {
412			settings.pluginPath = _wpmejsSettings.pluginPath;
413		}
414
415		settings.success = function (mejs) {
416			var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
417			if ( 'flash' === mejs.pluginType && autoplay ) {
418				mejs.addEventListener( 'canplay', function () {
419					mejs.play();
420				}, false );
421			}
422		};
423
424		$('.wp-audio-shortcode, .wp-video-shortcode').not( '.mejs-container' ).mediaelementplayer( settings );
425	});
426}
427
428/**
429 * Trigger IS to load additional posts if the initial posts don't fill the window.
430 * 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.
431 */
432Scroller.prototype.ensureFilledViewport = function() {
433	var	self = this,
434	   	windowHeight = self.window.height(),
435	   	postsHeight = self.element.height(),
436	   	aveSetHeight = 0,
437	   	wrapperQty = 0;
438
439	// Account for situations where postsHeight is 0 because child list elements are floated
440	if ( postsHeight === 0 ) {
441		$( self.element.selector + ' > li' ).each( function() {
442			postsHeight += $( this ).height();
443		} );
444
445		if ( postsHeight === 0 ) {
446			self.body.unbind( 'post-load', self.checkViewportOnLoad );
447			return;
448		}
449	}
450
451	// Calculate average height of a set of posts to prevent more posts than needed from being loaded.
452	$( '.' + self.wrapperClass ).each( function() {
453		aveSetHeight += $( this ).height();
454		wrapperQty++;
455	} );
456
457	if ( wrapperQty > 0 )
458		aveSetHeight = aveSetHeight / wrapperQty;
459	else
460		aveSetHeight = 0;
461
462	// Load more posts if space permits, otherwise stop checking for a full viewport
463	if ( postsHeight < windowHeight && ( postsHeight + aveSetHeight < windowHeight ) ) {
464		self.ready = true;
465		self.refresh();
466	}
467	else {
468		self.body.unbind( 'post-load', self.checkViewportOnLoad );
469	}
470}
471
472/**
473 * Event handler for ensureFilledViewport(), tied to the post-load trigger.
474 * 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.
475 */
476Scroller.prototype.checkViewportOnLoad = function( ev ) {
477	ev.data.self.ensureFilledViewport();
478}
479
480/**
481 * Identify archive page that corresponds to majority of posts shown in the current browser window.
482 */
483Scroller.prototype.determineURL = function () {
484	var self         = this,
485		windowTop    = $( window ).scrollTop(),
486		windowBottom = windowTop + $( window ).height(),
487		windowSize   = windowBottom - windowTop,
488		setsInView   = [],
489		setsHidden   = [],
490		pageNum      = false;
491
492	// Find out which sets are in view
493	$( '.' + self.wrapperClass ).each( function() {
494		var id         = $( this ).attr( 'id' ),
495			setTop     = $( this ).offset().top,
496			setHeight  = $( this ).outerHeight( false ),
497			setBottom  = 0,
498			setPageNum = $( this ).data( 'page-num' );
499
500		// Account for containers that have no height because their children are floated elements.
501		if ( 0 === setHeight ) {
502			$( '> *', this ).each( function() {
503				setHeight += $( this ).outerHeight( false );
504			} );
505		}
506
507		// Determine position of bottom of set by adding its height to the scroll position of its top.
508		setBottom = setTop + setHeight;
509
510		// Populate setsInView object. While this logic could all be combined into a single conditional statement, this is easier to understand.
511		if ( setTop < windowTop && setBottom > windowBottom ) { // top of set is above window, bottom is below
512			setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum });
513		}
514		else if( setTop > windowTop && setTop < windowBottom ) { // top of set is between top (gt) and bottom (lt)
515			setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum });
516		}
517		else if( setBottom > windowTop && setBottom < windowBottom ) { // bottom of set is between top (gt) and bottom (lt)
518			setsInView.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum });
519		} else {
520			setsHidden.push({'id': id, 'top': setTop, 'bottom': setBottom, 'pageNum': setPageNum });
521		}
522	} );
523
524	$.each(setsHidden, function() {
525		var $set = $('#' + this.id);
526		if( $set.hasClass( 'is--replaced' ) ) {
527			return;
528		}
529
530	        self.pageCache[ this.pageNum].html = $set.html();
531
532		$set.css('min-height', ( this.bottom - this.top ) + 'px' )
533		    .addClass('is--replaced')
534		    .empty();
535	});
536
537	$.each(setsInView, function() {
538		var $set = $('#' + this.id);
539
540		if( $set.hasClass('is--replaced') ) {
541			$set.css('min-height', '').removeClass('is--replaced');
542			if( this.pageNum in self.pageCache ) {
543				$set.html( self.pageCache[this.pageNum].html );
544		        	self.body.trigger( 'post-load', self.pageCache[this.pageNum] );
545			}
546		}
547
548	});
549
550	// 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.
551	if ( 0 == setsInView.length ) {
552		pageNum = -1;
553	}
554	else if ( 1 == setsInView.length ) {
555		var setData = setsInView.pop();
556
557		// 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
558		if ( ( ( windowBottom - setData.top ) / windowSize ) < 0.5 )
559			pageNum = -1;
560		else
561			pageNum = setData.pageNum;
562	}
563	else {
564		var majorityPercentageInView = 0;
565
566		// Identify the IS set that comprises the majority of the current window and set the URL to it.
567		$.each( setsInView, function( i, setData ) {
568			var topInView     = 0,
569				bottomInView  = 0,
570				percentOfView = 0;
571
572			// Figure percentage of view the current set represents
573			if ( setData.top > windowTop && setData.top < windowBottom )
574				topInView = ( windowBottom - setData.top ) / windowSize;
575
576			if ( setData.bottom > windowTop && setData.bottom < windowBottom )
577				bottomInView = ( setData.bottom - windowTop ) / windowSize;
578
579			// Figure out largest percentage of view for current set
580			if ( topInView >= bottomInView )
581				percentOfView = topInView;
582			else if ( bottomInView >= topInView )
583				percentOfView = bottomInView;
584
585			// Does current set's percentage of view supplant the largest previously-found set?
586			if ( percentOfView > majorityPercentageInView ) {
587				pageNum = setData.pageNum;
588				majorityPercentageInView = percentOfView;
589			}
590		} );
591	}
592
593	// If a page number could be determined, update the URL
594	// -1 indicates that the original requested URL should be used.
595	if ( 'number' == typeof pageNum ) {
596		if ( pageNum != -1 )
597			pageNum++;
598
599		self.updateURL( pageNum );
600	}
601}
602
603/**
604 * Update address bar to reflect archive page URL for a given page number.
605 * Checks if URL is different to prevent pollution of browser history.
606 */
607Scroller.prototype.updateURL = function( page ) {
608	// IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met.
609	if ( ! window.history.pushState ) {
610		return;
611	}
612	var self = this,
613		offset = self.offset > 0 ? self.offset - 1 : 0,
614		pageSlug = -1 == page ? self.origURL : window.location.protocol + '//' + self.history.host + self.history.path.replace( /%d/, page + offset ) + self.history.parameters;
615
616	if ( window.location.href != pageSlug ) {
617		history.pushState( null, null, pageSlug );
618	}
619}
620
621/**
622 * Ready, set, go!
623 */
624$( document ).ready( function() {
625	// Check for our variables
626	if ( 'object' != typeof infiniteScroll )
627		return;
628
629	// Set ajaxurl (for brevity)
630	ajaxurl = infiniteScroll.settings.ajaxurl;
631
632	// Set stats, used for tracking stats
633	stats = infiniteScroll.settings.stats;
634
635	// Define what type of infinity we have, grab text for click-handle
636	type  = infiniteScroll.settings.type;
637	text  = infiniteScroll.settings.text;
638	totop = infiniteScroll.settings.totop;
639
640	// Initialize the scroller (with the ID of the element from the theme)
641	infiniteScroll.scroller = new Scroller( infiniteScroll.settings );
642
643	/**
644	 * Monitor user scroll activity to update URL to correspond to archive page for current set of IS posts
645	 */
646    if( type == 'click' ) {
647        var timer = null;
648        $( window ).bind( 'scroll', function() {
649            // run the real scroll handler once every 250 ms.
650            if ( timer ) { return; }
651            timer = setTimeout( function() {
652                infiniteScroll.scroller.determineURL();
653                timer = null;
654            } , 250 );
655        });
656    }
657});
658
659
660})(jQuery); // Close closure

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.