PageSourceSearch

https://mea-living.de/templates/SALEPIX/js/plugins/infinite-scroll.pkgd.js

js mea-living.de collected 2026-09-24 21:38:12 UTC 59,925 bytes, 1,909 lines download raw bytes

vendor: 10,938 bytes, lines 1-381
1/*!
2 * Infinite Scroll PACKAGED v4.0.1
3 * Automatically add next page
4 *
5 * Licensed GPLv3 for open source use
6 * or Infinite Scroll Commercial License for commercial use
7 *
8 * https://infinite-scroll.com
9 * Copyright 2018-2020 Metafizzy
10 */
11
12/**
13 * Bridget makes jQuery widgets
14 * v3.0.0
15 * MIT license
16 */
17
18( function( window, factory ) {
19    // module definition
20    if ( typeof module == 'object' && module.exports ) {
21        // CommonJS
22        module.exports = factory(
23            window,
24            require('jquery'),
25        );
26    } else {
27        // browser global
28        window.jQueryBridget = factory(
29            window,
30            window.jQuery,
31        );
32    }
33
34}( window, function factory( window, jQuery ) {
35
36// ----- utils ----- //
37
38// helper function for logging errors
39// $.error breaks jQuery chaining
40    let console = window.console;
41    let logError = typeof console == 'undefined' ? function() {} :
42        function( message ) {
43            console.error( message );
44        };
45
46// ----- jQueryBridget ----- //
47
48    function jQueryBridget( namespace, PluginClass, $ ) {
49        $ = $ || jQuery || window.jQuery;
50        if ( !$ ) {
51            return;
52        }
53
54        // add option method -> $().plugin('option', {...})
55        if ( !PluginClass.prototype.option ) {
56            // option setter
57            PluginClass.prototype.option = function( opts ) {
58                if ( !opts ) return;
59
60                this.options = Object.assign( this.options || {}, opts );
61            };
62        }
63
64        // make jQuery plugin
65        $.fn[ namespace ] = function( arg0, ...args ) {
66            if ( typeof arg0 == 'string' ) {
67                // method call $().plugin( 'methodName', { options } )
68                return methodCall( this, arg0, args );
69            }
70            // just $().plugin({ options })
71            plainCall( this, arg0 );
72            return this;
73        };
74
75        // $().plugin('methodName')
76        function methodCall( $elems, methodName, args ) {
77            let returnValue;
78            let pluginMethodStr = `$().${namespace}("${methodName}")`;
79
80            $elems.each( function( i, elem ) {
81                // get instance
82                let instance = $.data( elem, namespace );
83                if ( !instance ) {
84                    logError( `${namespace} not initialized.` +
85                        ` Cannot call method ${pluginMethodStr}` );
86                    return;
87                }
88
89                let method = instance[ methodName ];
90                if ( !method || methodName.charAt( 0 ) == '_' ) {
91                    logError(`${pluginMethodStr} is not a valid method`);
92                    return;
93                }
94
95                // apply method, get return value
96                let value = method.apply( instance, args );
97                // set return value if value is returned, use only first value
98                returnValue = returnValue === undefined ? value : returnValue;
99            } );
100
101            return returnValue !== undefined ? returnValue : $elems;
102        }
103
104        function plainCall( $elems, options ) {
105            $elems.each( function( i, elem ) {
106                let instance = $.data( elem, namespace );
107                if ( instance ) {
108                    // set options & init
109                    instance.option( options );
110                    instance._init();
111                } else {
112                    // initialize new instance
113                    instance = new PluginClass( elem, options );
114                    $.data( elem, namespace, instance );
115                }
116            } );
117        }
118
119    }
120
121// -----  ----- //
122
123    return jQueryBridget;
124
125} ) );
126/**
127 * EvEmitter v2.0.0
128 * Lil' event emitter
129 * MIT License
130 */
131
132( function( global, factory ) {
133    // universal module definition
134    if ( typeof module == 'object' && module.exports ) {
135        // CommonJS - Browserify, Webpack
136        module.exports = factory();
137    } else {
138        // Browser globals
139        global.EvEmitter = factory();
140    }
141
142}( typeof window != 'undefined' ? window : this, function() {
143
144    function EvEmitter() {}
145
146    let proto = EvEmitter.prototype;
147
148    proto.on = function( eventName, listener ) {
149        if ( !eventName || !listener ) return this;
150
151        // set events hash
152        let events = this._events = this._events || {};
153        // set listeners array
154        let listeners = events[ eventName ] = events[ eventName ] || [];
155        // only add once
156        if ( !listeners.includes( listener ) ) {
157            listeners.push( listener );
158        }
159
160        return this;
161    };
162
163    proto.once = function( eventName, listener ) {
164        if ( !eventName || !listener ) return this;
165
166        // add event
167        this.on( eventName, listener );
168        // set once flag
169        // set onceEvents hash
170        let onceEvents = this._onceEvents = this._onceEvents || {};
171        // set onceListeners object
172        let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
173        // set flag
174        onceListeners[ listener ] = true;
175
176        return this;
177    };
178
179    proto.off = function( eventName, listener ) {
180        let listeners = this._events && this._events[ eventName ];
181        if ( !listeners || !listeners.length ) return this;
182
183        let index = listeners.indexOf( listener );
184        if ( index != -1 ) {
185            listeners.splice( index, 1 );
186        }
187
188        return this;
189    };
190
191    proto.emitEvent = function( eventName, args ) {
192        let listeners = this._events && this._events[ eventName ];
193        if ( !listeners || !listeners.length ) return this;
194
195        // copy over to avoid interference if .off() in listener
196        listeners = listeners.slice( 0 );
197        args = args || [];
198        // once stuff
199        let onceListeners = this._onceEvents && this._onceEvents[ eventName ];
200
201        for ( let listener of listeners ) {
202            let isOnce = onceListeners && onceListeners[ listener ];
203            if ( isOnce ) {
204                // remove listener
205                // remove before trigger to prevent recursion
206                this.off( eventName, listener );
207                // unset once flag
208                delete onceListeners[ listener ];
209            }
210            // trigger listener
211            listener.apply( this, args );
212        }
213
214        return this;
215    };
216
217    proto.allOff = function() {
218        delete this._events;
219        delete this._onceEvents;
220        return this;
221    };
222
223    return EvEmitter;
224
225} ) );
226/**
227 * Fizzy UI utils v3.0.0
228 * MIT license
229 */
230
231( function( global, factory ) {
232    // universal module definition
233    if ( typeof module == 'object' && module.exports ) {
234        // CommonJS
235        module.exports = factory( global );
236    } else {
237        // browser global
238        global.fizzyUIUtils = factory( global );
239    }
240
241}( this, function factory( global ) {
242
243    let utils = {};
244
245// ----- extend ----- //
246
247// extends objects
248    utils.extend = function( a, b ) {
249        return Object.assign( a, b );
250    };
251
252// ----- modulo ----- //
253
254    utils.modulo = function( num, div ) {
255        return ( ( num % div ) + div ) % div;
256    };
257
258// ----- makeArray ----- //
259
260// turn element or nodeList into an array
261    utils.makeArray = function( obj ) {
262        // use object if already an array
263        if ( Array.isArray( obj ) ) return obj;
264
265        // return empty array if undefined or null. #6
266        if ( obj === null || obj === undefined ) return [];
267
268        let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
269        // convert nodeList to array
270        if ( isArrayLike ) return [ ...obj ];
271
272        // array of single index
273        return [ obj ];
274    };
275
276// ----- removeFrom ----- //
277
278    utils.removeFrom = function( ary, obj ) {
279        let index = ary.indexOf( obj );
280        if ( index != -1 ) {
281            ary.splice( index, 1 );
282        }
283    };
284
285// ----- getParent ----- //
286
287    utils.getParent = function( elem, selector ) {
288        while ( elem.parentNode && elem != document.body ) {
289            elem = elem.parentNode;
290            if ( elem.matches( selector ) ) return elem;
291        }
292    };
293
294// ----- getQueryElement ----- //
295
296// use element as selector string
297    utils.getQueryElement = function( elem ) {
298        if ( typeof elem == 'string' ) {
299            return document.querySelector( elem );
300        }
301        return elem;
302    };
303
304// ----- handleEvent ----- //
305
306// enable .ontype to trigger from .addEventListener( elem, 'type' )
307    utils.handleEvent = function( event ) {
308        let method = 'on' + event.type;
309        if ( this[ method ] ) {
310            this[ method ]( event );
311        }
312    };
313
314// ----- filterFindElements ----- //
315
316    utils.filterFindElements = function( elems, selector ) {
317        // make array of elems
318        elems = utils.makeArray( elems );
319
320        return elems
321        // check that elem is an actual element
322            .filter( ( elem ) => elem instanceof HTMLElement )
323            .reduce( ( ffElems, elem ) => {
324                // add elem if no selector
325                if ( !selector ) {
326                    ffElems.push( elem );
327                    return ffElems;
328                }
329                // filter & find items if we have a selector
330                // filter
331                if ( elem.matches( selector ) ) {
332                    ffElems.push( elem );
333                }
334                // find children
335                let childElems = elem.querySelectorAll( selector );
336                // concat childElems to filterFound array
337                ffElems = ffElems.concat( ...childElems );
338                return ffElems;
339            }, [] );
340    };
341
342// ----- debounceMethod ----- //
343
344    utils.debounceMethod = function( _class, methodName, threshold ) {
345        threshold = threshold || 100;
346        // original method
347        let method = _class.prototype[ methodName ];
348        let timeoutName = methodName + 'Timeout';
349
350        _class.prototype[ methodName ] = function() {
351            clearTimeout( this[ timeoutName ] );
352
353            let args = arguments;
354            this[ timeoutName ] = setTimeout( () => {
355                method.apply( this, args );
356                delete this[ timeoutName ];
357            }, threshold );
358        };
359    };
360
361// ----- docReady ----- //
362
363    utils.docReady = function( onDocReady ) {
364        let readyState = document.readyState;
365        if ( readyState == 'complete' || readyState == 'interactive' ) {
366            // do async to allow for other scripts to run. metafizzy/flickity#441
367            setTimeout( onDocReady );
368        } else {
369            document.addEventListener( 'DOMContentLoaded', onDocReady );
370        }
371    };
372
373// ----- htmlInit ----- //
374
375// http://bit.ly/3oYLusc
376    utils.toDashed = function( str ) {
377        return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
378            return $1 + '-' + $2;
379        } ).toLowerCase();
380    };
381
vendor: 41,312 bytes, lines 382-1693
382    let console = global.console;
383
384// allow user to initialize classes via [data-namespace] or .js-namespace class
385// htmlInit( Widget, 'widgetName' )
386// options are parsed from data-namespace-options
387    utils.htmlInit = function( WidgetClass, namespace ) {
388        utils.docReady( function() {
389            let dashedNamespace = utils.toDashed( namespace );
390            let dataAttr = 'data-' + dashedNamespace;
391            let dataAttrElems = document.querySelectorAll( `[${dataAttr}]` );
392            let jQuery = global.jQuery;
393
394            [ ...dataAttrElems ].forEach( ( elem ) => {
395                let attr = elem.getAttribute( dataAttr );
396                let options;
397                try {
398                    options = attr && JSON.parse( attr );
399                } catch ( error ) {
400                    // log error, do not initialize
401                    if ( console ) {
402                        console.error( `Error parsing ${dataAttr} on ${elem.className}: ${error}` );
403                    }
404                    return;
405                }
406                // initialize
407                let instance = new WidgetClass( elem, options );
408                // make available via $().data('namespace')
409                if ( jQuery ) {
410                    jQuery.data( elem, namespace, instance );
411                }
412            } );
413
414        } );
415    };
416
417// -----  ----- //
418
419    return utils;
420
421} ) );
422// core
423( function( window, factory ) {
424    // universal module definition
425    if ( typeof module == 'object' && module.exports ) {
426        // CommonJS
427        module.exports = factory(
428            window,
429            require('ev-emitter'),
430            require('fizzy-ui-utils'),
431        );
432    } else {
433        // browser global
434        window.InfiniteScroll = factory(
435            window,
436            window.EvEmitter,
437            window.fizzyUIUtils,
438        );
439    }
440
441}( window, function factory( window, EvEmitter, utils ) {
442
443    let jQuery = window.jQuery;
444// internal store of all InfiniteScroll intances
445    let instances = {};
446
447    function InfiniteScroll( element, options ) {
448        let queryElem = utils.getQueryElement( element );
449
450        if ( !queryElem ) {
451            console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) );
452            return;
453        }
454        element = queryElem;
455        // do not initialize twice on same element
456        if ( element.infiniteScrollGUID ) {
457            let instance = instances[ element.infiniteScrollGUID ];
458            instance.option( options );
459            return instance;
460        }
461
462        this.element = element;
463        // options
464        this.options = { ...InfiniteScroll.defaults };
465        this.option( options );
466        // add jQuery
467        if ( jQuery ) {
468            this.$element = jQuery( this.element );
469        }
470
471        this.create();
472    }
473
474// defaults
475    InfiniteScroll.defaults = {
476        // path: null,
477        // hideNav: null,
478        // debug: false,
479    };
480
481// create & destroy methods
482    InfiniteScroll.create = {};
483    InfiniteScroll.destroy = {};
484
485    let proto = InfiniteScroll.prototype;
486// inherit EvEmitter
487    Object.assign( proto, EvEmitter.prototype );
488
489// --------------------------  -------------------------- //
490
491// globally unique identifiers
492    let GUID = 0;
493
494    proto.create = function() {
495        // create core
496        // add id for InfiniteScroll.data
497        let id = this.guid = ++GUID;
498        this.element.infiniteScrollGUID = id; // expando
499        instances[ id ] = this; // associate via id
500        // properties
501        this.pageIndex = 1; // default to first page
502        this.loadCount = 0;
503        this.updateGetPath();
504        // bail if getPath not set, or returns falsey #776
505        let hasPath = this.getPath && this.getPath();
506        if ( !hasPath ) {
507            console.error('Disabling InfiniteScroll');
508            return;
509        }
510        this.updateGetAbsolutePath();
511        this.log( 'initialized', [ this.element.className ] );
512        this.callOnInit();
513        // create features
514        for ( let method in InfiniteScroll.create ) {
515            InfiniteScroll.create[ method ].call( this );
516        }
517    };
518
519    proto.option = function( opts ) {
520        Object.assign( this.options, opts );
521    };
522
523// call onInit option, used for binding events on init
524    proto.callOnInit = function() {
525        let onInit = this.options.onInit;
526        if ( onInit ) {
527            onInit.call( this, this );
528        }
529    };
530
531// ----- events ----- //
532
533    proto.dispatchEvent = function( type, event, args ) {
534        this.log( type, args );
535        let emitArgs = event ? [ event ].concat( args ) : args;
536        this.emitEvent( type, emitArgs );
537        // trigger jQuery event
538        if ( !jQuery || !this.$element ) {
539            return;
540        }
541        // namespace jQuery event
542        type += '.infiniteScroll';
543        let $event = type;
544        if ( event ) {
545            // create jQuery event
546            /* eslint-disable-next-line new-cap */
547            let jQEvent = jQuery.Event( event );
548            jQEvent.type = type;
549            $event = jQEvent;
550        }
551        this.$element.trigger( $event, args );
552    };
553
554    let loggers = {
555        initialized: ( className ) => `on ${className}`,
556        request: ( path ) => `URL: ${path}`,
557        load: ( response, path ) => `${response.title || ''}. URL: ${path}`,
558        error: ( error, path ) => `${error}. URL: ${path}`,
559        append: ( response, path, items ) => `${items.length} items. URL: ${path}`,
560        last: ( response, path ) => `URL: ${path}`,
561        history: ( title, path ) => `URL: ${path}`,
562        pageIndex: function( index, origin ) {
563            return `current page determined to be: ${index} from ${origin}`;
564        },
565    };
566
567// log events
568    proto.log = function( type, args ) {
569        if ( !this.options.debug ) return;
570
571        let message = `[InfiniteScroll] ${type}`;
572        let logger = loggers[ type ];
573        if ( logger ) message += '. ' + logger.apply( this, args );
574        console.log( message );
575    };
576
577// -------------------------- methods used amoung features -------------------------- //
578
579    proto.updateMeasurements = function() {
580        this.windowHeight = window.innerHeight;
581        let rect = this.element.getBoundingClientRect();
582        this.top = rect.top + window.scrollY;
583    };
584
585    proto.updateScroller = function() {
586        let elementScroll = this.options.elementScroll;
587        if ( !elementScroll ) {
588            // default, use window
589            this.scroller = window;
590            return;
591        }
592        // if true, set to element, otherwise use option
593        this.scroller = elementScroll === true ? this.element :
594            utils.getQueryElement( elementScroll );
595        if ( !this.scroller ) {
596            throw new Error(`Unable to find elementScroll: ${elementScroll}`);
597        }
598    };
599
600// -------------------------- page path -------------------------- //
601
602    proto.updateGetPath = function() {
603        let optPath = this.options.path;
604        if ( !optPath ) {
605            console.error(`InfiniteScroll path option required. Set as: ${optPath}`);
606            return;
607        }
608        // function
609        let type = typeof optPath;
610        if ( type == 'function' ) {
611            this.getPath = optPath;
612            return;
613        }
614        // template string: '/pages/{{#}}.html'
615        let templateMatch = type == 'string' && optPath.match('{{#}}');
616        if ( templateMatch ) {
617            this.updateGetPathTemplate( optPath );
618            return;
619        }
620        // selector: '.next-page-selector'
621        this.updateGetPathSelector( optPath );
622    };
623
624    proto.updateGetPathTemplate = function( optPath ) {
625        // set getPath with template string
626        this.getPath = () => {
627            let nextIndex = this.pageIndex + 1;
628            return optPath.replace( '{{#}}', nextIndex );
629        };
630        // get pageIndex from location
631        // convert path option into regex to look for pattern in location
632        // escape query (?) in url, allows for parsing GET parameters
633        let regexString = optPath
634            .replace( /(\\\?|\?)/, '\\?' )
635            .replace( '{{#}}', '(\\d\\d?\\d?)' );
636        let templateRe = new RegExp( regexString );
637        let match = location.href.match( templateRe );
638
639        if ( match ) {
640            this.pageIndex = parseInt( match[1], 10 );
641            this.log( 'pageIndex', [ this.pageIndex, 'template string' ] );
642        }
643    };
644
645    let pathRegexes = [
646        // WordPress & Tumblr - example.com/page/2
647        // Jekyll - example.com/page2
648        /^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/,
649        // Drupal - example.com/?page=1
650        /^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/,
651        // catch all, last occurence of a number
652        /(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/,
653    ];
654
655// try matching href to pathRegexes patterns
656    let getPathParts = InfiniteScroll.getPathParts = function( href ) {
657        if ( !href ) return;
658        for ( let regex of pathRegexes ) {
659            let match = href.match( regex );
660            if ( match ) {
661                let [ , begin, index, end ] = match;
662                return { begin, index, end };
663            }
664        }
665    };
666
667    proto.updateGetPathSelector = function( optPath ) {
668        // parse href of link: '.next-page-link'
669        let hrefElem = document.querySelector( optPath );
670        if ( !hrefElem ) {
671            console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`);
672            return;
673        }
674
675        let href = hrefElem.getAttribute('href');
676        let pathParts = getPathParts( href );
677        if ( !pathParts ) {
678            console.error(`InfiniteScroll unable to parse next link href: ${href}`);
679            return;
680        }
681
682        let { begin, index, end } = pathParts;
683        this.isPathSelector = true; // flag for checkLastPage()
684        this.getPath = () => begin + ( this.pageIndex + 1 ) + end;
685        // get pageIndex from href
686        this.pageIndex = parseInt( index, 10 ) - 1;
687        this.log( 'pageIndex', [ this.pageIndex, 'next link' ] );
688    };
689
690    proto.updateGetAbsolutePath = function() {
691        let path = this.getPath();
692        // path doesn't start with http or /
693        let isAbsolute = path.match( /^http/ ) || path.match( /^\// );
694        if ( isAbsolute ) {
695            this.getAbsolutePath = this.getPath;
696            return;
697        }
698
699        let { pathname } = location;
700        // query parameter #829. example.com/?pg=2
701        let isQuery = path.match( /^\?/ );
702        // /foo/bar/index.html => /foo/bar
703        let directory = pathname.substring( 0, pathname.lastIndexOf('/') );
704        let pathStart = isQuery ? pathname : directory + '/';
705
706        this.getAbsolutePath = () => pathStart + this.getPath();
707    };
708
709// -------------------------- nav -------------------------- //
710
711// hide navigation
712    InfiniteScroll.create.hideNav = function() {
713        let nav = utils.getQueryElement( this.options.hideNav );
714        if ( !nav ) return;
715
716        nav.style.display = 'none';
717        this.nav = nav;
718    };
719
720    InfiniteScroll.destroy.hideNav = function() {
721        if ( this.nav ) this.nav.style.display = '';
722    };
723
724// -------------------------- destroy -------------------------- //
725
726    proto.destroy = function() {
727        this.allOff(); // remove all event listeners
728        // call destroy methods
729        for ( let method in InfiniteScroll.destroy ) {
730            InfiniteScroll.destroy[ method ].call( this );
731        }
732
733        delete this.element.infiniteScrollGUID;
734        delete instances[ this.guid ];
735        // remove jQuery data. #807
736        if ( jQuery && this.$element ) {
737            jQuery.removeData( this.element, 'infiniteScroll' );
738        }
739    };
740
741// -------------------------- utilities -------------------------- //
742
743// https://remysharp.com/2010/07/21/throttling-function-calls
744    InfiniteScroll.throttle = function( fn, threshold ) {
745        threshold = threshold || 200;
746        let last, timeout;
747
748        return function() {
749            let now = +new Date();
750            let args = arguments;
751            let trigger = () => {
752                last = now;
753                fn.apply( this, args );
754            };
755            if ( last && now < last + threshold ) {
756                // hold on to it
757                clearTimeout( timeout );
758                timeout = setTimeout( trigger, threshold );
759            } else {
760                trigger();
761            }
762        };
763    };
764
765    InfiniteScroll.data = function( elem ) {
766        elem = utils.getQueryElement( elem );
767        let id = elem && elem.infiniteScrollGUID;
768        return id && instances[ id ];
769    };
770
771// set internal jQuery, for Webpack + jQuery v3
772    InfiniteScroll.setJQuery = function( jqry ) {
773        jQuery = jqry;
774    };
775
776// -------------------------- setup -------------------------- //
777
778    utils.htmlInit( InfiniteScroll, 'infinite-scroll' );
779
780// add noop _init method for jQuery Bridget. #768
781    proto._init = function() {};
782
783    let { jQueryBridget } = window;
784    if ( jQuery && jQueryBridget ) {
785        jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery );
786    }
787
788// --------------------------  -------------------------- //
789
790    return InfiniteScroll;
791
792} ) );
793// page-load
794( function( window, factory ) {
795    // universal module definition
796    if ( typeof module == 'object' && module.exports ) {
797        // CommonJS
798        module.exports = factory(
799            window,
800            require('./core'),
801        );
802    } else {
803        // browser global
804        factory(
805            window,
806            window.InfiniteScroll,
807        );
808    }
809
810}( window, function factory( window, InfiniteScroll ) {
811
812    let proto = InfiniteScroll.prototype;
813
814    Object.assign( InfiniteScroll.defaults, {
815        // append: false,
816        loadOnScroll: true,
817        checkLastPage: true,
818        responseBody: 'text',
819        domParseResponse: true,
820        // prefill: false,
821        // outlayer: null,
822    } );
823
824    InfiniteScroll.create.pageLoad = function() {
825        this.canLoad = true;
826        this.on( 'scrollThreshold', this.onScrollThresholdLoad );
827        this.on( 'load', this.checkLastPage );
828        if ( this.options.outlayer ) {
829            this.on( 'append', this.onAppendOutlayer );
830        }
831    };
832
833    proto.onScrollThresholdLoad = function() {
834        if ( this.options.loadOnScroll ) this.loadNextPage();
835    };
836
837    let domParser = new DOMParser();
838
839    proto.loadNextPage = function() {
840        if ( this.isLoading || !this.canLoad ) return;
841
842        let { responseBody, domParseResponse, fetchOptions } = this.options;
843        let path = this.getAbsolutePath();
844        this.isLoading = true;
845        if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions();
846
847        let fetchPromise = fetch( path, fetchOptions )
848            .then( ( response ) => {
849                if ( !response.ok ) {
850                    let error = new Error( response.statusText );
851                    this.onPageError( error, path, response );
852                    return { response };
853                }
854
855                return response[ responseBody ]().then( ( body ) => {
856                    let canDomParse = responseBody == 'text' && domParseResponse;
857                    if ( canDomParse ) {
858                        body = domParser.parseFromString( body, 'text/html' );
859                    }
860                    if ( response.status == 204 ) {
861                        this.lastPageReached( body, path );
862                        return { body, response };
863                    } else {
864                        return this.onPageLoad( body, path, response );
865                    }
866                } );
867            } )
868            .catch( ( error ) => {
869                this.onPageError( error, path );
870            } );
871
872        this.dispatchEvent( 'request', null, [ path, fetchPromise ] );
873
874        return fetchPromise;
875    };
876
877    proto.onPageLoad = function( body, path, response ) {
878        // done loading if not appending
879        if ( !this.options.append ) {
880            this.isLoading = false;
881        }
882        this.pageIndex++;
883        this.loadCount++;
884        this.dispatchEvent( 'load', null, [ body, path, response ] );
885        return this.appendNextPage( body, path, response );
886    };
887
888    proto.appendNextPage = function( body, path, response ) {
889        let { append, responseBody, domParseResponse } = this.options;
890        // do not append json
891        let isDocument = responseBody == 'text' && domParseResponse;
892        if ( !isDocument || !append ) return { body, response };
893
894        let items = body.querySelectorAll( append );
895        let promiseValue = { body, response, items };
896        // last page hit if no items. #840
897        if ( !items || !items.length ) {
898            this.lastPageReached( body, path );
899            return promiseValue;
900        }
901
902        let fragment = getItemsFragment( items );
903        let appendReady = () => {
904            this.appendItems( items, fragment );
905            this.isLoading = false;
906            this.dispatchEvent( 'append', null, [ body, path, items, response ] );
907            return promiseValue;
908        };
909
910        // TODO add hook for option to trigger appendReady
911        if ( this.options.outlayer ) {
912            return this.appendOutlayerItems( fragment, appendReady );
913        } else {
914            return appendReady();
915        }
916    };
917
918    proto.appendItems = function( items, fragment ) {
919        if ( !items || !items.length ) return;
920
921        // get fragment if not provided
922        fragment = fragment || getItemsFragment( items );
923        refreshScripts( fragment );
924        this.element.appendChild( fragment );
925    };
926
927    function getItemsFragment( items ) {
928        // add items to fragment
929        let fragment = document.createDocumentFragment();
930        if ( items ) fragment.append( ...items );
931        return fragment;
932    }
933
934// replace <script>s with copies so they load
935// <script>s added by InfiniteScroll will not load
936// similar to https://stackoverflow.com/questions/610995
937    function refreshScripts( fragment ) {
938        let scripts = fragment.querySelectorAll('script');
939        for ( let script of scripts ) {
940            let freshScript = document.createElement('script');
941            // copy attributes
942            let attrs = script.attributes;
943            for ( let attr of attrs ) {
944                freshScript.setAttribute( attr.name, attr.value );
945            }
946            // copy inner script code. #718, #782
947            freshScript.innerHTML = script.innerHTML;
948            script.parentNode.replaceChild( freshScript, script );
949        }
950    }
951
952// ----- outlayer ----- //
953
954    proto.appendOutlayerItems = function( fragment, appendReady ) {
955        let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded;
956        if ( !imagesLoaded ) {
957            console.error('[InfiniteScroll] imagesLoaded required for outlayer option');
958            this.isLoading = false;
959            return;
960        }
961        // append once images loaded
962        return new Promise( function( resolve ) {
963            imagesLoaded( fragment, function() {
964                let bodyResponse = appendReady();
965                resolve( bodyResponse );
966            } );
967        } );
968    };
969
970    proto.onAppendOutlayer = function( response, path, items ) {
971        this.options.outlayer.appended( items );
972    };
973
974// ----- checkLastPage ----- //
975
976// check response for next element
977    proto.checkLastPage = function( body, path ) {
978        let { checkLastPage, path: pathOpt } = this.options;
979        if ( !checkLastPage ) return;
980
981        // if path is function, check if next path is truthy
982        if ( typeof pathOpt == 'function' ) {
983            let nextPath = this.getPath();
984            if ( !nextPath ) {
985                this.lastPageReached( body, path );
986                return;
987            }
988        }
989        // get selector from checkLastPage or path option
990        let selector;
991        if ( typeof checkLastPage == 'string' ) {
992            selector = checkLastPage;
993        } else if ( this.isPathSelector ) {
994            // path option is selector string
995            selector = pathOpt;
996        }
997        // check last page for selector
998        // bail if no selector or not document response
999        if ( !selector || !body.querySelector ) return;
1000
1001        // check if response has selector
1002        let nextElem = body.querySelector( selector );
1003        if ( !nextElem ) this.lastPageReached( body, path );
1004    };
1005
1006    proto.lastPageReached = function( body, path ) {
1007        this.canLoad = false;
1008        this.dispatchEvent( 'last', null, [ body, path ] );
1009    };
1010
1011// ----- error ----- //
1012
1013    proto.onPageError = function( error, path, response ) {
1014        this.isLoading = false;
1015        this.canLoad = false;
1016        this.dispatchEvent( 'error', null, [ error, path, response ] );
1017        return error;
1018    };
1019
1020// -------------------------- prefill -------------------------- //
1021
1022    InfiniteScroll.create.prefill = function() {
1023        if ( !this.options.prefill ) return;
1024
1025        let append = this.options.append;
1026        if ( !append ) {
1027            console.error(`append option required for prefill. Set as :${append}`);
1028            return;
1029        }
1030        this.updateMeasurements();
1031        this.updateScroller();
1032        this.isPrefilling = true;
1033        this.on( 'append', this.prefill );
1034        this.once( 'error', this.stopPrefill );
1035        this.once( 'last', this.stopPrefill );
1036        this.prefill();
1037    };
1038
1039    proto.prefill = function() {
1040        let distance = this.getPrefillDistance();
1041        this.isPrefilling = distance >= 0;
1042        if ( this.isPrefilling ) {
1043            this.log('prefill');
1044            this.loadNextPage();
1045        } else {
1046            this.stopPrefill();
1047        }
1048    };
1049
1050    proto.getPrefillDistance = function() {
1051        // element scroll
1052        if ( this.options.elementScroll ) {
1053            return this.scroller.clientHeight - this.scroller.scrollHeight;
1054        }
1055        // window
1056        return this.windowHeight - this.element.clientHeight;
1057    };
1058
1059    proto.stopPrefill = function() {
1060        this.log('stopPrefill');
1061        this.off( 'append', this.prefill );
1062    };
1063
1064// --------------------------  -------------------------- //
1065
1066    return InfiniteScroll;
1067
1068} ) );
1069// scroll-watch
1070( function( window, factory ) {
1071    // universal module definition
1072    if ( typeof module == 'object' && module.exports ) {
1073        // CommonJS
1074        module.exports = factory(
1075            window,
1076            require('./core'),
1077            require('fizzy-ui-utils'),
1078        );
1079    } else {
1080        // browser global
1081        factory(
1082            window,
1083            window.InfiniteScroll,
1084            window.fizzyUIUtils,
1085        );
1086    }
1087
1088}( window, function factory( window, InfiniteScroll, utils ) {
1089
1090    let proto = InfiniteScroll.prototype;
1091
1092// default options
1093    Object.assign( InfiniteScroll.defaults, {
1094        scrollThreshold: 400,
1095        // elementScroll: null,
1096    } );
1097
1098    InfiniteScroll.create.scrollWatch = function() {
1099        // events
1100        this.pageScrollHandler = this.onPageScroll.bind( this );
1101        this.resizeHandler = this.onResize.bind( this );
1102
1103        let scrollThreshold = this.options.scrollThreshold;
1104        let isEnable = scrollThreshold || scrollThreshold === 0;
1105        if ( isEnable ) this.enableScrollWatch();
1106    };
1107
1108    InfiniteScroll.destroy.scrollWatch = function() {
1109        this.disableScrollWatch();
1110    };
1111
1112    proto.enableScrollWatch = function() {
1113        if ( this.isScrollWatching ) return;
1114
1115        this.isScrollWatching = true;
1116        this.updateMeasurements();
1117        this.updateScroller();
1118        // TODO disable after error?
1119        this.on( 'last', this.disableScrollWatch );
1120        this.bindScrollWatchEvents( true );
1121    };
1122
1123    proto.disableScrollWatch = function() {
1124        if ( !this.isScrollWatching ) return;
1125
1126        this.bindScrollWatchEvents( false );
1127        delete this.isScrollWatching;
1128    };
1129
1130    proto.bindScrollWatchEvents = function( isBind ) {
1131        let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
1132        this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler );
1133        window[ addRemove ]( 'resize', this.resizeHandler );
1134    };
1135
1136    proto.onPageScroll = InfiniteScroll.throttle( function() {
1137        let distance = this.getBottomDistance();
1138        if ( distance <= this.options.scrollThreshold ) {
1139            this.dispatchEvent('scrollThreshold');
1140        }
1141    } );
1142
1143    proto.getBottomDistance = function() {
1144        let bottom, scrollY;
1145        if ( this.options.elementScroll ) {
1146            bottom = this.scroller.scrollHeight;
1147            scrollY = this.scroller.scrollTop + this.scroller.clientHeight;
1148        } else {
1149            bottom = this.top + this.element.clientHeight;
1150            scrollY = window.scrollY + this.windowHeight;
1151        }
1152        return bottom - scrollY;
1153    };
1154
1155    proto.onResize = function() {
1156        this.updateMeasurements();
1157    };
1158
1159    utils.debounceMethod( InfiniteScroll, 'onResize', 150 );
1160
1161// --------------------------  -------------------------- //
1162
1163    return InfiniteScroll;
1164
1165} ) );
1166// history
1167( function( window, factory ) {
1168    // universal module definition
1169    if ( typeof module == 'object' && module.exports ) {
1170        // CommonJS
1171        module.exports = factory(
1172            window,
1173            require('./core'),
1174            require('fizzy-ui-utils'),
1175        );
1176    } else {
1177        // browser global
1178        factory(
1179            window,
1180            window.InfiniteScroll,
1181            window.fizzyUIUtils,
1182        );
1183    }
1184
1185}( window, function factory( window, InfiniteScroll, utils ) {
1186
1187    let proto = InfiniteScroll.prototype;
1188
1189    Object.assign( InfiniteScroll.defaults, {
1190        history: 'replace',
1191        // historyTitle: false,
1192    } );
1193
1194    let link = document.createElement('a');
1195
1196// ----- create/destroy ----- //
1197
1198    InfiniteScroll.create.history = function() {
1199        if ( !this.options.history ) return;
1200
1201        // check for same origin
1202        link.href = this.getAbsolutePath();
1203        // MS Edge does not have origin on link
1204        // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/
1205        let linkOrigin = link.origin || link.protocol + '//' + link.host;
1206        let isSameOrigin = linkOrigin == location.origin;
1207        if ( !isSameOrigin ) {
1208            console.error( '[InfiniteScroll] cannot set history with different origin: ' +
1209                `${link.origin} on ${location.origin} . History behavior disabled.` );
1210            return;
1211        }
1212
1213        // two ways to handle changing history
1214        if ( this.options.append ) {
1215            this.createHistoryAppend();
1216        } else {
1217            this.createHistoryPageLoad();
1218        }
1219    };
1220
1221    proto.createHistoryAppend = function() {
1222        this.updateMeasurements();
1223        this.updateScroller();
1224        // array of scroll positions of appended pages
1225        this.scrollPages = [
1226            // first page
1227            {
1228                top: 0,
1229                path: location.href,
1230                title: document.title,
1231            },
1232        ];
1233        this.scrollPage = this.scrollPages[0];
1234        // events
1235        this.scrollHistoryHandler = this.onScrollHistory.bind( this );
1236        this.unloadHandler = this.onUnload.bind( this );
1237        this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler );
1238        this.on( 'append', this.onAppendHistory );
1239        this.bindHistoryAppendEvents( true );
1240    };
1241
1242    proto.bindHistoryAppendEvents = function( isBind ) {
1243        let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
1244        this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler );
1245        window[ addRemove ]( 'unload', this.unloadHandler );
1246    };
1247
1248    proto.createHistoryPageLoad = function() {
1249        this.on( 'load', this.onPageLoadHistory );
1250    };
1251
1252    InfiniteScroll.destroy.history =
1253        proto.destroyHistory = function() {
1254            let isHistoryAppend = this.options.history && this.options.append;
1255            if ( isHistoryAppend ) {
1256                this.bindHistoryAppendEvents( false );
1257            }
1258        };
1259
1260// ----- append history ----- //
1261
1262    proto.onAppendHistory = function( response, path, items ) {
1263        // do not proceed if no items. #779
1264        if ( !items || !items.length ) return;
1265
1266        let firstItem = items[0];
1267        let elemScrollY = this.getElementScrollY( firstItem );
1268        // resolve path
1269        link.href = path;
1270        // add page data to hash
1271        this.scrollPages.push({
1272            top: elemScrollY,
1273            path: link.href,
1274            title: response.title,
1275        });
1276    };
1277
1278    proto.getElementScrollY = function( elem ) {
1279        if ( this.options.elementScroll ) {
1280            return elem.offsetTop - this.top;
1281        } else {
1282            let rect = elem.getBoundingClientRect();
1283            return rect.top + window.scrollY;
1284        }
1285    };
1286
1287    proto.onScrollHistory = function() {
1288        // cycle through positions, find biggest without going over
1289        let scrollPage = this.getClosestScrollPage();
1290        // set history if changed
1291        if ( scrollPage != this.scrollPage ) {
1292            this.scrollPage = scrollPage;
1293            this.setHistory( scrollPage.title, scrollPage.path );
1294        }
1295    };
1296
1297    utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 );
1298
1299    proto.getClosestScrollPage = function() {
1300        let scrollViewY;
1301        if ( this.options.elementScroll ) {
1302            scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2;
1303        } else {
1304            scrollViewY = window.scrollY + this.windowHeight / 2;
1305        }
1306
1307        let scrollPage;
1308        for ( let page of this.scrollPages ) {
1309            if ( page.top >= scrollViewY ) break;
1310
1311            scrollPage = page;
1312        }
1313        return scrollPage;
1314    };
1315
1316    proto.setHistory = function( title, path ) {
1317        let optHistory = this.options.history;
1318        let historyMethod = optHistory && history[ optHistory + 'State' ];
1319        if ( !historyMethod ) return;
1320
1321        history[ optHistory + 'State' ]( null, title, path );
1322        if ( this.options.historyTitle ) document.title = title;
1323        this.dispatchEvent( 'history', null, [ title, path ] );
1324    };
1325
1326// scroll to top to prevent initial scroll-reset after page refresh
1327// https://stackoverflow.com/a/18633915/182183
1328    proto.onUnload = function() {
1329        if ( this.scrollPage.top === 0 ) return;
1330
1331        // calculate where scroll position would be on refresh
1332        let scrollY = window.scrollY - this.scrollPage.top + this.top;
1333        // disable scroll event before setting scroll #679
1334        this.destroyHistory();
1335        scrollTo( 0, scrollY );
1336    };
1337
1338// ----- load history ----- //
1339
1340// update URL
1341    proto.onPageLoadHistory = function( response, path ) {
1342        this.setHistory( response.title, path );
1343    };
1344
1345// --------------------------  -------------------------- //
1346
1347    return InfiniteScroll;
1348
1349} ) );
1350// button
1351( function( window, factory ) {
1352    // universal module definition
1353    if ( typeof module == 'object' && module.exports ) {
1354        // CommonJS
1355        module.exports = factory(
1356            window,
1357            require('./core'),
1358            require('fizzy-ui-utils'),
1359        );
1360    } else {
1361        // browser global
1362        factory(
1363            window,
1364            window.InfiniteScroll,
1365            window.fizzyUIUtils,
1366        );
1367    }
1368
1369}( window, function factory( window, InfiniteScroll, utils ) {
1370
1371// -------------------------- InfiniteScrollButton -------------------------- //
1372
1373    class InfiniteScrollButton {
1374        constructor( element, infScroll ) {
1375            this.element = element;
1376            this.infScroll = infScroll;
1377            // events
1378            this.clickHandler = this.onClick.bind( this );
1379            this.element.addEventListener( 'click', this.clickHandler );
1380            infScroll.on( 'request', this.disable.bind( this ) );
1381            infScroll.on( 'load', this.enable.bind( this ) );
1382            infScroll.on( 'error', this.hide.bind( this ) );
1383            infScroll.on( 'last', this.hide.bind( this ) );
1384        }
1385
1386        onClick( event ) {
1387            event.preventDefault();
1388            this.infScroll.loadNextPage();
1389        }
1390
1391        enable() {
1392            this.element.removeAttribute('disabled');
1393        }
1394
1395        disable() {
1396            this.element.disabled = 'disabled';
1397        }
1398
1399        hide() {
1400            this.element.style.display = 'none';
1401        }
1402
1403        destroy() {
1404            this.element.removeEventListener( 'click', this.clickHandler );
1405        }
1406
1407    }
1408
1409// -------------------------- InfiniteScroll methods -------------------------- //
1410
1411// InfiniteScroll.defaults.button = null;
1412
1413    InfiniteScroll.create.button = function() {
1414        let buttonElem = utils.getQueryElement( this.options.button );
1415        if ( buttonElem ) {
1416            this.button = new InfiniteScrollButton( buttonElem, this );
1417        }
1418    };
1419
1420    InfiniteScroll.destroy.button = function() {
1421        if ( this.button ) this.button.destroy();
1422    };
1423
1424// --------------------------  -------------------------- //
1425
1426    InfiniteScroll.Button = InfiniteScrollButton;
1427
1428    return InfiniteScroll;
1429
1430} ) );
1431// status
1432( function( window, factory ) {
1433    // universal module definition
1434    if ( typeof module == 'object' && module.exports ) {
1435        // CommonJS
1436        module.exports = factory(
1437            window,
1438            require('./core'),
1439            require('fizzy-ui-utils'),
1440        );
1441    } else {
1442        // browser global
1443        factory(
1444            window,
1445            window.InfiniteScroll,
1446            window.fizzyUIUtils,
1447        );
1448    }
1449
1450}( window, function factory( window, InfiniteScroll, utils ) {
1451
1452    let proto = InfiniteScroll.prototype;
1453
1454// InfiniteScroll.defaults.status = null;
1455
1456    InfiniteScroll.create.status = function() {
1457        let statusElem = utils.getQueryElement( this.options.status );
1458        if ( !statusElem ) return;
1459
1460        // elements
1461        this.statusElement = statusElem;
1462        this.statusEventElements = {
1463            request: statusElem.querySelector('.infinite-scroll-request'),
1464            error: statusElem.querySelector('.infinite-scroll-error'),
1465            last: statusElem.querySelector('.infinite-scroll-last'),
1466        };
1467        // events
1468        this.on( 'request', this.showRequestStatus );
1469        this.on( 'error', this.showErrorStatus );
1470        this.on( 'last', this.showLastStatus );
1471        this.bindHideStatus('on');
1472    };
1473
1474    proto.bindHideStatus = function( bindMethod ) {
1475        let hideEvent = this.options.append ? 'append' : 'load';
1476        this[ bindMethod ]( hideEvent, this.hideAllStatus );
1477    };
1478
1479    proto.showRequestStatus = function() {
1480        this.showStatus('request');
1481    };
1482
1483    proto.showErrorStatus = function() {
1484        this.showStatus('error');
1485    };
1486
1487    proto.showLastStatus = function() {
1488        this.showStatus('last');
1489        // prevent last then append event race condition from showing last status #706
1490        this.bindHideStatus('off');
1491    };
1492
1493    proto.showStatus = function( eventName ) {
1494        show( this.statusElement );
1495        this.hideStatusEventElements();
1496        let eventElem = this.statusEventElements[ eventName ];
1497        show( eventElem );
1498    };
1499
1500    proto.hideAllStatus = function() {
1501        hide( this.statusElement );
1502        this.hideStatusEventElements();
1503    };
1504
1505    proto.hideStatusEventElements = function() {
1506        for ( let type in this.statusEventElements ) {
1507            let eventElem = this.statusEventElements[ type ];
1508            hide( eventElem );
1509        }
1510    };
1511
1512// --------------------------  -------------------------- //
1513
1514    function hide( elem ) {
1515        setDisplay( elem, 'none' );
1516    }
1517
1518    function show( elem ) {
1519        setDisplay( elem, 'block' );
1520    }
1521
1522    function setDisplay( elem, value ) {
1523        if ( elem ) {
1524            elem.style.display = value;
1525        }
1526    }
1527
1528// --------------------------  -------------------------- //
1529
1530    return InfiniteScroll;
1531
1532} ) );
1533/*!
1534 * imagesLoaded v4.1.4
1535 * JavaScript is all like "You images are done yet or what?"
1536 * MIT License
1537 */
1538
1539( function( window, factory ) { 'use strict';
1540    // universal module definition
1541
1542    /*global define: false, module: false, require: false */
1543
1544    if ( typeof define == 'function' && define.amd ) {
1545        // AMD
1546        define( [
1547            'ev-emitter/ev-emitter'
1548        ], function( EvEmitter ) {
1549            return factory( window, EvEmitter );
1550        });
1551    } else if ( typeof module == 'object' && module.exports ) {
1552        // CommonJS
1553        module.exports = factory(
1554            window,
1555            require('ev-emitter')
1556        );
1557    } else {
1558        // browser global
1559        window.imagesLoaded = factory(
1560            window,
1561            window.EvEmitter
1562        );
1563    }
1564
1565})( typeof window !== 'undefined' ? window : this,
1566
1567// --------------------------  factory -------------------------- //
1568
1569    function factory( window, EvEmitter ) {
1570
1571        'use strict';
1572
1573        var $ = window.jQuery;
1574        var console = window.console;
1575
1576// -------------------------- helpers -------------------------- //
1577
1578// extend objects
1579        function extend( a, b ) {
1580            for ( var prop in b ) {
1581                a[ prop ] = b[ prop ];
1582            }
1583            return a;
1584        }
1585
1586        var arraySlice = Array.prototype.slice;
1587
1588// turn element or nodeList into an array
1589        function makeArray( obj ) {
1590            if ( Array.isArray( obj ) ) {
1591                // use object if already an array
1592                return obj;
1593            }
1594
1595            var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
1596            if ( isArrayLike ) {
1597                // convert nodeList to array
1598                return arraySlice.call( obj );
1599            }
1600
1601            // array of single index
1602            return [ obj ];
1603        }
1604
1605// -------------------------- imagesLoaded -------------------------- //
1606
1607        /**
1608         * @param {Array, Element, NodeList, String} elem
1609         * @param {Object or Function} options - if function, use as callback
1610         * @param {Function} onAlways - callback function
1611         */
1612        function ImagesLoaded( elem, options, onAlways ) {
1613            // coerce ImagesLoaded() without new, to be new ImagesLoaded()
1614            if ( !( this instanceof ImagesLoaded ) ) {
1615                return new ImagesLoaded( elem, options, onAlways );
1616            }
1617            // use elem as selector string
1618            var queryElem = elem;
1619            if ( typeof elem == 'string' ) {
1620                queryElem = document.querySelectorAll( elem );
1621            }
1622            // bail if bad element
1623            if ( !queryElem ) {
1624                console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) );
1625                return;
1626            }
1627
1628            this.elements = makeArray( queryElem );
1629            this.options = extend( {}, this.options );
1630            // shift arguments if no options set
1631            if ( typeof options == 'function' ) {
1632                onAlways = options;
1633            } else {
1634                extend( this.options, options );
1635            }
1636
1637            if ( onAlways ) {
1638                this.on( 'always', onAlways );
1639            }
1640
1641            this.getImages();
1642
1643            if ( $ ) {
1644                // add jQuery Deferred object
1645                this.jqDeferred = new $.Deferred();
1646            }
1647
1648            // HACK check async to allow time to bind listeners
1649            setTimeout( this.check.bind( this ) );
1650        }
1651
1652        ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
1653
1654        ImagesLoaded.prototype.options = {};
1655
1656        ImagesLoaded.prototype.getImages = function() {
1657            this.images = [];
1658
1659            // filter & find items if we have an item selector
1660            this.elements.forEach( this.addElementImages, this );
1661        };
1662
1663        /**
1664         * @param {Node} element
1665         */
1666        ImagesLoaded.prototype.addElementImages = function( elem ) {
1667            // filter siblings
1668            if ( elem.nodeName == 'IMG' ) {
1669                this.addImage( elem );
1670            }
1671            // get background image on element
1672            if ( this.options.background === true ) {
1673                this.addElementBackgroundImages( elem );
1674            }
1675
1676            // find children
1677            // no non-element nodes, #143
1678            var nodeType = elem.nodeType;
1679            if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
1680                return;
1681            }
1682            var childImgs = elem.querySelectorAll('img');
1683            // concat childElems to filterFound array
1684            for ( var i=0; i < childImgs.length; i++ ) {
1685                var img = childImgs[i];
1686                this.addImage( img );
1687            }
1688
1689            // get child background images
1690            if ( typeof this.options.background == 'string' ) {
1691                var children = elem.querySelectorAll( this.options.background );
1692                for ( i=0; i < children.length; i++ ) {
1693                    var child = children[i];
vendor: 4,462 bytes, lines 1694-1816
1694                    this.addElementBackgroundImages( child );
1695                }
1696            }
1697        };
1698
1699        var elementNodeTypes = {
1700            1: true,
1701            9: true,
1702            11: true
1703        };
1704
1705        ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
1706            var style = getComputedStyle( elem );
1707            if ( !style ) {
1708                // Firefox returns null if in a hidden iframe https://bugzil.la/548397
1709                return;
1710            }
1711            // get url inside url("...")
1712            var reURL = /url\((['"])?(.*?)\1\)/gi;
1713            var matches = reURL.exec( style.backgroundImage );
1714            while ( matches !== null ) {
1715                var url = matches && matches[2];
1716                if ( url ) {
1717                    this.addBackground( url, elem );
1718                }
1719                matches = reURL.exec( style.backgroundImage );
1720            }
1721        };
1722
1723        /**
1724         * @param {Image} img
1725         */
1726        ImagesLoaded.prototype.addImage = function( img ) {
1727            var loadingImage = new LoadingImage( img );
1728            this.images.push( loadingImage );
1729        };
1730
1731        ImagesLoaded.prototype.addBackground = function( url, elem ) {
1732            var background = new Background( url, elem );
1733            this.images.push( background );
1734        };
1735
1736        ImagesLoaded.prototype.check = function() {
1737            var _this = this;
1738            this.progressedCount = 0;
1739            this.hasAnyBroken = false;
1740            // complete if no images
1741            if ( !this.images.length ) {
1742                this.complete();
1743                return;
1744            }
1745
1746            function onProgress( image, elem, message ) {
1747                // HACK - Chrome triggers event before object properties have changed. #83
1748                setTimeout( function() {
1749                    _this.progress( image, elem, message );
1750                });
1751            }
1752
1753            this.images.forEach( function( loadingImage ) {
1754                loadingImage.once( 'progress', onProgress );
1755                loadingImage.check();
1756            });
1757        };
1758
1759        ImagesLoaded.prototype.progress = function( image, elem, message ) {
1760            this.progressedCount++;
1761            this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
1762            // progress event
1763            this.emitEvent( 'progress', [ this, image, elem ] );
1764            if ( this.jqDeferred && this.jqDeferred.notify ) {
1765                this.jqDeferred.notify( this, image );
1766            }
1767            // check if completed
1768            if ( this.progressedCount == this.images.length ) {
1769                this.complete();
1770            }
1771
1772            if ( this.options.debug && console ) {
1773                console.log( 'progress: ' + message, image, elem );
1774            }
1775        };
1776
1777        ImagesLoaded.prototype.complete = function() {
1778            var eventName = this.hasAnyBroken ? 'fail' : 'done';
1779            this.isComplete = true;
1780            this.emitEvent( eventName, [ this ] );
1781            this.emitEvent( 'always', [ this ] );
1782            if ( this.jqDeferred ) {
1783                var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
1784                this.jqDeferred[ jqMethod ]( this );
1785            }
1786        };
1787
1788// --------------------------  -------------------------- //
1789
1790        function LoadingImage( img ) {
1791            this.img = img;
1792        }
1793
1794        LoadingImage.prototype = Object.create( EvEmitter.prototype );
1795
1796        LoadingImage.prototype.check = function() {
1797            // If complete is true and browser supports natural sizes,
1798            // try to check for image status manually.
1799            var isComplete = this.getIsImageComplete();
1800            if ( isComplete ) {
1801                // report based on naturalWidth
1802                this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
1803                return;
1804            }
1805
1806            // If none of the checks above matched, simulate loading on detached element.
1807            this.proxyImage = new Image();
1808            this.proxyImage.addEventListener( 'load', this );
1809            this.proxyImage.addEventListener( 'error', this );
1810            // bind to image as well for Firefox. #191
1811            this.img.addEventListener( 'load', this );
1812            this.img.addEventListener( 'error', this );
1813            this.proxyImage.src = this.img.src;
1814        };
1815
1816        LoadingImage.prototype.getIsImageComplete = function() {
vendor: 3,213 bytes, lines 1817-1909
1817            // check for non-zero, non-undefined naturalWidth
1818            // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
1819            return this.img.complete && this.img.naturalWidth;
1820        };
1821
1822        LoadingImage.prototype.confirm = function( isLoaded, message ) {
1823            this.isLoaded = isLoaded;
1824            this.emitEvent( 'progress', [ this, this.img, message ] );
1825        };
1826
1827// ----- events ----- //
1828
1829// trigger specified handler for event type
1830        LoadingImage.prototype.handleEvent = function( event ) {
1831            var method = 'on' + event.type;
1832            if ( this[ method ] ) {
1833                this[ method ]( event );
1834            }
1835        };
1836
1837        LoadingImage.prototype.onload = function() {
1838            this.confirm( true, 'onload' );
1839            this.unbindEvents();
1840        };
1841
1842        LoadingImage.prototype.onerror = function() {
1843            this.confirm( false, 'onerror' );
1844            this.unbindEvents();
1845        };
1846
1847        LoadingImage.prototype.unbindEvents = function() {
1848            this.proxyImage.removeEventListener( 'load', this );
1849            this.proxyImage.removeEventListener( 'error', this );
1850            this.img.removeEventListener( 'load', this );
1851            this.img.removeEventListener( 'error', this );
1852        };
1853
1854// -------------------------- Background -------------------------- //
1855
1856        function Background( url, element ) {
1857            this.url = url;
1858            this.element = element;
1859            this.img = new Image();
1860        }
1861
1862// inherit LoadingImage prototype
1863        Background.prototype = Object.create( LoadingImage.prototype );
1864
1865        Background.prototype.check = function() {
1866            this.img.addEventListener( 'load', this );
1867            this.img.addEventListener( 'error', this );
1868            this.img.src = this.url;
1869            // check if image is already complete
1870            var isComplete = this.getIsImageComplete();
1871            if ( isComplete ) {
1872                this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
1873                this.unbindEvents();
1874            }
1875        };
1876
1877        Background.prototype.unbindEvents = function() {
1878            this.img.removeEventListener( 'load', this );
1879            this.img.removeEventListener( 'error', this );
1880        };
1881
1882        Background.prototype.confirm = function( isLoaded, message ) {
1883            this.isLoaded = isLoaded;
1884            this.emitEvent( 'progress', [ this, this.element, message ] );
1885        };
1886
1887// -------------------------- jQuery -------------------------- //
1888
1889        ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
1890            jQuery = jQuery || window.jQuery;
1891            if ( !jQuery ) {
1892                return;
1893            }
1894            // set local variable
1895            $ = jQuery;
1896            // $().imagesLoaded()
1897            $.fn.imagesLoaded = function( options, callback ) {
1898                var instance = new ImagesLoaded( this, options, callback );
1899                return instance.jqDeferred.promise( $(this) );
1900            };
1901        };
1902// try making plugin
1903        ImagesLoaded.makeJQueryPlugin();
1904
1905// --------------------------  -------------------------- //
1906
1907        return ImagesLoaded;
1908
1909    });

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.