PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.9.5
Jetpack – WP Security, Backup, Speed, & Growth v12.9.5
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / infinite-scroll / infinity.js
jetpack / modules / infinite-scroll Last commit date
themes 4 years ago infinity-customizer.js 6 years ago infinity.css 5 years ago infinity.js 3 years ago infinity.php 2 years ago
infinity.js
922 lines
1 /* globals infiniteScroll, _wpmejsSettings, ga, _gaq, WPCOM_sharing_counts, MediaElementPlayer */
2 ( function () {
3 // Open closure.
4 // Local vars.
5 var Scroller, ajaxurl, stats, type, text, totop, loading_text;
6
7 // IE requires special handling
8 var isIE = -1 != navigator.userAgent.search( 'MSIE' );
9 if ( isIE ) {
10 var IEVersion = navigator.userAgent.match( /MSIE\s?(\d+)\.?\d*;/ );
11 IEVersion = parseInt( IEVersion[ 1 ] );
12 }
13
14 // HTTP ajaxurl when site is HTTPS causes Access-Control-Allow-Origin failure in Desktop and iOS Safari
15 if ( 'https:' == document.location.protocol ) {
16 infiniteScroll.settings.ajaxurl = infiniteScroll.settings.ajaxurl.replace(
17 'http://',
18 'https://'
19 );
20 }
21
22 /**
23 * Loads new posts when users scroll near the bottom of the page.
24 */
25 Scroller = function ( settings ) {
26 var self = this;
27
28 // Initialize our variables
29 this.id = settings.id;
30 this.body = document.body;
31 this.window = window;
32 this.element = document.getElementById( settings.id );
33 this.wrapperClass = settings.wrapper_class;
34 this.ready = true;
35 this.disabled = false;
36 this.page = 1;
37 this.offset = settings.offset;
38 this.currentday = settings.currentday;
39 this.order = settings.order;
40 this.throttle = false;
41 this.click_handle = settings.click_handle;
42 this.google_analytics = settings.google_analytics;
43 this.history = settings.history;
44 this.origURL = window.location.href;
45
46 // Handle element
47 this.handle = document.createElement( 'div' );
48 this.handle.setAttribute( 'id', 'infinite-handle' );
49 this.handle.innerHTML = '<span><button>' + text.replace( '\\', '' ) + '</button></span>';
50
51 // Footer settings
52 this.footer = {
53 el: document.getElementById( 'infinite-footer' ),
54 wrap: settings.footer,
55 };
56
57 // Bind methods used as callbacks
58 this.checkViewportOnLoadBound = self.checkViewportOnLoad.bind( this );
59
60 // Core's native MediaElement.js implementation needs special handling
61 this.wpMediaelement = null;
62
63 // We have two type of infinite scroll
64 // cases 'scroll' and 'click'
65
66 if ( type == 'scroll' ) {
67 // Bind refresh to the scroll event
68 // Throttle to check for such case every 300ms
69
70 // On event the case becomes a fact
71 this.window.addEventListener( 'scroll', function () {
72 self.throttle = true;
73 } );
74
75 // Go back top method
76 self.gotop();
77
78 setInterval( function () {
79 if ( self.throttle ) {
80 // Once the case is the case, the action occurs and the fact is no more
81 self.throttle = false;
82 // Reveal or hide footer
83 self.thefooter();
84 // Fire the refresh
85 self.refresh();
86 self.determineURL(); // determine the url
87 }
88 }, 250 );
89
90 // Ensure that enough posts are loaded to fill the initial viewport, to compensate for short posts and large displays.
91 self.ensureFilledViewport();
92 this.body.addEventListener( 'is.post-load', self.checkViewportOnLoadBound );
93 } else if ( type == 'click' ) {
94 if ( this.click_handle ) {
95 this.element.appendChild( this.handle );
96 }
97
98 this.handle.addEventListener( 'click', function () {
99 // Handle the handle
100 if ( self.click_handle ) {
101 self.handle.parentNode.removeChild( self.handle );
102 }
103
104 // Fire the refresh
105 self.refresh();
106 } );
107 }
108
109 // Initialize any Core audio or video players loaded via IS
110 this.body.addEventListener( 'is.post-load', self.initializeMejs );
111 };
112
113 /**
114 * Normalize the access to the document scrollTop value.
115 */
116 Scroller.prototype.getScrollTop = function () {
117 return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
118 };
119
120 /**
121 * Polyfill jQuery.extend.
122 */
123 Scroller.prototype.extend = function ( out ) {
124 out = out || {};
125
126 for ( var i = 1; i < arguments.length; i++ ) {
127 if ( ! arguments[ i ] ) {
128 continue;
129 }
130
131 for ( var key in arguments[ i ] ) {
132 if ( arguments[ i ].hasOwnProperty( key ) ) {
133 out[ key ] = arguments[ i ][ key ];
134 }
135 }
136 }
137 return out;
138 };
139
140 /**
141 * Check whether we should fetch any additional posts.
142 */
143 Scroller.prototype.check = function () {
144 var wrapperMeasurements = this.measure( this.element, [ this.wrapperClass ] );
145
146 // Fetch more posts when we're less than 2 screens away from the bottom.
147 return wrapperMeasurements.bottom < 2 * this.window.innerHeight;
148 };
149
150 /**
151 * Renders the results from a successful response.
152 */
153 Scroller.prototype.render = function ( response ) {
154 var childrenToAppend = Array.prototype.slice.call( response.fragment.childNodes );
155 this.body.classList.add( 'infinity-success' );
156
157 // Render the retrieved nodes.
158 while ( childrenToAppend.length > 0 ) {
159 var currentNode = childrenToAppend.shift();
160 this.element.appendChild( currentNode );
161 }
162
163 this.trigger( this.body, 'is.post-load', {
164 jqueryEventName: 'post-load',
165 data: response,
166 } );
167
168 this.ready = true;
169 };
170
171 /**
172 * Returns the object used to query for new posts.
173 */
174 Scroller.prototype.query = function () {
175 return {
176 page: this.page + this.offset, // Load the next page.
177 currentday: this.currentday,
178 order: this.order,
179 scripts: window.infiniteScroll.settings.scripts,
180 styles: window.infiniteScroll.settings.styles,
181 query_args: window.infiniteScroll.settings.query_args,
182 query_before: window.infiniteScroll.settings.query_before,
183 last_post_date: window.infiniteScroll.settings.last_post_date,
184 };
185 };
186
187 Scroller.prototype.animate = function ( cb, duration ) {
188 var start = performance.now();
189
190 requestAnimationFrame( function animate( time ) {
191 var timeFraction = Math.min( 1, ( time - start ) / duration );
192 cb( timeFraction );
193
194 if ( timeFraction < 1 ) {
195 requestAnimationFrame( animate );
196 }
197 } );
198 };
199
200 /**
201 * Scroll back to top.
202 */
203 Scroller.prototype.gotop = function () {
204 var blog = document.getElementById( 'infinity-blog-title' );
205 var self = this;
206
207 if ( ! blog ) {
208 return;
209 }
210
211 blog.setAttribute( 'title', totop );
212 blog.addEventListener( 'click', function ( e ) {
213 var sourceScroll = self.window.pageYOffset;
214 e.preventDefault();
215
216 self.animate( function ( progress ) {
217 var currentScroll = sourceScroll - sourceScroll * progress;
218 document.documentElement.scrollTop = document.body.scrollTop = currentScroll;
219 }, 200 );
220 } );
221 };
222
223 /**
224 * The infinite footer.
225 */
226 Scroller.prototype.thefooter = function () {
227 var self = this,
228 pageWrapper,
229 footerContainer,
230 width,
231 sourceBottom,
232 targetBottom,
233 footerEnabled = this.footer && this.footer.el;
234
235 if ( ! footerEnabled ) {
236 return;
237 }
238
239 // Check if we have an id for the page wrapper
240 if ( 'string' === typeof this.footer.wrap ) {
241 try {
242 pageWrapper = document.getElementById( this.footer.wrap );
243 width = pageWrapper.getBoundingClientRect();
244 width = width.width;
245 } catch ( err ) {
246 width = 0;
247 }
248
249 // Make the footer match the width of the page
250 if ( width > 479 ) {
251 footerContainer = this.footer.el.querySelector( '.container' );
252 if ( footerContainer ) {
253 footerContainer.style.width = width + 'px';
254 }
255 }
256 }
257
258 // Reveal footer
259 sourceBottom = parseInt( self.footer.el.style.bottom || -50, 10 );
260 targetBottom = this.window.pageYOffset >= 350 ? 0 : -50;
261
262 if ( sourceBottom !== targetBottom ) {
263 self.animate( function ( progress ) {
264 var currentBottom = sourceBottom + ( targetBottom - sourceBottom ) * progress;
265 self.footer.el.style.bottom = currentBottom + 'px';
266
267 if ( 1 === progress ) {
268 sourceBottom = targetBottom;
269 }
270 }, 200 );
271 }
272 };
273
274 /**
275 * Recursively convert a JS object into URL encoded data.
276 */
277 Scroller.prototype.urlEncodeJSON = function ( obj, prefix ) {
278 var params = [],
279 encodedKey,
280 newPrefix;
281
282 for ( var key in obj ) {
283 encodedKey = encodeURIComponent( key );
284 newPrefix = prefix ? prefix + '[' + encodedKey + ']' : encodedKey;
285
286 if ( 'object' === typeof obj[ key ] ) {
287 if ( ! Array.isArray( obj[ key ] ) || obj[ key ].length > 0 ) {
288 params.push( this.urlEncodeJSON( obj[ key ], newPrefix ) );
289 } else {
290 // Explicitly expose empty arrays with no values
291 params.push( newPrefix + '[]=' );
292 }
293 } else {
294 params.push( newPrefix + '=' + encodeURIComponent( obj[ key ] ) );
295 }
296 }
297 return params.join( '&' );
298 };
299
300 /**
301 * Controls the flow of the refresh. Don't mess.
302 */
303 Scroller.prototype.refresh = function () {
304 var self = this,
305 query,
306 xhr,
307 loader,
308 customized;
309
310 // If we're disabled, ready, or don't pass the check, bail.
311 if ( this.disabled || ! this.ready || ! this.check() ) {
312 return;
313 }
314
315 // Let's get going -- set ready to false to prevent
316 // multiple refreshes from occurring at once.
317 this.ready = false;
318
319 // Create a loader element to show it's working.
320 if ( this.click_handle ) {
321 if ( ! loader ) {
322 document.getElementById( 'infinite-aria' ).textContent = loading_text;
323 loader = document.createElement( 'div' );
324 loader.classList.add( 'infinite-loader' );
325 loader.setAttribute( 'role', 'progress' );
326 loader.innerHTML =
327 '<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>';
328 }
329 this.element.appendChild( loader );
330 }
331
332 // Generate our query vars.
333 query = self.extend(
334 {
335 action: 'infinite_scroll',
336 },
337 this.query()
338 );
339
340 // Inject Customizer state.
341 if ( 'undefined' !== typeof wp && wp.customize && wp.customize.settings.theme ) {
342 customized = {};
343 query.wp_customize = 'on';
344 query.theme = wp.customize.settings.theme.stylesheet;
345 wp.customize.each( function ( setting ) {
346 if ( setting._dirty ) {
347 customized[ setting.id ] = setting();
348 }
349 } );
350 query.customized = JSON.stringify( customized );
351 query.nonce = wp.customize.settings.nonce.preview;
352 }
353
354 // Fire the ajax request.
355 xhr = new XMLHttpRequest();
356 xhr.open( 'POST', infiniteScroll.settings.ajaxurl, true );
357 xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
358 xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
359 xhr.send( self.urlEncodeJSON( query ) );
360
361 // Allow refreshes to occur again if an error is triggered.
362 xhr.onerror = function () {
363 if ( self.click_handle && loader.parentNode ) {
364 loader.parentNode.removeChild( loader );
365 }
366
367 self.ready = true;
368 };
369
370 // Success handler
371 xhr.onload = function () {
372 var response = JSON.parse( xhr.responseText ),
373 httpCheck = xhr.status >= 200 && xhr.status < 300,
374 responseCheck = 'undefined' !== typeof response.html;
375
376 if ( ! response || ! httpCheck || ! responseCheck ) {
377 if ( self.click_handle && loader.parentNode ) {
378 loader.parentNode.removeChild( loader );
379 }
380 return;
381 }
382
383 // On success, let's hide the loader circle.
384 if ( self.click_handle && loader.parentNode ) {
385 loader.parentNode.removeChild( loader );
386 }
387
388 // If additional scripts are required by the incoming set of posts, parse them
389 if ( response.scripts && Array.isArray( response.scripts ) ) {
390 response.scripts.forEach( function ( item ) {
391 var elementToAppendTo = item.footer ? 'body' : 'head';
392
393 // Add script handle to list of those already parsed
394 window.infiniteScroll.settings.scripts.push( item.handle );
395
396 // Output extra data, if present
397 if ( item.extra_data ) {
398 self.appendInlineScript( item.extra_data, elementToAppendTo );
399 }
400
401 if ( item.before_handle ) {
402 self.appendInlineScript( item.before_handle, elementToAppendTo );
403 }
404
405 // Build script tag and append to DOM in requested location
406 var script = document.createElement( 'script' );
407 script.type = 'text/javascript';
408 script.src = item.src;
409 script.id = item.handle;
410
411 // Dynamically loaded scripts are async by default.
412 // We don't want that, it breaks stuff, e.g. wp-mediaelement init.
413 script.async = false;
414
415 if ( item.after_handle ) {
416 script.onload = function () {
417 self.appendInlineScript( item.after_handle, elementToAppendTo );
418 };
419 }
420
421 // If MediaElement.js is loaded in by item set of posts, don't initialize the players a second time as it breaks them all
422 if ( 'wp-mediaelement' === item.handle ) {
423 self.body.removeEventListener( 'is.post-load', self.initializeMejs );
424 }
425
426 if ( 'wp-mediaelement' === item.handle && 'undefined' === typeof mejs ) {
427 self.wpMediaelement = {};
428 self.wpMediaelement.tag = script;
429 self.wpMediaelement.element = elementToAppendTo;
430 setTimeout( self.maybeLoadMejs.bind( self ), 250 );
431 } else {
432 document.getElementsByTagName( elementToAppendTo )[ 0 ].appendChild( script );
433 }
434 } );
435 }
436
437 // If additional stylesheets are required by the incoming set of posts, parse them
438 if ( response.styles && Array.isArray( response.styles ) ) {
439 response.styles.forEach( function ( item ) {
440 // Add stylesheet handle to list of those already parsed
441 window.infiniteScroll.settings.styles.push( item.handle );
442
443 // Build link tag
444 var style = document.createElement( 'link' );
445 style.rel = 'stylesheet';
446 style.href = item.src;
447 style.id = item.handle + '-css';
448
449 // Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true
450 if (
451 item.conditional &&
452 ( ! isIE || ! eval( item.conditional.replace( /%ver/g, IEVersion ) ) )
453 ) {
454 style = false;
455 }
456
457 // Append link tag if necessary
458 if ( style ) {
459 document.getElementsByTagName( 'head' )[ 0 ].appendChild( style );
460 }
461 } );
462 }
463
464 // Convert the response.html to a fragment element.
465 // Using a div instead of DocumentFragment, because the latter doesn't support innerHTML.
466 response.fragment = document.createElement( 'div' );
467 response.fragment.innerHTML = response.html;
468
469 // Increment the page number
470 self.page++;
471
472 // Record pageview in WP Stats, if available.
473 if ( stats ) {
474 new Image().src =
475 document.location.protocol +
476 '//pixel.wp.com/g.gif?' +
477 stats +
478 '&post=0&baba=' +
479 Math.random();
480 }
481
482 // Add new posts to the postflair object
483 if ( 'object' === typeof response.postflair && 'object' === typeof WPCOM_sharing_counts ) {
484 WPCOM_sharing_counts = self.extend( WPCOM_sharing_counts, response.postflair ); // eslint-disable-line no-global-assign
485 }
486
487 // Render the results
488 self.render.call( self, response );
489
490 // If 'click' type and there are still posts to fetch, add back the handle
491 if ( type == 'click' ) {
492 // add focus to new posts, only in button mode as we know where page focus currently is and only if we have a wrapper
493 if ( infiniteScroll.settings.wrapper ) {
494 document
495 .querySelector(
496 '#infinite-view-' + ( self.page + self.offset - 1 ) + ' a:first-of-type'
497 )
498 .focus( {
499 preventScroll: true,
500 } );
501 }
502
503 if ( response.lastbatch ) {
504 if ( self.click_handle ) {
505 // Update body classes
506 self.body.classList.add( 'infinity-end' );
507 self.body.classList.remove( 'infinity-success' );
508 } else {
509 self.trigger( this.body, 'infinite-scroll-posts-end' );
510 }
511 } else {
512 if ( self.click_handle ) {
513 self.element.appendChild( self.handle );
514 } else {
515 self.trigger( this.body, 'infinite-scroll-posts-more' );
516 }
517 }
518 } else if ( response.lastbatch ) {
519 self.disabled = true;
520
521 self.body.classList.add( 'infinity-end' );
522 self.body.classList.remove( 'infinity-success' );
523 }
524
525 // Update currentday to the latest value returned from the server
526 if ( response.currentday ) {
527 self.currentday = response.currentday;
528 }
529
530 // Fire Google Analytics pageview
531 if ( self.google_analytics ) {
532 var ga_url = self.history.path.replace( /%d/, self.page );
533 if ( 'object' === typeof _gaq ) {
534 _gaq.push( [ '_trackPageview', ga_url ] );
535 }
536 if ( 'function' === typeof ga ) {
537 ga( 'send', 'pageview', ga_url );
538 }
539 }
540 };
541
542 return xhr;
543 };
544
545 /**
546 * Given JavaScript blob and the name of a parent tag, this helper function will
547 * generate a script tag, insert the JavaScript blob, and append it to the parent.
548 *
549 * It's important to note that the JavaScript blob will be evaluated immediately. If
550 * you need a parent script to load first, use that script element's onload handler.
551 *
552 * @param {string} script The blob of JavaScript to run.
553 * @param {string} parentTag The tag name of the parent element.
554 */
555 Scroller.prototype.appendInlineScript = function ( script, parentTag ) {
556 var element = document.createElement( 'script' ),
557 scriptContent = document.createTextNode( '//<![CDATA[ \n' + script + '\n//]]>' );
558
559 element.type = 'text/javascript';
560 element.appendChild( scriptContent );
561
562 document.getElementsByTagName( parentTag )[ 0 ].appendChild( element );
563 };
564
565 /**
566 * Core's native media player uses MediaElement.js
567 * 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.
568 */
569 Scroller.prototype.maybeLoadMejs = function () {
570 if ( null === this.wpMediaelement ) {
571 return;
572 }
573
574 if ( 'undefined' === typeof mejs ) {
575 setTimeout( this.maybeLoadMejs.bind( this ), 250 );
576 } else {
577 document
578 .getElementsByTagName( this.wpMediaelement.element )[ 0 ]
579 .appendChild( this.wpMediaelement.tag );
580 this.wpMediaelement = null;
581
582 // Ensure any subsequent IS loads initialize the players
583 this.body.addEventListener( 'is.post-load', this.initializeMejs );
584 }
585 };
586
587 /**
588 * Initialize the MediaElement.js player for any posts not previously initialized
589 */
590 Scroller.prototype.initializeMejs = function ( e ) {
591 // Are there media players in the incoming set of posts?
592 if (
593 ! e.detail ||
594 ! e.detail.html ||
595 ( -1 === e.detail.html.indexOf( 'wp-audio-shortcode' ) &&
596 -1 === e.detail.html.indexOf( 'wp-video-shortcode' ) )
597 ) {
598 return;
599 }
600
601 // Don't bother if mejs isn't loaded for some reason
602 if ( 'undefined' === typeof mejs ) {
603 return;
604 }
605
606 // Adapted from wp-includes/js/mediaelement/wp-mediaelement.js
607 // Modified to not initialize already-initialized players, as Mejs doesn't handle that well
608 var settings = {};
609 var audioVideoElements;
610
611 if ( typeof _wpmejsSettings !== 'undefined' ) {
612 settings.pluginPath = _wpmejsSettings.pluginPath;
613 }
614
615 settings.success = function ( mejs ) {
616 var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
617 if ( 'flash' === mejs.pluginType && autoplay ) {
618 mejs.addEventListener(
619 'canplay',
620 function () {
621 mejs.play();
622 },
623 false
624 );
625 }
626 };
627
628 audioVideoElements = document.querySelectorAll( '.wp-audio-shortcode, .wp-video-shortcode' );
629 audioVideoElements = Array.prototype.slice.call( audioVideoElements );
630
631 // Only process already unprocessed shortcodes.
632 audioVideoElements = audioVideoElements.filter( function ( el ) {
633 while ( el.parentNode ) {
634 if ( el.classList.contains( 'mejs-container' ) ) {
635 return false;
636 }
637 el = el.parentNode;
638 }
639 return true;
640 } );
641
642 for ( var i = 0; i < audioVideoElements.length; i++ ) {
643 new MediaElementPlayer( audioVideoElements[ i ], settings );
644 }
645 };
646
647 /**
648 * Get element measurements relative to the viewport.
649 *
650 * @returns {object}
651 */
652 Scroller.prototype.measure = function ( element, expandClasses ) {
653 expandClasses = expandClasses || [];
654
655 var childrenToTest = Array.prototype.slice.call( element.children );
656 var currentChild,
657 minTop = Number.MAX_VALUE,
658 maxBottom = 0,
659 currentChildRect,
660 i;
661
662 while ( childrenToTest.length > 0 ) {
663 currentChild = childrenToTest.shift();
664
665 for ( i = 0; i < expandClasses.length; i++ ) {
666 // Expand (= measure) child elements of nodes with class names from expandClasses.
667 if ( currentChild.classList.contains( expandClasses[ i ] ) ) {
668 childrenToTest = childrenToTest.concat(
669 Array.prototype.slice.call( currentChild.children )
670 );
671 break;
672 }
673 }
674 currentChildRect = currentChild.getBoundingClientRect();
675
676 minTop = Math.min( minTop, currentChildRect.top );
677 maxBottom = Math.max( maxBottom, currentChildRect.bottom );
678 }
679
680 var viewportMiddle = Math.round( window.innerHeight / 2 );
681
682 // isActive = does the middle of the viewport cross the element?
683 var isActive = minTop <= viewportMiddle && maxBottom >= viewportMiddle;
684
685 /**
686 * Factor = percentage of viewport above the middle line occupied by the element.
687 *
688 * Negative factors are assigned for elements below the middle line. That's on purpose
689 * to only allow "page 2" to change the URL once it's in the middle of the viewport.
690 */
691 var factor = ( Math.min( maxBottom, viewportMiddle ) - Math.max( minTop, 0 ) ) / viewportMiddle;
692
693 return {
694 top: minTop,
695 bottom: maxBottom,
696 height: maxBottom - minTop,
697 factor: factor,
698 isActive: isActive,
699 };
700 };
701
702 /**
703 * Trigger IS to load additional posts if the initial posts don't fill the window.
704 *
705 * On large displays, or when posts are very short, the viewport may not be filled with posts,
706 * so we overcome this by loading additional posts when IS initializes.
707 */
708 Scroller.prototype.ensureFilledViewport = function () {
709 var self = this,
710 windowHeight = self.window.innerHeight,
711 wrapperMeasurements = self.measure( self.element, [ self.wrapperClass ] );
712
713 // Only load more posts once. This prevents infinite loops when there are no more posts.
714 self.body.removeEventListener( 'is.post-load', self.checkViewportOnLoadBound );
715
716 // Load more posts if space permits, otherwise stop checking for a full viewport.
717 if ( wrapperMeasurements.bottom !== 0 && wrapperMeasurements.bottom < windowHeight ) {
718 self.ready = true;
719 self.refresh();
720 }
721 };
722
723 /**
724 * Event handler for ensureFilledViewport(), tied to the post-load trigger.
725 * 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.
726 */
727 Scroller.prototype.checkViewportOnLoad = function () {
728 this.ensureFilledViewport();
729 };
730
731 function fullscreenState() {
732 return document.fullscreenElement ||
733 document.mozFullScreenElement ||
734 document.webkitFullscreenElement ||
735 document.msFullscreenElement
736 ? 1
737 : 0;
738 }
739
740 var previousFullScrenState = fullscreenState();
741
742 /**
743 * Identify archive page that corresponds to majority of posts shown in the current browser window.
744 */
745 Scroller.prototype.determineURL = function () {
746 var self = this,
747 pageNum = -1,
748 currentFullScreenState = fullscreenState(),
749 wrapperEls,
750 maxFactor = 0;
751
752 // xor - check if the state has changed
753 if ( previousFullScrenState ^ currentFullScreenState ) {
754 // If we just switched to/from fullscreen,
755 // don't do the div clearing/caching or the
756 // URL setting. Doing so can break video playback
757 // if the video goes to fullscreen.
758
759 previousFullScrenState = currentFullScreenState;
760 return;
761 }
762 previousFullScrenState = currentFullScreenState;
763 wrapperEls = document.querySelectorAll( '.' + self.wrapperClass );
764
765 for ( var i = 0; i < wrapperEls.length; i++ ) {
766 var setMeasurements = self.measure( wrapperEls[ i ] );
767
768 // If it exists, pick a set that is crossed by the middle of the viewport.
769 if ( setMeasurements.isActive ) {
770 pageNum = parseInt( wrapperEls[ i ].dataset.pageNum, 10 );
771 break;
772 }
773
774 // If there is such a set, pick the one that occupies the most space
775 // above the middle of the viewport.
776 if ( setMeasurements.factor > maxFactor ) {
777 pageNum = parseInt( wrapperEls[ i ].dataset.pageNum, 10 );
778 maxFactor = setMeasurements.factor;
779 }
780
781 // Otherwise default to -1
782 }
783
784 self.updateURL( pageNum );
785 };
786
787 /**
788 * Update address bar to reflect archive page URL for a given page number.
789 * Checks if URL is different to prevent pollution of browser history.
790 */
791 Scroller.prototype.updateURL = function ( page ) {
792 // IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met.
793 if ( ! window.history.pushState ) {
794 return;
795 }
796 var self = this,
797 pageSlug = self.origURL;
798
799 if ( -1 !== page ) {
800 pageSlug =
801 window.location.protocol +
802 '//' +
803 self.history.host +
804 self.history.path.replace( /%d/, page ) +
805 self.history.parameters;
806 }
807
808 if ( window.location.href != pageSlug ) {
809 history.pushState( null, null, pageSlug );
810 }
811 };
812
813 /**
814 * Pause scrolling.
815 */
816 Scroller.prototype.pause = function () {
817 this.disabled = true;
818 };
819
820 /**
821 * Resume scrolling.
822 */
823 Scroller.prototype.resume = function () {
824 this.disabled = false;
825 };
826
827 /**
828 * Emits custom JS events.
829 *
830 * @param {Node} el
831 * @param {string} eventName
832 * @param {*} data
833 */
834 Scroller.prototype.trigger = function ( el, eventName, opts ) {
835 opts = opts || {};
836
837 /**
838 * Emit the event in a jQuery way for backwards compatibility where necessary.
839 */
840 if ( opts.jqueryEventName && 'undefined' !== typeof jQuery ) {
841 jQuery( el ).trigger( opts.jqueryEventName, opts.data || null );
842 }
843
844 /**
845 * Emit the event in a standard way.
846 */
847 var e;
848 try {
849 e = new CustomEvent( eventName, {
850 bubbles: true,
851 cancelable: true,
852 detail: opts.data || null,
853 } );
854 } catch ( err ) {
855 e = document.createEvent( 'CustomEvent' );
856 e.initCustomEvent( eventName, true, true, opts.data || null );
857 }
858 el.dispatchEvent( e );
859 };
860
861 /**
862 * Ready, set, go!
863 */
864 var jetpackInfinityModule = function () {
865 var bodyClasses = infiniteScroll.settings.body_class.split( ' ' );
866
867 // Check for our variables
868 if ( 'object' !== typeof infiniteScroll ) {
869 return;
870 }
871
872 bodyClasses.forEach( function ( className ) {
873 if ( className ) {
874 document.body.classList.add( className );
875 }
876 } );
877
878 // Set ajaxurl (for brevity)
879 ajaxurl = infiniteScroll.settings.ajaxurl;
880
881 // Set stats, used for tracking stats
882 stats = infiniteScroll.settings.stats;
883
884 // Define what type of infinity we have, grab text for click-handle
885 type = infiniteScroll.settings.type;
886 text = infiniteScroll.settings.text;
887 totop = infiniteScroll.settings.totop;
888
889 // aria text
890 loading_text = infiniteScroll.settings.loading_text;
891
892 // Initialize the scroller (with the ID of the element from the theme)
893 infiniteScroll.scroller = new Scroller( infiniteScroll.settings );
894
895 /**
896 * Monitor user scroll activity to update URL to correspond to archive page for current set of IS posts
897 */
898 if ( type == 'click' ) {
899 var timer = null;
900 window.addEventListener( 'scroll', function () {
901 // run the real scroll handler once every 250 ms.
902 if ( timer ) {
903 return;
904 }
905 timer = setTimeout( function () {
906 infiniteScroll.scroller.determineURL();
907 timer = null;
908 }, 250 );
909 } );
910 }
911 };
912
913 /**
914 * Ready, set, go!
915 */
916 if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
917 jetpackInfinityModule();
918 } else {
919 document.addEventListener( 'DOMContentLoaded', jetpackInfinityModule );
920 }
921 } )(); // Close closure
922