mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-03 09:49:28 +02:00
371 lines
9.9 KiB
JavaScript
371 lines
9.9 KiB
JavaScript
// 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;
|
|
|
|
} ) );
|