Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
gilour
/
wp-content
/
plugins
/
kadence-pro
/
dist
/
infinite-scroll
/
src
:
infinite-scroll.js
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
/*! * Infinite Scroll PACKAGED v4.0.1 * Automatically add next page * * Licensed GPLv3 for open source use * or Infinite Scroll Commercial License for commercial use * * https://infinite-scroll.com * Copyright 2018-2020 Metafizzy */ /** * Bridget makes jQuery widgets * v3.0.0 * MIT license */ ( function( window, factory ) { // module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('jquery'), ); } else { // browser global window.jQueryBridget = factory( window, window.jQuery, ); } }( window, function factory( window, jQuery ) { // ----- utils ----- // // helper function for logging errors // $.error breaks jQuery chaining let console = window.console; let logError = typeof console == 'undefined' ? function() {} : function( message ) { console.error( message ); }; // ----- jQueryBridget ----- // function jQueryBridget( namespace, PluginClass, $ ) { $ = $ || jQuery || window.jQuery; if ( !$ ) { return; } // add option method -> $().plugin('option', {...}) if ( !PluginClass.prototype.option ) { // option setter PluginClass.prototype.option = function( opts ) { if ( !opts ) return; this.options = Object.assign( this.options || {}, opts ); }; } // make jQuery plugin $.fn[ namespace ] = function( arg0, ...args ) { if ( typeof arg0 == 'string' ) { // method call $().plugin( 'methodName', { options } ) return methodCall( this, arg0, args ); } // just $().plugin({ options }) plainCall( this, arg0 ); return this; }; // $().plugin('methodName') function methodCall( $elems, methodName, args ) { let returnValue; let pluginMethodStr = `$().${namespace}("${methodName}")`; $elems.each( function( i, elem ) { // get instance let instance = $.data( elem, namespace ); if ( !instance ) { logError( `${namespace} not initialized.` + ` Cannot call method ${pluginMethodStr}` ); return; } let method = instance[ methodName ]; if ( !method || methodName.charAt( 0 ) == '_' ) { logError(`${pluginMethodStr} is not a valid method`); return; } // apply method, get return value let value = method.apply( instance, args ); // set return value if value is returned, use only first value returnValue = returnValue === undefined ? value : returnValue; } ); return returnValue !== undefined ? returnValue : $elems; } function plainCall( $elems, options ) { $elems.each( function( i, elem ) { let instance = $.data( elem, namespace ); if ( instance ) { // set options & init instance.option( options ); instance._init(); } else { // initialize new instance instance = new PluginClass( elem, options ); $.data( elem, namespace, instance ); } } ); } } // ----- ----- // return jQueryBridget; } ) ); /** * EvEmitter v2.0.0 * Lil' event emitter * MIT License */ ( function( global, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS - Browserify, Webpack module.exports = factory(); } else { // Browser globals global.EvEmitter = factory(); } }( typeof window != 'undefined' ? window : this, function() { function EvEmitter() {} let proto = EvEmitter.prototype; proto.on = function( eventName, listener ) { if ( !eventName || !listener ) return this; // set events hash let events = this._events = this._events || {}; // set listeners array let listeners = events[ eventName ] = events[ eventName ] || []; // only add once if ( !listeners.includes( listener ) ) { listeners.push( listener ); } return this; }; proto.once = function( eventName, listener ) { if ( !eventName || !listener ) return this; // add event this.on( eventName, listener ); // set once flag // set onceEvents hash let onceEvents = this._onceEvents = this._onceEvents || {}; // set onceListeners object let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; // set flag onceListeners[ listener ] = true; return this; }; proto.off = function( eventName, listener ) { let listeners = this._events && this._events[ eventName ]; if ( !listeners || !listeners.length ) return this; let index = listeners.indexOf( listener ); if ( index != -1 ) { listeners.splice( index, 1 ); } return this; }; proto.emitEvent = function( eventName, args ) { let listeners = this._events && this._events[ eventName ]; if ( !listeners || !listeners.length ) return this; // copy over to avoid interference if .off() in listener listeners = listeners.slice( 0 ); args = args || []; // once stuff let onceListeners = this._onceEvents && this._onceEvents[ eventName ]; for ( let listener of listeners ) { let isOnce = onceListeners && onceListeners[ listener ]; if ( isOnce ) { // remove listener // remove before trigger to prevent recursion this.off( eventName, listener ); // unset once flag delete onceListeners[ listener ]; } // trigger listener listener.apply( this, args ); } return this; }; proto.allOff = function() { delete this._events; delete this._onceEvents; return this; }; return EvEmitter; } ) ); /** * Fizzy UI utils v3.0.0 * MIT license */ ( function( global, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( global ); } else { // browser global global.fizzyUIUtils = factory( global ); } }( this, function factory( global ) { let utils = {}; // ----- extend ----- // // extends objects utils.extend = function( a, b ) { return Object.assign( a, b ); }; // ----- modulo ----- // utils.modulo = function( num, div ) { return ( ( num % div ) + div ) % div; }; // ----- makeArray ----- // // turn element or nodeList into an array utils.makeArray = function( obj ) { // use object if already an array if ( Array.isArray( obj ) ) return obj; // return empty array if undefined or null. #6 if ( obj === null || obj === undefined ) return []; let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number'; // convert nodeList to array if ( isArrayLike ) return [ ...obj ]; // array of single index return [ obj ]; }; // ----- removeFrom ----- // utils.removeFrom = function( ary, obj ) { let index = ary.indexOf( obj ); if ( index != -1 ) { ary.splice( index, 1 ); } }; // ----- getParent ----- // utils.getParent = function( elem, selector ) { while ( elem.parentNode && elem != document.body ) { elem = elem.parentNode; if ( elem.matches( selector ) ) return elem; } }; // ----- getQueryElement ----- // // use element as selector string utils.getQueryElement = function( elem ) { if ( typeof elem == 'string' ) { return document.querySelector( elem ); } return elem; }; // ----- handleEvent ----- // // enable .ontype to trigger from .addEventListener( elem, 'type' ) utils.handleEvent = function( event ) { let method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; // ----- filterFindElements ----- // utils.filterFindElements = function( elems, selector ) { // make array of elems elems = utils.makeArray( elems ); return elems // check that elem is an actual element .filter( ( elem ) => elem instanceof HTMLElement ) .reduce( ( ffElems, elem ) => { // add elem if no selector if ( !selector ) { ffElems.push( elem ); return ffElems; } // filter & find items if we have a selector // filter if ( elem.matches( selector ) ) { ffElems.push( elem ); } // find children let childElems = elem.querySelectorAll( selector ); // concat childElems to filterFound array ffElems = ffElems.concat( ...childElems ); return ffElems; }, [] ); }; // ----- debounceMethod ----- // utils.debounceMethod = function( _class, methodName, threshold ) { threshold = threshold || 100; // original method let method = _class.prototype[ methodName ]; let timeoutName = methodName + 'Timeout'; _class.prototype[ methodName ] = function() { clearTimeout( this[ timeoutName ] ); let args = arguments; this[ timeoutName ] = setTimeout( () => { method.apply( this, args ); delete this[ timeoutName ]; }, threshold ); }; }; // ----- docReady ----- // utils.docReady = function( onDocReady ) { let readyState = document.readyState; if ( readyState == 'complete' || readyState == 'interactive' ) { // do async to allow for other scripts to run. metafizzy/flickity#441 setTimeout( onDocReady ); } else { document.addEventListener( 'DOMContentLoaded', onDocReady ); } }; // ----- htmlInit ----- // // http://bit.ly/3oYLusc utils.toDashed = function( str ) { return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) { return $1 + '-' + $2; } ).toLowerCase(); }; let console = global.console; // allow user to initialize classes via [data-namespace] or .js-namespace class // htmlInit( Widget, 'widgetName' ) // options are parsed from data-namespace-options utils.htmlInit = function( WidgetClass, namespace ) { utils.docReady( function() { let dashedNamespace = utils.toDashed( namespace ); let dataAttr = 'data-' + dashedNamespace; let dataAttrElems = document.querySelectorAll( `[${dataAttr}]` ); let jQuery = global.jQuery; [ ...dataAttrElems ].forEach( ( elem ) => { let attr = elem.getAttribute( dataAttr ); let options; try { options = attr && JSON.parse( attr ); } catch ( error ) { // log error, do not initialize if ( console ) { console.error( `Error parsing ${dataAttr} on ${elem.className}: ${error}` ); } return; } // initialize let instance = new WidgetClass( elem, options ); // make available via $().data('namespace') if ( jQuery ) { jQuery.data( elem, namespace, instance ); } } ); } ); }; // ----- ----- // return utils; } ) ); // core ( function( window, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('ev-emitter'), require('fizzy-ui-utils'), ); } else { // browser global window.InfiniteScroll = factory( window, window.EvEmitter, window.fizzyUIUtils, ); } }( window, function factory( window, EvEmitter, utils ) { let jQuery = window.jQuery; // internal store of all InfiniteScroll intances let instances = {}; function InfiniteScroll( element, options ) { let queryElem = utils.getQueryElement( element ); if ( !queryElem ) { console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) ); return; } element = queryElem; // do not initialize twice on same element if ( element.infiniteScrollGUID ) { let instance = instances[ element.infiniteScrollGUID ]; instance.option( options ); return instance; } this.element = element; // options this.options = { ...InfiniteScroll.defaults }; this.option( options ); // add jQuery if ( jQuery ) { this.$element = jQuery( this.element ); } this.create(); } // defaults InfiniteScroll.defaults = { // path: null, // hideNav: null, // debug: false, }; // create & destroy methods InfiniteScroll.create = {}; InfiniteScroll.destroy = {}; let proto = InfiniteScroll.prototype; // inherit EvEmitter Object.assign( proto, EvEmitter.prototype ); // -------------------------- -------------------------- // // globally unique identifiers let GUID = 0; proto.create = function() { // create core // add id for InfiniteScroll.data let id = this.guid = ++GUID; this.element.infiniteScrollGUID = id; // expando instances[ id ] = this; // associate via id // properties this.pageIndex = 1; // default to first page this.loadCount = 0; this.updateGetPath(); // bail if getPath not set, or returns falsey #776 let hasPath = this.getPath && this.getPath(); if ( !hasPath ) { console.error('Disabling InfiniteScroll'); return; } this.updateGetAbsolutePath(); this.log( 'initialized', [ this.element.className ] ); this.callOnInit(); // create features for ( let method in InfiniteScroll.create ) { InfiniteScroll.create[ method ].call( this ); } }; proto.option = function( opts ) { Object.assign( this.options, opts ); }; // call onInit option, used for binding events on init proto.callOnInit = function() { let onInit = this.options.onInit; if ( onInit ) { onInit.call( this, this ); } }; // ----- events ----- // proto.dispatchEvent = function( type, event, args ) { this.log( type, args ); let emitArgs = event ? [ event ].concat( args ) : args; this.emitEvent( type, emitArgs ); // trigger jQuery event if ( !jQuery || !this.$element ) { return; } // namespace jQuery event type += '.infiniteScroll'; let $event = type; if ( event ) { // create jQuery event /* eslint-disable-next-line new-cap */ let jQEvent = jQuery.Event( event ); jQEvent.type = type; $event = jQEvent; } this.$element.trigger( $event, args ); }; let loggers = { initialized: ( className ) => `on ${className}`, request: ( path ) => `URL: ${path}`, load: ( response, path ) => `${response.title || ''}. URL: ${path}`, error: ( error, path ) => `${error}. URL: ${path}`, append: ( response, path, items ) => `${items.length} items. URL: ${path}`, last: ( response, path ) => `URL: ${path}`, history: ( title, path ) => `URL: ${path}`, pageIndex: function( index, origin ) { return `current page determined to be: ${index} from ${origin}`; }, }; // log events proto.log = function( type, args ) { if ( !this.options.debug ) return; let message = `[InfiniteScroll] ${type}`; let logger = loggers[ type ]; if ( logger ) message += '. ' + logger.apply( this, args ); console.log( message ); }; // -------------------------- methods used amoung features -------------------------- // proto.updateMeasurements = function() { this.windowHeight = window.innerHeight; let rect = this.element.getBoundingClientRect(); this.top = rect.top + window.scrollY; }; proto.updateScroller = function() { let elementScroll = this.options.elementScroll; if ( !elementScroll ) { // default, use window this.scroller = window; return; } // if true, set to element, otherwise use option this.scroller = elementScroll === true ? this.element : utils.getQueryElement( elementScroll ); if ( !this.scroller ) { throw new Error(`Unable to find elementScroll: ${elementScroll}`); } }; // -------------------------- page path -------------------------- // proto.updateGetPath = function() { let optPath = this.options.path; if ( !optPath ) { console.error(`InfiniteScroll path option required. Set as: ${optPath}`); return; } // function let type = typeof optPath; if ( type == 'function' ) { this.getPath = optPath; return; } // template string: '/pages/{{#}}.html' let templateMatch = type == 'string' && optPath.match('{{#}}'); if ( templateMatch ) { this.updateGetPathTemplate( optPath ); return; } // selector: '.next-page-selector' this.updateGetPathSelector( optPath ); }; proto.updateGetPathTemplate = function( optPath ) { // set getPath with template string this.getPath = () => { let nextIndex = this.pageIndex + 1; return optPath.replace( '{{#}}', nextIndex ); }; // get pageIndex from location // convert path option into regex to look for pattern in location // escape query (?) in url, allows for parsing GET parameters let regexString = optPath .replace( /(\\\?|\?)/, '\\?' ) .replace( '{{#}}', '(\\d\\d?\\d?)' ); let templateRe = new RegExp( regexString ); let match = location.href.match( templateRe ); if ( match ) { this.pageIndex = parseInt( match[1], 10 ); this.log( 'pageIndex', [ this.pageIndex, 'template string' ] ); } }; let pathRegexes = [ // WordPress & Tumblr - example.com/page/2 // Jekyll - example.com/page2 /^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/, // Drupal - example.com/?page=1 /^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/, // catch all, last occurence of a number /(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/, ]; // try matching href to pathRegexes patterns let getPathParts = InfiniteScroll.getPathParts = function( href ) { if ( !href ) return; for ( let regex of pathRegexes ) { let match = href.match( regex ); if ( match ) { let [ , begin, index, end ] = match; return { begin, index, end }; } } }; proto.updateGetPathSelector = function( optPath ) { // parse href of link: '.next-page-link' let hrefElem = document.querySelector( optPath ); if ( !hrefElem ) { console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`); return; } let href = hrefElem.getAttribute('href'); let pathParts = getPathParts( href ); if ( !pathParts ) { console.error(`InfiniteScroll unable to parse next link href: ${href}`); return; } let { begin, index, end } = pathParts; this.isPathSelector = true; // flag for checkLastPage() this.getPath = () => begin + ( this.pageIndex + 1 ) + end; // get pageIndex from href this.pageIndex = parseInt( index, 10 ) - 1; this.log( 'pageIndex', [ this.pageIndex, 'next link' ] ); }; proto.updateGetAbsolutePath = function() { let path = this.getPath(); // path doesn't start with http or / let isAbsolute = path.match( /^http/ ) || path.match( /^\// ); if ( isAbsolute ) { this.getAbsolutePath = this.getPath; return; } let { pathname } = location; // query parameter #829. example.com/?pg=2 let isQuery = path.match( /^\?/ ); // /foo/bar/index.html => /foo/bar let directory = pathname.substring( 0, pathname.lastIndexOf('/') ); let pathStart = isQuery ? pathname : directory + '/'; this.getAbsolutePath = () => pathStart + this.getPath(); }; // -------------------------- nav -------------------------- // // hide navigation InfiniteScroll.create.hideNav = function() { let nav = utils.getQueryElement( this.options.hideNav ); if ( !nav ) return; nav.style.display = 'none'; this.nav = nav; }; InfiniteScroll.destroy.hideNav = function() { if ( this.nav ) this.nav.style.display = ''; }; // -------------------------- destroy -------------------------- // proto.destroy = function() { this.allOff(); // remove all event listeners // call destroy methods for ( let method in InfiniteScroll.destroy ) { InfiniteScroll.destroy[ method ].call( this ); } delete this.element.infiniteScrollGUID; delete instances[ this.guid ]; // remove jQuery data. #807 if ( jQuery && this.$element ) { jQuery.removeData( this.element, 'infiniteScroll' ); } }; // -------------------------- utilities -------------------------- // // https://remysharp.com/2010/07/21/throttling-function-calls InfiniteScroll.throttle = function( fn, threshold ) { threshold = threshold || 200; let last, timeout; return function() { let now = +new Date(); let args = arguments; let trigger = () => { last = now; fn.apply( this, args ); }; if ( last && now < last + threshold ) { // hold on to it clearTimeout( timeout ); timeout = setTimeout( trigger, threshold ); } else { trigger(); } }; }; InfiniteScroll.data = function( elem ) { elem = utils.getQueryElement( elem ); let id = elem && elem.infiniteScrollGUID; return id && instances[ id ]; }; // set internal jQuery, for Webpack + jQuery v3 InfiniteScroll.setJQuery = function( jqry ) { jQuery = jqry; }; // -------------------------- setup -------------------------- // utils.htmlInit( InfiniteScroll, 'infinite-scroll' ); // add noop _init method for jQuery Bridget. #768 proto._init = function() {}; let { jQueryBridget } = window; if ( jQuery && jQueryBridget ) { jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery ); } // -------------------------- -------------------------- // return InfiniteScroll; } ) ); // page-load ( function( window, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('./core'), ); } else { // browser global factory( window, window.InfiniteScroll, ); } }( window, function factory( window, InfiniteScroll ) { let proto = InfiniteScroll.prototype; Object.assign( InfiniteScroll.defaults, { // append: false, loadOnScroll: true, checkLastPage: true, responseBody: 'text', domParseResponse: true, // prefill: false, // outlayer: null, } ); InfiniteScroll.create.pageLoad = function() { this.canLoad = true; this.on( 'scrollThreshold', this.onScrollThresholdLoad ); this.on( 'load', this.checkLastPage ); if ( this.options.outlayer ) { this.on( 'append', this.onAppendOutlayer ); } this.on( 'append', this.reloadSrcsetImgs ); }; proto.onScrollThresholdLoad = function() { if ( this.options.loadOnScroll ) this.loadNextPage(); }; let domParser = new DOMParser(); proto.loadNextPage = function() { if ( this.isLoading || !this.canLoad ) return; let { responseBody, domParseResponse, fetchOptions } = this.options; let path = this.getAbsolutePath(); this.isLoading = true; if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions(); let fetchPromise = fetch( path, fetchOptions ) .then( ( response ) => { if ( !response.ok ) { let error = new Error( response.statusText ); this.onPageError( error, path, response ); return { response }; } return response[ responseBody ]().then( ( body ) => { let canDomParse = responseBody == 'text' && domParseResponse; if ( canDomParse ) { body = domParser.parseFromString( body, 'text/html' ); } if ( response.status == 204 ) { this.lastPageReached( body, path ); return { body, response }; } else { return this.onPageLoad( body, path, response ); } } ); } ) .catch( ( error ) => { this.onPageError( error, path ); } ); this.dispatchEvent( 'request', null, [ path, fetchPromise ] ); return fetchPromise; }; proto.onPageLoad = function( body, path, response ) { // done loading if not appending if ( !this.options.append ) { this.isLoading = false; } this.pageIndex++; this.loadCount++; this.dispatchEvent( 'load', null, [ body, path, response ] ); return this.appendNextPage( body, path, response ); }; proto.appendNextPage = function( body, path, response ) { let { append, responseBody, domParseResponse } = this.options; // do not append json let isDocument = responseBody == 'text' && domParseResponse; if ( !isDocument || !append ) return { body, response }; let items = body.querySelectorAll( append ); let promiseValue = { body, response, items }; // last page hit if no items. #840 if ( !items || !items.length ) { this.lastPageReached( body, path ); return promiseValue; } let fragment = getItemsFragment( items ); let appendReady = () => { this.appendItems( items, fragment ); this.isLoading = false; this.dispatchEvent( 'append', null, [ body, path, items, response ] ); return promiseValue; }; // TODO add hook for option to trigger appendReady if ( this.options.outlayer ) { return this.appendOutlayerItems( fragment, appendReady ); } else { return appendReady(); } }; proto.appendItems = function( items, fragment ) { if ( !items || !items.length ) return; // get fragment if not provided fragment = fragment || getItemsFragment( items ); refreshScripts( fragment ); this.element.appendChild( fragment ); }; function getItemsFragment( items ) { // add items to fragment let fragment = document.createDocumentFragment(); if ( items ) fragment.append( ...items ); return fragment; } // replace <script>s with copies so they load // <script>s added by InfiniteScroll will not load // similar to https://stackoverflow.com/questions/610995 function refreshScripts( fragment ) { let scripts = fragment.querySelectorAll('script'); for ( let script of scripts ) { let freshScript = document.createElement('script'); // copy attributes let attrs = script.attributes; for ( let attr of attrs ) { freshScript.setAttribute( attr.name, attr.value ); } // copy inner script code. #718, #782 freshScript.innerHTML = script.innerHTML; script.parentNode.replaceChild( freshScript, script ); } } // ----- outlayer ----- // proto.appendOutlayerItems = function( fragment, appendReady ) { let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded; if ( !imagesLoaded ) { console.error('[InfiniteScroll] imagesLoaded required for outlayer option'); this.isLoading = false; return; } // append once images loaded return new Promise( function( resolve ) { imagesLoaded( fragment, function() { let bodyResponse = appendReady(); resolve( bodyResponse ); } ); } ); }; proto.reloadSrcsetImgsItem = function( item ) { var imgs = item.querySelectorAll('img[srcset]'); for ( var i=0; i < imgs.length; i++ ) { var img = imgs[i]; img.outerHTML = img.outerHTML; } }; proto.reloadSrcsetImgs = function( response, path, items ) { for ( var i=0; i < items.length; i++ ) { this.reloadSrcsetImgsItem( items[i] ); } }; proto.onAppendOutlayer = function( response, path, items ) { this.options.outlayer.appended( items ); }; // ----- checkLastPage ----- // // check response for next element proto.checkLastPage = function( body, path ) { let { checkLastPage, path: pathOpt } = this.options; if ( !checkLastPage ) return; // if path is function, check if next path is truthy if ( typeof pathOpt == 'function' ) { let nextPath = this.getPath(); if ( !nextPath ) { this.lastPageReached( body, path ); return; } } // get selector from checkLastPage or path option let selector; if ( typeof checkLastPage == 'string' ) { selector = checkLastPage; } else if ( this.isPathSelector ) { // path option is selector string selector = pathOpt; } // check last page for selector // bail if no selector or not document response if ( !selector || !body.querySelector ) return; // check if response has selector let nextElem = body.querySelector( selector ); if ( !nextElem ) this.lastPageReached( body, path ); }; proto.lastPageReached = function( body, path ) { this.canLoad = false; this.dispatchEvent( 'last', null, [ body, path ] ); }; // ----- error ----- // proto.onPageError = function( error, path, response ) { this.isLoading = false; this.canLoad = false; this.dispatchEvent( 'error', null, [ error, path, response ] ); return error; }; // -------------------------- prefill -------------------------- // InfiniteScroll.create.prefill = function() { if ( !this.options.prefill ) return; let append = this.options.append; if ( !append ) { console.error(`append option required for prefill. Set as :${append}`); return; } this.updateMeasurements(); this.updateScroller(); this.isPrefilling = true; this.on( 'append', this.prefill ); this.once( 'error', this.stopPrefill ); this.once( 'last', this.stopPrefill ); this.prefill(); }; proto.prefill = function() { let distance = this.getPrefillDistance(); this.isPrefilling = distance >= 0; if ( this.isPrefilling ) { this.log('prefill'); this.loadNextPage(); } else { this.stopPrefill(); } }; proto.getPrefillDistance = function() { // element scroll if ( this.options.elementScroll ) { return this.scroller.clientHeight - this.scroller.scrollHeight; } // window return this.windowHeight - this.element.clientHeight; }; proto.stopPrefill = function() { this.log('stopPrefill'); this.off( 'append', this.prefill ); }; // -------------------------- -------------------------- // return InfiniteScroll; } ) ); // scroll-watch ( function( window, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('./core'), require('fizzy-ui-utils'), ); } else { // browser global factory( window, window.InfiniteScroll, window.fizzyUIUtils, ); } }( window, function factory( window, InfiniteScroll, utils ) { let proto = InfiniteScroll.prototype; // default options Object.assign( InfiniteScroll.defaults, { scrollThreshold: 400, // elementScroll: null, } ); InfiniteScroll.create.scrollWatch = function() { // events this.pageScrollHandler = this.onPageScroll.bind( this ); this.resizeHandler = this.onResize.bind( this ); let scrollThreshold = this.options.scrollThreshold; let isEnable = scrollThreshold || scrollThreshold === 0; if ( isEnable ) this.enableScrollWatch(); }; InfiniteScroll.destroy.scrollWatch = function() { this.disableScrollWatch(); }; proto.enableScrollWatch = function() { if ( this.isScrollWatching ) return; this.isScrollWatching = true; this.updateMeasurements(); this.updateScroller(); // TODO disable after error? this.on( 'last', this.disableScrollWatch ); this.bindScrollWatchEvents( true ); }; proto.disableScrollWatch = function() { if ( !this.isScrollWatching ) return; this.bindScrollWatchEvents( false ); delete this.isScrollWatching; }; proto.bindScrollWatchEvents = function( isBind ) { let addRemove = isBind ? 'addEventListener' : 'removeEventListener'; this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler ); window[ addRemove ]( 'resize', this.resizeHandler ); }; proto.onPageScroll = InfiniteScroll.throttle( function() { let distance = this.getBottomDistance(); if ( distance <= this.options.scrollThreshold ) { this.dispatchEvent('scrollThreshold'); } } ); proto.getBottomDistance = function() { let bottom, scrollY; if ( this.options.elementScroll ) { bottom = this.scroller.scrollHeight; scrollY = this.scroller.scrollTop + this.scroller.clientHeight; } else { bottom = this.top + this.element.clientHeight; scrollY = window.scrollY + this.windowHeight; } return bottom - scrollY; }; proto.onResize = function() { this.updateMeasurements(); }; utils.debounceMethod( InfiniteScroll, 'onResize', 150 ); // -------------------------- -------------------------- // return InfiniteScroll; } ) ); // history ( function( window, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('./core'), require('fizzy-ui-utils'), ); } else { // browser global factory( window, window.InfiniteScroll, window.fizzyUIUtils, ); } }( window, function factory( window, InfiniteScroll, utils ) { let proto = InfiniteScroll.prototype; Object.assign( InfiniteScroll.defaults, { history: 'replace', // historyTitle: false, } ); let link = document.createElement('a'); // ----- create/destroy ----- // InfiniteScroll.create.history = function() { if ( !this.options.history ) return; // check for same origin link.href = this.getAbsolutePath(); // MS Edge does not have origin on link // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/ let linkOrigin = link.origin || link.protocol + '//' + link.host; let isSameOrigin = linkOrigin == location.origin; if ( !isSameOrigin ) { console.error( '[InfiniteScroll] cannot set history with different origin: ' + `${link.origin} on ${location.origin} . History behavior disabled.` ); return; } // two ways to handle changing history if ( this.options.append ) { this.createHistoryAppend(); } else { this.createHistoryPageLoad(); } }; proto.createHistoryAppend = function() { this.updateMeasurements(); this.updateScroller(); // array of scroll positions of appended pages this.scrollPages = [ // first page { top: 0, path: location.href, title: document.title, }, ]; this.scrollPage = this.scrollPages[0]; // events this.scrollHistoryHandler = this.onScrollHistory.bind( this ); this.unloadHandler = this.onUnload.bind( this ); this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler ); this.on( 'append', this.onAppendHistory ); this.bindHistoryAppendEvents( true ); }; proto.bindHistoryAppendEvents = function( isBind ) { let addRemove = isBind ? 'addEventListener' : 'removeEventListener'; this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler ); window[ addRemove ]( 'unload', this.unloadHandler ); }; proto.createHistoryPageLoad = function() { this.on( 'load', this.onPageLoadHistory ); }; InfiniteScroll.destroy.history = proto.destroyHistory = function() { let isHistoryAppend = this.options.history && this.options.append; if ( isHistoryAppend ) { this.bindHistoryAppendEvents( false ); } }; // ----- append history ----- // proto.onAppendHistory = function( response, path, items ) { // do not proceed if no items. #779 if ( !items || !items.length ) return; let firstItem = items[0]; let elemScrollY = this.getElementScrollY( firstItem ); // resolve path link.href = path; // add page data to hash this.scrollPages.push({ top: elemScrollY, path: link.href, title: response.title, }); }; proto.getElementScrollY = function( elem ) { if ( this.options.elementScroll ) { return elem.offsetTop - this.top; } else { let rect = elem.getBoundingClientRect(); return rect.top + window.scrollY; } }; proto.onScrollHistory = function() { // cycle through positions, find biggest without going over let scrollPage = this.getClosestScrollPage(); // set history if changed if ( scrollPage != this.scrollPage ) { this.scrollPage = scrollPage; this.setHistory( scrollPage.title, scrollPage.path ); } }; utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 ); proto.getClosestScrollPage = function() { let scrollViewY; if ( this.options.elementScroll ) { scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2; } else { scrollViewY = window.scrollY + this.windowHeight / 2; } let scrollPage; for ( let page of this.scrollPages ) { if ( page.top >= scrollViewY ) break; scrollPage = page; } return scrollPage; }; proto.setHistory = function( title, path ) { let optHistory = this.options.history; let historyMethod = optHistory && history[ optHistory + 'State' ]; if ( !historyMethod ) return; history[ optHistory + 'State' ]( null, title, path ); if ( this.options.historyTitle ) document.title = title; this.dispatchEvent( 'history', null, [ title, path ] ); }; // scroll to top to prevent initial scroll-reset after page refresh // https://stackoverflow.com/a/18633915/182183 proto.onUnload = function() { if ( this.scrollPage.top === 0 ) return; // calculate where scroll position would be on refresh let scrollY = window.scrollY - this.scrollPage.top + this.top; // disable scroll event before setting scroll #679 this.destroyHistory(); scrollTo( 0, scrollY ); }; // ----- load history ----- // // update URL proto.onPageLoadHistory = function( response, path ) { this.setHistory( response.title, path ); }; // -------------------------- -------------------------- // return InfiniteScroll; } ) ); // button ( function( window, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('./core'), require('fizzy-ui-utils'), ); } else { // browser global factory( window, window.InfiniteScroll, window.fizzyUIUtils, ); } }( window, function factory( window, InfiniteScroll, utils ) { // -------------------------- InfiniteScrollButton -------------------------- // class InfiniteScrollButton { constructor( element, infScroll ) { this.element = element; this.infScroll = infScroll; // events this.clickHandler = this.onClick.bind( this ); this.element.addEventListener( 'click', this.clickHandler ); infScroll.on( 'request', this.disable.bind( this ) ); infScroll.on( 'load', this.enable.bind( this ) ); infScroll.on( 'error', this.hide.bind( this ) ); infScroll.on( 'last', this.hide.bind( this ) ); } onClick( event ) { event.preventDefault(); this.infScroll.loadNextPage(); } enable() { this.element.removeAttribute('disabled'); } disable() { this.element.disabled = 'disabled'; } hide() { this.element.style.display = 'none'; } destroy() { this.element.removeEventListener( 'click', this.clickHandler ); } } // -------------------------- InfiniteScroll methods -------------------------- // // InfiniteScroll.defaults.button = null; InfiniteScroll.create.button = function() { let buttonElem = utils.getQueryElement( this.options.button ); if ( buttonElem ) { this.button = new InfiniteScrollButton( buttonElem, this ); } }; InfiniteScroll.destroy.button = function() { if ( this.button ) this.button.destroy(); }; // -------------------------- -------------------------- // InfiniteScroll.Button = InfiniteScrollButton; return InfiniteScroll; } ) ); // status ( function( window, factory ) { // universal module definition if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('./core'), require('fizzy-ui-utils'), ); } else { // browser global factory( window, window.InfiniteScroll, window.fizzyUIUtils, ); } }( window, function factory( window, InfiniteScroll, utils ) { let proto = InfiniteScroll.prototype; // InfiniteScroll.defaults.status = null; InfiniteScroll.create.status = function() { let statusElem = utils.getQueryElement( this.options.status ); if ( !statusElem ) return; // elements this.statusElement = statusElem; this.statusEventElements = { request: statusElem.querySelector('.infinite-scroll-request'), error: statusElem.querySelector('.infinite-scroll-error'), last: statusElem.querySelector('.infinite-scroll-last'), }; // events this.on( 'request', this.showRequestStatus ); this.on( 'error', this.showErrorStatus ); this.on( 'last', this.showLastStatus ); this.bindHideStatus('on'); }; proto.bindHideStatus = function( bindMethod ) { let hideEvent = this.options.append ? 'append' : 'load'; this[ bindMethod ]( hideEvent, this.hideAllStatus ); }; proto.showRequestStatus = function() { this.showStatus('request'); }; proto.showErrorStatus = function() { this.showStatus('error'); }; proto.showLastStatus = function() { this.showStatus('last'); // prevent last then append event race condition from showing last status #706 this.bindHideStatus('off'); }; proto.showStatus = function( eventName ) { show( this.statusElement ); this.hideStatusEventElements(); let eventElem = this.statusEventElements[ eventName ]; show( eventElem ); }; proto.hideAllStatus = function() { hide( this.statusElement ); this.hideStatusEventElements(); }; proto.hideStatusEventElements = function() { for ( let type in this.statusEventElements ) { let eventElem = this.statusEventElements[ type ]; hide( eventElem ); } }; // -------------------------- -------------------------- // function hide( elem ) { setDisplay( elem, 'none' ); } function show( elem ) { setDisplay( elem, 'block' ); } function setDisplay( elem, value ) { if ( elem ) { elem.style.display = value; } } // -------------------------- -------------------------- // return InfiniteScroll; } ) ); /*! * imagesLoaded v4.1.4 * JavaScript is all like "You images are done yet or what?" * MIT License */ ( function( window, factory ) { 'use strict'; // universal module definition /*global define: false, module: false, require: false */ if ( typeof define == 'function' && define.amd ) { // AMD define( [ 'ev-emitter/ev-emitter' ], function( EvEmitter ) { return factory( window, EvEmitter ); }); } else if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('ev-emitter') ); } else { // browser global window.imagesLoaded = factory( window, window.EvEmitter ); } })( typeof window !== 'undefined' ? window : this, // -------------------------- factory -------------------------- // function factory( window, EvEmitter ) { 'use strict'; var $ = window.jQuery; var console = window.console; // -------------------------- helpers -------------------------- // // extend objects function extend( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; } var arraySlice = Array.prototype.slice; // turn element or nodeList into an array function makeArray( obj ) { if ( Array.isArray( obj ) ) { // use object if already an array return obj; } var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number'; if ( isArrayLike ) { // convert nodeList to array return arraySlice.call( obj ); } // array of single index return [ obj ]; } // -------------------------- imagesLoaded -------------------------- // /** * @param {Array, Element, NodeList, String} elem * @param {Object or Function} options - if function, use as callback * @param {Function} onAlways - callback function */ function ImagesLoaded( elem, options, onAlways ) { // coerce ImagesLoaded() without new, to be new ImagesLoaded() if ( !( this instanceof ImagesLoaded ) ) { return new ImagesLoaded( elem, options, onAlways ); } // use elem as selector string var queryElem = elem; if ( typeof elem == 'string' ) { queryElem = document.querySelectorAll( elem ); } // bail if bad element if ( !queryElem ) { console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) ); return; } this.elements = makeArray( queryElem ); this.options = extend( {}, this.options ); // shift arguments if no options set if ( typeof options == 'function' ) { onAlways = options; } else { extend( this.options, options ); } if ( onAlways ) { this.on( 'always', onAlways ); } this.getImages(); if ( $ ) { // add jQuery Deferred object this.jqDeferred = new $.Deferred(); } // HACK check async to allow time to bind listeners setTimeout( this.check.bind( this ) ); } ImagesLoaded.prototype = Object.create( EvEmitter.prototype ); ImagesLoaded.prototype.options = {}; ImagesLoaded.prototype.getImages = function() { this.images = []; // filter & find items if we have an item selector this.elements.forEach( this.addElementImages, this ); }; /** * @param {Node} element */ ImagesLoaded.prototype.addElementImages = function( elem ) { // filter siblings if ( elem.nodeName == 'IMG' ) { this.addImage( elem ); } // get background image on element if ( this.options.background === true ) { this.addElementBackgroundImages( elem ); } // find children // no non-element nodes, #143 var nodeType = elem.nodeType; if ( !nodeType || !elementNodeTypes[ nodeType ] ) { return; } var childImgs = elem.querySelectorAll('img'); // concat childElems to filterFound array for ( var i=0; i < childImgs.length; i++ ) { var img = childImgs[i]; this.addImage( img ); } // get child background images if ( typeof this.options.background == 'string' ) { var children = elem.querySelectorAll( this.options.background ); for ( i=0; i < children.length; i++ ) { var child = children[i]; this.addElementBackgroundImages( child ); } } }; var elementNodeTypes = { 1: true, 9: true, 11: true }; ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) { var style = getComputedStyle( elem ); if ( !style ) { // Firefox returns null if in a hidden iframe https://bugzil.la/548397 return; } // get url inside url("...") var reURL = /url\((['"])?(.*?)\1\)/gi; var matches = reURL.exec( style.backgroundImage ); while ( matches !== null ) { var url = matches && matches[2]; if ( url ) { this.addBackground( url, elem ); } matches = reURL.exec( style.backgroundImage ); } }; /** * @param {Image} img */ ImagesLoaded.prototype.addImage = function( img ) { var loadingImage = new LoadingImage( img ); this.images.push( loadingImage ); }; ImagesLoaded.prototype.addBackground = function( url, elem ) { var background = new Background( url, elem ); this.images.push( background ); }; ImagesLoaded.prototype.check = function() { var _this = this; this.progressedCount = 0; this.hasAnyBroken = false; // complete if no images if ( !this.images.length ) { this.complete(); return; } function onProgress( image, elem, message ) { // HACK - Chrome triggers event before object properties have changed. #83 setTimeout( function() { _this.progress( image, elem, message ); }); } this.images.forEach( function( loadingImage ) { loadingImage.once( 'progress', onProgress ); loadingImage.check(); }); }; ImagesLoaded.prototype.progress = function( image, elem, message ) { this.progressedCount++; this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded; // progress event this.emitEvent( 'progress', [ this, image, elem ] ); if ( this.jqDeferred && this.jqDeferred.notify ) { this.jqDeferred.notify( this, image ); } // check if completed if ( this.progressedCount == this.images.length ) { this.complete(); } if ( this.options.debug && console ) { console.log( 'progress: ' + message, image, elem ); } }; ImagesLoaded.prototype.complete = function() { var eventName = this.hasAnyBroken ? 'fail' : 'done'; this.isComplete = true; this.emitEvent( eventName, [ this ] ); this.emitEvent( 'always', [ this ] ); if ( this.jqDeferred ) { var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve'; this.jqDeferred[ jqMethod ]( this ); } }; // -------------------------- -------------------------- // function LoadingImage( img ) { this.img = img; } LoadingImage.prototype = Object.create( EvEmitter.prototype ); LoadingImage.prototype.check = function() { // If complete is true and browser supports natural sizes, // try to check for image status manually. var isComplete = this.getIsImageComplete(); if ( isComplete ) { // report based on naturalWidth this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); return; } // If none of the checks above matched, simulate loading on detached element. this.proxyImage = new Image(); this.proxyImage.addEventListener( 'load', this ); this.proxyImage.addEventListener( 'error', this ); // bind to image as well for Firefox. #191 this.img.addEventListener( 'load', this ); this.img.addEventListener( 'error', this ); this.proxyImage.src = this.img.src; }; LoadingImage.prototype.getIsImageComplete = function() { // check for non-zero, non-undefined naturalWidth // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671 return this.img.complete && this.img.naturalWidth; }; LoadingImage.prototype.confirm = function( isLoaded, message ) { this.isLoaded = isLoaded; this.emitEvent( 'progress', [ this, this.img, message ] ); }; // ----- events ----- // // trigger specified handler for event type LoadingImage.prototype.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; LoadingImage.prototype.onload = function() { this.confirm( true, 'onload' ); this.unbindEvents(); }; LoadingImage.prototype.onerror = function() { this.confirm( false, 'onerror' ); this.unbindEvents(); }; LoadingImage.prototype.unbindEvents = function() { this.proxyImage.removeEventListener( 'load', this ); this.proxyImage.removeEventListener( 'error', this ); this.img.removeEventListener( 'load', this ); this.img.removeEventListener( 'error', this ); }; // -------------------------- Background -------------------------- // function Background( url, element ) { this.url = url; this.element = element; this.img = new Image(); } // inherit LoadingImage prototype Background.prototype = Object.create( LoadingImage.prototype ); Background.prototype.check = function() { this.img.addEventListener( 'load', this ); this.img.addEventListener( 'error', this ); this.img.src = this.url; // check if image is already complete var isComplete = this.getIsImageComplete(); if ( isComplete ) { this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); this.unbindEvents(); } }; Background.prototype.unbindEvents = function() { this.img.removeEventListener( 'load', this ); this.img.removeEventListener( 'error', this ); }; Background.prototype.confirm = function( isLoaded, message ) { this.isLoaded = isLoaded; this.emitEvent( 'progress', [ this, this.element, message ] ); }; // -------------------------- jQuery -------------------------- // ImagesLoaded.makeJQueryPlugin = function( jQuery ) { jQuery = jQuery || window.jQuery; if ( !jQuery ) { return; } // set local variable $ = jQuery; // $().imagesLoaded() $.fn.imagesLoaded = function( options, callback ) { var instance = new ImagesLoaded( this, options, callback ); return instance.jqDeferred.promise( $(this) ); }; }; // try making plugin ImagesLoaded.makeJQueryPlugin(); // -------------------------- -------------------------- // return ImagesLoaded; });