diff --git a/index.js b/index.js index af5be5a..6c57fce 100644 --- a/index.js +++ b/index.js @@ -12,7 +12,7 @@ app.set( 'view engine', 'ejs' ); function initHelmet( app, helmet ) { app.use( helmet.contentSecurityPolicy( { directives: { - defaultSrc: ['\'self\''], + defaultSrc: ['\'self\'', 'edt.univ-evry.fr'], baseUri: ['\'self\''], blockAllMixedContent: [], fontSrc: ['\'self\'', 'https:', 'data:'], diff --git a/public/css/style.css b/public/css/style.css index 062187f..04353c5 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -870,6 +870,12 @@ body:not(.color-scheme-dark) #site-header.color-scheme-dark .light-scheme-logo:n transition: opacity var(--tr-short) var(--tr-func); } +.color-scheme-dark .light-scheme-icon:not(:only-child) img, +.color-scheme-light .dark-scheme-icon:not(:only-child) img { + opacity: 0; + transition: opacity var(--tr-short) var(--tr-func); +} + /*-------------------------------------------------------------- # Theme Switcher --------------------------------------------------------------*/ diff --git a/public/js/app.js b/public/js/app.js index a84bc54..645e358 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -67,18 +67,18 @@ child.push( async function () { child.push( async function () { const __global__ = getGlobal(); - if ( is( __global__ ) ) { + if ( __global__ ) { __global__.edt = { cookieName: 'univgrade', - toggle: byId( 'edt-element' ), - form: byId( 'edt-form' ), - img: byId( 'edt-img' ), - imgContainer: byId( 'edt-img-cont' ), - grade: byId( 'edt-grade' ), - week: byId( 'edt-week' ), - year: byId( 'edt-year' ), - comeback: byId( 'edt-comeback' ), - setCookie: byId( 'set-default-cookie' ), + toggle: document.getElementById( 'edt-element' ), + form: document.getElementById( 'edt-form' ), + img: document.getElementById( 'edt-img' ), + imgContainer: document.getElementById( 'edt-img-cont' ), + grade: document.getElementById( 'edt-grade' ), + week: document.getElementById( 'edt-week' ), + year: document.getElementById( 'edt-year' ), + comeback: document.getElementById( 'edt-comeback' ), + setCookie: document.getElementById( 'set-default-cookie' ), today: {}, current: {}, grades: [{ @@ -207,11 +207,11 @@ child.push( async function () { function updateImage() { const { edt } = __global__; const { imgContainer } = edt; - const img = byId( 'edt-img' ); - if ( is( img ) ) { + const img = document.getElementById( 'edt-img' ); + if ( img ) { edt.img = img; img.src = updateLink(); - } else if ( is( imgContainer ) ) { + } else if ( imgContainer ) { const img = new Image(); img.id = 'edt-img'; img.src = updateLink(); @@ -219,7 +219,7 @@ child.push( async function () { } } - if ( 'form' in __global__.edt && is( __global__.edt.form ) ) { + if ( 'form' in __global__.edt && __global__.edt.form ) { const { edt } = __global__; const { has, get } = Cookies; const { cookieName, setCookie, today } = edt; @@ -302,7 +302,7 @@ child.push( async function () { toggleClass( imgContainer, 'modal-active' ); } imgContainer.addEventListener( 'click', toggleModalActive ); - byId( 'modal-close' ).addEventListener( 'click', toggleModalActive ); + document.getElementById( 'modal-close' ).addEventListener( 'click', toggleModalActive ); function backward() { const { edt } = __global__; diff --git a/public/js/script.js b/public/js/script.js index fd95c16..3707708 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1,15 +1,5 @@ /* eslint-env browser */ -'use script'; - -function is( object ) { - return object !== null && object !== undefined; -} - -function isset( object ) { - return is( object ) && object !== ''; -} - function getGlobal() { const value = globalThis || self || window || global; if ( value ) { @@ -19,176 +9,462 @@ function getGlobal() { } function entries( object, func ) { - for ( const key in object ) { + for ( let key in object ) { if ( Object.prototype.hasOwnProperty.call( object, key ) ) { func( key, object[key] ); } } } -function isNumber( variable ) { - return typeof variable === 'number' || variable instanceof Number; -} - -function isString( variable ) { - return typeof variable === 'string' || variable instanceof String; -} - -function isBoolean( variable ) { - return typeof variable === 'boolean'; -} - -function isFunction( object ) { - return typeof object === 'function' || object instanceof Function ? object : false; -} - -function isObject( object ) { - return typeof object === 'object' || object instanceof Object; -} +class Chars { + constructor () { + this.values = Object.create( null ); -function isURL( object ) { - if ( isString( object ) ) { - try { - return new URL( object ) instanceof URL; - } catch ( _ ) { - return false; + for ( let i = 0; i < 62; i++ ) { + if ( i < 10 ) { + this.values[i] = 48 + i; + } else if ( i < 36 ) { + this.values[i] = 65 + i - 10; + } else if ( i < 62 ) { + this.values[i] = 97 + i - 36; + } } - } else { - return object instanceof URL; } -} - -function identifier( length ) { - 'use script'; - const values = []; - const list = []; - for ( let i = 0; i < 62; i += 1 ) { - if ( i < 10 ) { - values[i] = 48 + i; - } else if ( i < 36 ) { - values[i] = 65 + ( i - 10 ); - } else if ( i < 62 ) { - values[i] = 97 + ( i - 36 ); + get( x ) { + return this.values[x < 62 ? x : x % 62]; + } + static get( x ) { + const gl = getGlobal(); + if ( !( 'CharsList' in gl ) ) { + gl.CharsList = new Chars(); } + return gl.CharsList.get( x ); } - for ( let i = 0; i < ( length || 16 ); i += 1 ) { - list[i] = values[Math.floor( Math.random() * 62 )]; +} +/** + * Measure Execution Time of a Function + * @param {Function} func + * @param {...any} args + * @returns {Promise} A Promise + */ +function measure( func, ...args ) { + const perf = getGlobal().performance; + const start = perf.now(); + const res = func( ...args ); + const end = perf.now(); + return new Promise( function ( resolve, reject ) { + if ( res instanceof Promise ) { + res.then( function ( response ) { + const end = perf.now(); + console.info( `${func.name} Execution Time : ${end - start}` ); + resolve( response ); + } ).catch( function ( _ ) { + const end = perf.now(); + console.error( `${func.name} Execution Time : ${end - start}` ); + reject( _ ); + } ); + } else { + console.info( `${func.name} Execution Time : ${end - start}` ); + resolve( res ); + } + } ); +} +/** + * Randomly Generate a String + * @param {Number} length Number + * @returns {String} Randomly generated string of length size + */ +function identifier( length ) { + const res = []; + for ( let i = 0, l = length || 16; i < l; i += 1 ) { + res[i] = String.fromCharCode( Chars.get( Math.floor( Math.random() * 124 ) & Math.ceil( Math.random() * 124 ) ) ); } - return String.fromCharCode( ...list ); + return res.join( '' ); } - +/** + * Inhibit Propagation and Default Behavior of an Event + * @param {Event} event + */ function inhibitEvent( event ) { event.preventDefault(); event.stopPropagation(); } -function catchError( caughtError, customMessage ) { - if ( typeof caughtError === 'string' ) { - caughtError = new Error( caughtError ); - } - setTimeout( () => { - console.group( caughtError.name ); - console.warn( caughtError.name ); - console.warn( caughtError.message ); - console.warn( caughtError.stack ); - console.groupEnd( caughtError.name ); - }, 0 ); - return false; -} - -function byId( string ) { - return document.getElementById( string ); -} - -function byClass( string, element ) { - return ( element || document ).getElementsByClassName( string ); -} - function requestFrame() { const [func, ...args] = arguments; - if ( isFunction( func ) ) { - return window.requestAnimationFrame( timestamp => func( timestamp, ...args ) ); - } - throw new Error( `${func} is not a function.` ); -} - -function cancelFrame( id ) { - try { - cancelAnimationFrame( id ); - return true; - } catch ( error ) { - catchError( error ); - return false; + if (typeof func === 'function' || func instanceof Function ) { + return window.requestAnimationFrame(timestamp => func(timestamp, ...args)); } + throw new Error(`${func} is not a function.`); } +/** + * Return true if classname is present, false otherwise. + * @param {HTMLElement} element + * @param {String} className + * @returns {Boolean} + */ function hasClass( element, className ) { - if ( is( element ) && isset( className ) ) { + if ( element && !!className && typeof className === 'string' ) { return element.classList.contains( className ); } else { - return catchError( `element:${element} or class:${className} is undefined.` ); + throw new Error( + 'SyntaxError: element and/or classname is/or undefined.' + ); } } - -function addClass( element, className, doNotRequestFrame ) { - doNotRequestFrame = doNotRequestFrame || true; - if ( is( element ) && isset( className ) ) { - if ( doNotRequestFrame ) { +function addClass( element, className, requireFrame ) { + requireFrame = requireFrame || false; + if ( element && !!className && typeof className === 'string' ) { + if ( !requireFrame ) { element.classList.add( className ); return true; } else { - return !!requestFrame( () => element.classList.add( className ) ); + return !!window.requestAnimationFrame( function () { + element.classList.add( className ); + } ); } + } else { + throw new Error( 'element or/and className is/are undefined.' ); } - return catchError( `element:${element} or class:${className} is undefined.` ); } - -function removeClass( element, className, doNotRequestFrame ) { - doNotRequestFrame = doNotRequestFrame || true; - if ( is( element ) && isset( className ) ) { - if ( doNotRequestFrame ) { +/** + * Remove classname from element and request a frame before removing it + * @param {HTMLElement} element + * @param {String} className + * @param {Boolean} [requireFrame] + * @returns {Boolean} + */ +function removeClass( element, className, requireFrame ) { + requireFrame = requireFrame || false; + if ( element && !!className && typeof className === 'string' ) { + if ( !requireFrame ) { element.classList.remove( className ); return true; } else { - return !!requestFrame( () => element.classList.remove( className ) ); + return !!window.requestAnimationFrame( function () { + element.classList.remove( className ); + } ); } + } else { + throw new Error( 'element or/and className is/are not valid.' ); } - return catchError( `element:${element} or class:${className} is undefined.` ); } - -function toggleClass( element, className ) { - if ( is( element ) && isset( className ) ) { +/** + * Toggle classname from element and request a frame before toggling it + * @param {HTMLElement} element + * @param {String} className + * @param {Boolean} [window.requestAnimationFrame] + * @returns {Boolean} + */ +function toggleClass( element, className, requireFrame ) { + requireFrame = requireFrame || false; + if ( element && !!className && typeof className === 'string' ) { const boolean = hasClass( element, className ); - if ( isBoolean( boolean ) ) { - requestFrame( () => - boolean ? !removeClass( element, className ) : addClass( element, className ) - ); - return !boolean; + if ( typeof boolean === 'boolean' ) { + if ( boolean ) { + return !removeClass( element, className, requireFrame ); + } else { + return addClass( element, className, requireFrame ); + } } + } else { + throw new Error( 'element or/and className is/are not valid.' ); } - return catchError( `element:${element} or class:${className} is undefined.` ); } - -function attr() { - const [element, attrName, value] = arguments; - if ( is( value ) ) { +/** + * Get or set attribute of Element + * @param {HTMLElement} element + * @param {String} attrName + * @param {Any} [value] + * @returns {String} + */ +function attr( element, attrName, value ) { + if ( value === 0 ? true : !!value ) { return element.setAttribute( attrName, value ); } return element.getAttribute( attrName ); } - function data() { const [element, dataset, value] = arguments; - if ( isset( dataset ) ) { - if ( is( value ) ) { + if ( !!dataset && typeof dataset === 'string' ) { + if ( value === 0 ? true : !!value ) { element.dataset[dataset] = value; return element.dataset[dataset]; } return element.dataset[dataset]; } - return element.dataset; + return Object.assign( Object.create( null ), element.dataset ); +} + +function useInter( force = false ) { + if ( force || window && 'navigator' in window && + !['MacIntel', 'iPhone', 'iPod', 'iPad'].includes( + window.navigator.platform + ) + ) { + return addClass( document.body, 'inter', true ); + } + return false; } +/** + * Element Creation Shorthand + * @param {...({attr:{String:String},data:{String:String},events:[type:String,listener:Function,options:Boolean|AddEventListenerOptions][],id:String,ns:String,style:{String:String}t:String,_:(Any[]|Any)})} + * @returns {HTLMElement} + */ +function ecs() { + const l = []; + let ll = arguments.length; + if ( ll === 0 ) { return document.createElement( 'div' ); } + for ( let x = 0, n = ll; x < n; x += 1 ) { + const y = arguments[x]; + if ( y ) { l[x] = y; } else { ll -= 1; } + + } + if ( ll === 0 ) { + return document.createElement( 'div' ); + } else if ( ll !== 1 ) { + const a = document.createElement( 'div' ); + for ( const b of l ) { + a.appendChild( ecs( b ) ); + } + return a; + } + let e = l.pop(); + if ( e instanceof Element ) { + return e; + } + const { + actions: a, + attr: t, + class: c, + data: d, + _: h, + events: v, + id, + ns: n, + style: s, + t: g + } = e; + if ( id || c || g ) { + if ( !!n && typeof n === 'string' ) { + e = document.createElementNS( n, !!g && typeof g === 'string' ? g : 'div' ); + } else { e = document.createElement( !!g && typeof g === 'string' ? g : 'div' ); } + if ( id ) { + e.id = id; + } + if ( c ) { + if ( typeof c === 'string' ) { + e.classList.add( c ); + } else { + e.classList.add( ...c ); + } + } + } else { + e = document.createElement( 'div' ); + } + if ( t ) { + entries( t, function ( k, v ) { + if ( v instanceof Promise ) { + v.then( function ( r ) { + attr( e, k, r ); + } ); + } else { + attr( e, k, v ); + } + } ); + } + if ( d ) { + entries( d, function ( k, v ) { + if ( v instanceof Promise ) { + v.then( function ( r ) { + e.dataset[k] = r; + } ); + } else { + e.dataset[k] = v; + } + } ); + } + if ( v ) { + for ( const ev of v ) { + e.addEventListener( ...ev ); + } + } + if ( s ) { + entries( s, function ( k, v ) { + e.style[k] = v; + } ); + } + if ( h ) { + for ( const i of !( typeof h === 'string' ) && Symbol.iterator in h ? h : [h] ) { + if ( i instanceof Element ) { + e.appendChild( i ); + } else if ( typeof i === 'string' ) { + e.innerHTML += i; + } else if ( i instanceof Promise ) { + const a = document.createElement( 'template' ); + e.appendChild( a ); + i.then( function ( r ) { + if ( typeof r === 'string' ) { + a.outerHTML += r; + a.remove(); + } else { + e.replaceChild( ecs( r ), a ); + } + } ).catch( function ( _ ) { + console.error( 'ecs error: ', _ ); + } ); + } else if ( + ['number', 'bigint', 'boolean', 'symbol'].includes( typeof i ) + ) { + e.innerHTML += `${i}`; + } else { + e.appendChild( ecs( i ) ); + } + } + } + if ( a ) { + entries( a, function ( k, v ) { + const a = k.split( /_\$/ ); + if ( a.length > 1 ) { + e[a[0]]( ...v ); + } else { + e[k]( ...v ); + } + } ); + } + return e; +} +/** + * Execute ecs in an inline script an replace script by ecs' result + * @param {...({attr:{String:String},data:{String:String},events:[type:String,listener:Function,options:Boolean|AddEventListenerOptions][],id:String,ns:String,style:{String:String}t:String,_:(Any[]|Any)})} + */ +function ecsr() { + const { currentScript: c } = document; + const { parentElement: p } = c; + if ( ![document.head, document.documentElement].includes( p ) ) { + p.replaceChild( ecs( ...arguments ), c ); + } +} +class WebPTest { + constructor () { } + static get data() { + return [ + [ + 'lossy', + 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA' + ], + ['lossless', 'UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA=='], + [ + 'alpha', + 'UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==' + ], + [ + 'animation', + 'UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA' + ] + ]; + } + static save( features ) { + return new Promise( function ( resolve ) { + const gl = getGlobal(); + gl.WebPTestResult = features.reduce( function ( acc, [feature, bool] ) { + if ( !( feature in acc ) ) { + acc[feature] = bool; + return acc; + } + }, Object.create( null ) ); + return resolve( gl.WebPTestResult ); + } ); + } + static imageLoading( data, feature ) { + return new Promise( function ( resolve ) { + const img = new Image(); + img.onload = function () { + resolve( [feature, img.width > 0 && img.height > 0] ); + }; + img.onerror = function () { + resolve( [feature, false] ); + }; + img.src = data; + } ); + } + static test() { + const gl = getGlobal(); + return new Promise( function ( resolve ) { + if ( 'WebPTestResult' in gl ) { + resolve( gl.WebPTestResult ); + } else { + Promise.all( + WebPTest.data.map( function ( [feature, data] ) { + return WebPTest.imageLoading( + `data:image/webp;base64,${data}`, + feature + ); + } ) + ).then( function ( response ) { + resolve( WebPTest.save( response ) ); + } ); + } + } ); + } + static get passed() { + const gl = getGlobal(); + let wtr; + return new Promise( async function ( resolve ) { + if ( 'WebPTestResult' in gl ) { + wtr = gl.WebPTestResult; + } else { + wtr = await WebPTest.test(); + } + return resolve( wtr.lossy && wtr.lossless && wtr.alpha && wtr.animation ); + } ); + } +} +class Cookies { + constructor () { } + static get( string ) { + return new Map( + decodeURIComponent( document.cookie ) + .split( /;/ ) + .map( function ( string ) { + return string.trim().split( /=/ ); + } ) + ).get( string ); + } + static has( string ) { + return new Map( + decodeURIComponent( document.cookie ) + .split( /;/ ) + .map( function ( string ) { + return string.trim().split( /=/ ); + } ) + ).has( string ); + } + static set( cookieName, cookieValue, options ) { + options = options && typeof options === 'object' ? options : Object.create( null ); + let { expiration, sameSite } = options; + if ( !expiration ) { + const newDate = new Date(); + const year = 365.244 * 24 * 3600 * 1000; + newDate.setTime( newDate.getTime() + year ); + expiration = newDate.toGMTString(); + } + const expirationString = `expires=${expiration}`; + const sameSiteString = `SameSite=${sameSite || 'Strict'};Secure`; + document.cookie = `${cookieName}=${encodeURIComponent( + cookieValue + )};path=/;${expirationString};${sameSiteString}`; + } + static delete( cookieName ) { + const newDate = new Date(); + const year = 365.244 * 24 * 3600 * 1000; + newDate.setTime( newDate.getTime() - year ); + const expirationString = `expires=${newDate.toGMTString()}`; + document.cookie = `${cookieName}=${''};${expirationString};`; + } +} class Easing { constructor () { } static linearTween( t, b, c, d ) { @@ -302,364 +578,410 @@ class Easing { return c / 2 * ( Math.sqrt( 1 - t * t ) + 1 ) + b; } } - -function smoothScrollTo( selector, duration ) { +/** + * + * @param {String} selector + * @param {Number} duration + */ +function smoothScrollTo( event, selector, duration ) { + inhibitEvent( event ); const easing = Easing.easeInOutCubic; - const target = document.querySelector( selector ); - if ( !( target instanceof HTMLElement ) ) { - return; - } - const startPosition = window.pageYOffset; - const targetPosition = startPosition + target.getBoundingClientRect().top; + let target = document.querySelector( selector ); + if ( !( target instanceof HTMLElement ) ) { return; } + let startPosition = window.pageYOffset; + let targetPosition = startPosition + target.getBoundingClientRect().top; duration = duration || 1000; - const distance = targetPosition - startPosition; + let distance = targetPosition - startPosition; let startTime = null; - function animation( currentTime ) { - startTime = is( startTime ) ? startTime : currentTime; - const timeElapsed = currentTime - startTime; - const run = easing( timeElapsed, startPosition, distance, duration ); + startTime = startTime ? startTime : currentTime; + let timeElapsed = currentTime - startTime; + let run = easing( timeElapsed, startPosition, distance, duration ); window.scrollTo( 0, run ); if ( timeElapsed < duration ) { - requestFrame( animation ); + window.requestAnimationFrame( animation ); } } - requestFrame( animation ); + window.requestAnimationFrame( animation ); } - -/** @returns {HTMLElement} */ -function ecs() { - const ce = a => document.createElement( isset( a ) ? a : 'div' ); - const ac = ( a, b ) => { - a.appendChild( b ); - return a; - }; - const l = [...arguments].filter( isset ); - const ll = l.length; - if ( ll === 0 ) { - return ce(); - } else if ( ll !== 1 ) { - const a = ce(); - for ( const b of l ) { - ac( a, ecs( b ) ); - } - return a; - } - let e = l.pop(); - if ( e instanceof Element ) { - return ac( ce(), e ); - } - const { attr: a, class: c, data, events, id, ns, style, actions, _, $ } = e; - if ( id || c || $ ) { - if ( ns ) { - e = document.createElementNS( ns, $ ); +class Wait { + constructor () { } + static register() { + const gl = getGlobal(); + if ( 'WaitRegister' in gl ) { + return gl.WaitRegister; } else { - e = ce( $ ); - } - if ( id ) { - e.id = id; - } - if ( c ) { - e.classList.add( ...c ); - } - } else { - e = ce(); - } - if ( a ) { - entries( a, ( k, v ) => { - attr( e, k, v ); - } ); - } - if ( data ) { - entries( data, ( k, v ) => { - e.dataset[k] = v; - } ); - } - if ( events ) { - events.forEach( ev => e.addEventListener( ...ev ) ); - } - if ( style ) { - entries( style, ( k, v ) => { - e.style[k] = v; - } ); - } - if ( _ ) { - for ( const i of _ ) { - if ( i instanceof Element ) { - ac( e, i ); - } else if ( ['string', 'number', 'bigint', 'boolean', 'symbol'].includes( typeof i ) ) { - e.innerHTML += `${i}`; - } else { - try { - ac( e, ecs( i ) ); - } catch ( _ ) { - catchError( _ ); + const wr = Object.assign( Object.create( null ), { + interactive: [], + complete: [], + DOMContentLoaded: [], + load: [] + } ); + gl.WaitRegister = wr; + document.addEventListener( 'readystatechange', function () { + Wait.all( document.readyState ); + } ); + document.addEventListener( 'DOMContentLoaded', function () { + Wait.all( 'DOMContentLoaded' ); + } ); + window.addEventListener( 'load', function () { + Wait.all( 'load' ); + } ); + return gl.WaitRegister; + } + } + static set( type, options ) { + const { resolve, reject, func, args } = options; + const wr = Wait.register(); + let exec = false; + const { readyState } = document; + switch ( type ) { + case 'interactive': + case 'DOMContentLoaded': { + if ( readyState !== 'loading' ) { + exec = true; + try { + resolve( func( ...args ) ); + } catch ( _ ) { + reject( _ ); + } } + break; } - } - } - if ( actions ) { - entries( actions, ( k, v ) => { - const a = k.split( /_\$/ ); - if ( a.length > 1 ) { - e[a[0]]( ...v ); - } else { - e[k]( ...v ); + case 'complete': + case 'load': { + if ( readyState === 'complete' ) { + exec = true; + try { + resolve( func( ...args ) ); + } catch ( _ ) { + reject( _ ); + } + } + break; } - } ); - } - return e; -} - -function ecsScript() { - const c = document.currentScript; - if ( ![document.head, document.documentElement].includes( c.parentElement ) ) { - for ( const b of arguments ) { - c.insertAdjacentElement( 'beforebegin', ecs( b ) ); } - c.remove(); + if ( exec === false ) { + wr[type].push( function () { + return new Promise( function ( res, rej ) { + try { + return res( resolve( func( ...args ) ) ); + } catch ( _ ) { + rej( reject( _ ) ); + } + } ); + } ); + } + } + static all( type ) { + return Promise.all( Wait.register()[type].map( function ( e ) { + return e(); + } ) ); } -} -class Wait { - constructor () { } static time( time ) { - return new Promise( resolve => setTimeout( resolve, time ) ); + return new Promise( function ( resolve ) { + return setTimeout( resolve, time ); + } ); } - static async first() { + static race() { return Promise.race( ...arguments ); } - static async delay() { + static delay() { const [func, timeout, ...args] = arguments; return setTimeout( func, timeout || 0, ...args ); } - static async async() { + static async() { const [func, ...args] = arguments; - return func( ...args ); + return new Promise( function ( resolve, reject ) { + try { + return resolve( func( ...args ) ); + } catch ( _ ) { + return reject( _ ); + } + } ); } - static async asyncDelay() { - const [func, ...args] = arguments; - return Wait.delay( func, ...args ); + static promiseDelay() { + const [func, timeout, ...args] = arguments; + return new Promise( function ( resolve, reject ) { + return setTimeout( function ( ...args ) { + try { + return resolve( func( ...args ) ); + } catch ( _ ) { + return reject( _ ); + } + }, timeout, ...args ); + } ); } - static async loading() { + static whileLoading() { const [func, ...args] = arguments; if ( document.readyState === 'loading' ) { - func( ...args ); + return func( ...args ); } } - static async interactive() { + static interactive() { const [func, ...args] = arguments; - if ( document.readyState !== 'loading' ) { - func( ...args ); - } else { - document.addEventListener( 'readystatechange', () => func( ...args ) ); - } + const options = Object.create( null ); + return new Promise( function ( resolve, reject ) { + options.resolve = resolve; + options.reject = reject; + options.func = func; + options.args = args; + Wait.set( 'interactive', options ); + } ); } - static async complete() { + static complete() { const [func, ...args] = arguments; - if ( document.readyState === 'complete' ) { - func( ...args ); - } else { - document.addEventListener( 'readystatechange', () => - document.readyState === 'complete' ? func( ...args ) : null - ); - } + const options = Object.create( null ); + return new Promise( function ( resolve, reject ) { + options.resolve = resolve; + options.reject = reject; + options.func = func; + options.args = args; + Wait.set( 'complete', options ); + } ); } - static async DOMContentLoaded() { + static DOMContentLoaded() { const [func, ...args] = arguments; - if ( document.readyState === 'interactive' || document.readyState === 'complete' ) { - func( ...args ); - } else { - document.addEventListener( 'DOMContentLoaded', () => func( ...args ) ); - } + const options = Object.create( null ); + return new Promise( function ( resolve, reject ) { + options.resolve = resolve; + options.reject = reject; + options.func = func; + options.args = args; + Wait.set( 'DOMContentLoaded', options ); + } ); } - static async ready() { + static ready() { const [func, ...args] = arguments; - if ( document.readyState !== 'loading' ) { - func( ...args ); - } else { - document.addEventListener( 'readystatechange', () => - document.readyState === 'complete' ? func( ...args ) : null - ); - } + const options = Object.create( null ); + return new Promise( function ( resolve, reject ) { + options.resolve = resolve; + options.reject = reject; + options.func = func; + options.args = args; + Wait.set( 'complete', options ); + } ); } - static async load() { + static load() { const [func, ...args] = arguments; - window.addEventListener( 'load', () => func( ...args ) ); + const options = Object.create( null ); + return new Promise( function ( resolve, reject ) { + options.resolve = resolve; + options.reject = reject; + options.func = func; + options.args = args; + Wait.set( 'complete', options ); + } ); } } -class Environment { - constructor () { - this.actions = []; - this.properties = new Object( null ); - } - async set( key, value ) { - return this.properties[key] = value; - } - async parallel( array ) { - try { - return Promise.all( array ); - } catch ( _ ) { - return array; +class ExtendedWorker { + constructor ( WorkerObject, WorkerOptions ) { + if ( typeof WorkerObject === 'function' ) { + WorkerObject = ExtendedWorker.prepareFromFunction( WorkerObject ); } - } - has( key ) { - return key in this.properties; - } - get( key ) { - return this.properties[key]; - } - assert( key, value ) { - if ( this.has( key ) ) { - if ( is( value ) ) { - return this.get( key ) === value; - } - return is( this.get( key ) ); + this.worker = new Worker( WorkerObject, WorkerOptions ); + if ( + WorkerOptions && + 'promise' in WorkerOptions && + WorkerOptions.promise === true + ) { + this.worker.promise = true; + ExtendedWorker.assert(); + this.worker.onmessage = ExtendedWorker.onMessage; + } else { + this.worker.promise = false; } - return false; } - push() { - for ( const func of arguments ) { - if ( isFunction( func ) ) { - this.actions.push( func ); - } else { - catchError( `func:${func} is not a function.` ); - } - } + static get global() { + return getGlobal(); } - async run() { - try { - return Promise.all( this.actions.map( Wait.interactive ) ); - } catch ( _ ) { - return catchError( _ ); + static prepareFromString( WorkerString ) { + if ( typeof WorkerString === 'string' ) { + const WorkerBody = '(' + WorkerString + ')()'; + const WorkerBlob = new Blob( [WorkerBody], { type: 'text/javascript' } ); + return URL.createObjectURL( WorkerBlob ); } + throw new Error( `WorkerString:${WorkerString} is not a string.` ); } -} -class Cookies { - constructor () { } - static get( string ) { - return new Map( - decodeURIComponent( document.cookie ) - .split( /;/ ) - .map( string => string.trim().split( /=/ ) ) - ).get( string ); - } - static has( string ) { - return new Map( - decodeURIComponent( document.cookie ) - .split( /;/ ) - .map( string => string.trim().split( /=/ ) ) - ).has( string ); - } - static set( cookieName, cookieValue, options ) { - options = is( options ) && isObject( options ) ? options : {}; - let { expiration } = options; - const { sameSite } = options; - if ( !is( expiration ) ) { - const newDate = new Date(); - const year = 365.244 * 24 * 3600 * 1000; - newDate.setTime( newDate.getTime() + year ); - expiration = newDate.toGMTString(); + static prepareFromFunction( WorkerFunction ) { + if ( typeof WorkerFunction === 'function' ) { + return ExtendedWorker.prepareFromString( WorkerFunction.toString() ); } - const expirationString = `expires=${expiration}`; - const sameSiteString = `SameSite=${sameSite || 'Strict'};Secure`; - document.cookie = - `${cookieName}=${encodeURIComponent( cookieValue )};path=/;${expirationString};${sameSiteString}`; + throw new Error( `WorkerFunction:${WorkerFunction} is not a function.` ); } - static delete( cookieName ) { - const newDate = new Date(); - const year = 365.244 * 24 * 3600 * 1000; - newDate.setTime( newDate.getTime() - year ); - const expirationString = `expires=${newDate.toGMTString()}`; - document.cookie = `${cookieName}=${''};${expirationString};`; + static createFromString( WorkerString, WorkerOptions ) { + if ( typeof WorkerString === 'string' ) { + const WorkerBody = '(' + WorkerString + ')()'; + const WorkerBlob = new Blob( [WorkerBody], { type: 'text/javascript' } ); + return new ExtendedWorker( + URL.createObjectURL( WorkerBlob ), + WorkerOptions + ); + } + throw new Error( `WorkerString:${WorkerString} is not a string.` ); } -} -class PromiseWorker { - constructor ( url ) { - PromiseWorker.assert(); - this.worker = new Worker( url ); - this.worker.onmessage = PromiseWorker.onMessage; + static createFromFunction( WorkerFunction, WorkerOptions ) { + if ( typeof WorkerFunction === 'function' ) { + return ExtendedWorker.createFromString( + WorkerFunction.toString(), + WorkerOptions + ); + } + throw new Error( `WorkerFunction:${WorkerFunction} is not a function.` ); } get env() { - return getGlobal().PromiseWorkers; + return ExtendedWorker.global.ExtendedWorkers; + } + set onmessage( func ) { + this.worker.onmessage = func; } get onmessage() { return this.worker.onmessage; } - postMessage( data ) { - return PromiseWorker.postMessage( data, this.worker ); + set onerror( func ) { + this.worker.onerror = func; + } + get onerror() { + return this.worker.onerror; + } + set onmessageerror( func ) { + this.worker.onmessageerror = func; + } + get onmessageerror() { + return this.worker.onmessageerror; + } + dispatchEvent() { + return this.worker.dispatchEvent( ...arguments ); + } + addEventListener() { + return this.worker.addEventListener( ...arguments ); + } + removeEventListener() { + return this.worker.removeEventListener( ...arguments ); + } + terminate() { + return this.worker.terminate(); + } + postMessage( data, transferableObject ) { + return ExtendedWorker.postMessage( + [data, transferableObject], + this.worker + ); } static assert() { - const self = getGlobal(); - if ( !( 'PromiseWorkers' in self ) ) { - self.PromiseWorkers = { + const self = ExtendedWorker.global; + if ( !( 'ExtendedWorkers' in self ) ) { + self.ExtendedWorkers = Object.assign( Object.create( null ), { resolves: [], rejects: [] - }; - } else if ( !( 'resolves' in self.PromiseWorkers && 'rejects' in self.PromiseWorkers ) ) { - self.PromiseWorkers.resolves = []; - self.PromiseWorkers.rejecs = []; + } ); + } else if ( + !( + 'resolves' in self.ExtendedWorkers && + 'rejects' in self.ExtendedWorkers + ) + ) { + self.ExtendedWorkers.resolves = []; + self.ExtendedWorkers.rejecs = []; } } - static postMessage( data, worker ) { - const messageId = PromiseWorker.id(); - const message = { - id: messageId, - data: data - }; - return new Promise( ( resolve, reject ) => { - PromiseWorker.resolves[messageId] = resolve; - PromiseWorker.rejects[messageId] = reject; - worker.postMessage( message ); - } ); + static postMessage( messagePayload, worker ) { + if ( worker.promise ) { + const messageId = identifier(); + const [data, transferableObject] = messagePayload; + const message = Object.assign( Object.create( null ), { + id: messageId, + data: data + } ); + return new Promise( function ( resolve, reject ) { + ExtendedWorker.resolves[messageId] = resolve; + ExtendedWorker.rejects[messageId] = reject; + if ( transferableObject ) { + worker.postMessage( message, transferableObject ); + } else { + worker.postMessage( message ); + } + } ); + } else { + worker.postMessage( ...messagePayload ); + } } static onMessage( message ) { - const { - id, - err, - data - } = message.data; - const resolve = PromiseWorker.resolves[id]; - const reject = PromiseWorker.rejects[id]; - if ( is( data ) ) { + const { id, err, data } = message.data; + const resolve = ExtendedWorker.resolves[id]; + const reject = ExtendedWorker.rejects[id]; + if ( data ) { if ( resolve ) { resolve( data ); } - } else if ( is( reject ) ) { + } else if ( reject ) { if ( err ) { reject( err ); } else { reject( 'Got nothing' ); } } - PromiseWorker.delete( id ); + ExtendedWorker.delete( id ); } static get resolves() { - PromiseWorker.assert(); - return getGlobal().PromiseWorkers.resolves; + ExtendedWorker.assert(); + return ExtendedWorker.global.ExtendedWorkers.resolves; } static get rejects() { - return getGlobal().PromiseWorkers.rejects; + ExtendedWorker.assert(); + return ExtendedWorker.global.ExtendedWorkers.rejects; } static delete( id ) { - delete PromiseWorker.resolves[id]; - delete PromiseWorker.rejects[id]; + delete ExtendedWorker.resolves[id]; + delete ExtendedWorker.rejects[id]; } - static id( length ) { - const values = []; - const list = []; - for ( let i = 0; i < 62; i += 1 ) { - if ( i < 10 ) { - values[i] = 48 + i; - } else if ( i < 36 ) { - values[i] = 65 + ( i - 10 ); - } else if ( i < 62 ) { - values[i] = 97 + ( i - 36 ); +} + +class Environment { + constructor () { + this.actions = []; + this.properties = new Object( null ); + } + async set( key, value ) { + return this.properties[key] = value; + } + async parallel( array ) { + try { + return Promise.all( array ); + } catch ( _ ) { + return array; + } + } + has( key ) { + return key in this.properties; + } + get( key ) { + return this.properties[key]; + } + assert( key, value ) { + if ( this.has( key ) ) { + if ( value ) { + return this.get( key ) === value; + } + return !!this.get( key ); + } + return false; + } + push() { + for ( const func of arguments ) { + if ( typeof func === 'function' || func instanceof Function ) { + this.actions.push( func ); + } else { + console.error( `func:${func} is not a function.` ); } } - for ( let i = 0; i < ( length || 16 ); i += 1 ) { - list[i] = values[Math.floor( Math.random() * 62 )]; + } + async run() { + try { + return Promise.all( this.actions.map( Wait.interactive ) ); + } catch ( _ ) { + return console.error( _ ); } - return String.fromCharCode( ...list ); } } @@ -675,7 +997,7 @@ function addNoOpener( link ) { if ( link instanceof HTMLAnchorElement ) { const relAttr = attr( link, 'rel' ) || ''; if ( !relAttr.includes( 'noopener' ) ) { - attr( link, 'rel', isset( relAttr ) ? `${relAttr} noopener` : 'noopener' ); + attr( link, 'rel', relAttr !== '' ? `${relAttr} noopener` : 'noopener' ); } } } @@ -687,17 +1009,17 @@ const aemi = new Environment(); aemi.parallel( [ aemi.set( 'global', getGlobal() ), - aemi.set( 'site-header', byId( 'site-header' ) ), - aemi.set( 'site-loop', byId( 'site-loop' ) ), - aemi.set( 'first-header', byClass( 'post-header' )[0] ), - aemi.set( 'nav-toggle', byId( 'navigation-toggle' ) ), - aemi.set( 'sea-toggle', byId( 'search-toggle' ) ), - aemi.set( 'sea-input', byId( 'search-input-0' ) ), - aemi.set( 'pro-bar', byId( 'site-progress-bar' ) ), - aemi.set( 'csh-sel', byId( 'color-scheme-selector' ) ), - aemi.set( 'csh-light', byId( 'light-scheme-option' ) ), - aemi.set( 'csh-dark', byId( 'dark-scheme-option' ) ), - aemi.set( 'csh-auto', byId( 'auto-scheme-option' ) ) + aemi.set( 'site-header', document.getElementById( 'site-header' ) ), + aemi.set( 'site-loop', document.getElementById( 'site-loop' ) ), + aemi.set( 'first-header', document.getElementsByClassName( 'post-header' )[0] ), + aemi.set( 'nav-toggle', document.getElementById( 'navigation-toggle' ) ), + aemi.set( 'sea-toggle', document.getElementById( 'search-toggle' ) ), + aemi.set( 'sea-input', document.getElementById( 'search-input-0' ) ), + aemi.set( 'pro-bar', document.getElementById( 'site-progress-bar' ) ), + aemi.set( 'csh-sel', document.getElementById( 'color-scheme-selector' ) ), + aemi.set( 'csh-light', document.getElementById( 'light-scheme-option' ) ), + aemi.set( 'csh-dark', document.getElementById( 'dark-scheme-option' ) ), + aemi.set( 'csh-auto', document.getElementById( 'auto-scheme-option' ) ) ] ); class ColorScheme { @@ -705,28 +1027,54 @@ class ColorScheme { this.scheme = ColorScheme.init(); } + /** + * Détecte la présence d'un cookie de préférence du ColorScheme + * @returns {Boolean} Retourne vrai si un cookie est configuré + */ static hasCookie() { return Cookies.has( 'color-scheme' ); } + /** + * Configure un cookie de préférence de ColorScheme + * @param {String} scheme Scheme + * @returns {Boolean} Retourne vrai si le cookie a bien été configuré + */ static setCookie( scheme ) { Cookies.set( 'color-scheme', scheme ); return ColorScheme.getCookiesState() === scheme; } + /** + * Supprime le cookie de préférence du ColorScheme + * @returns {Boolean} Retourne vrai si le cookie a été supprimé + */ static deleteCookie() { Cookies.delete( 'color-scheme' ); return Cookies.has( 'color-scheme' ) === false; } + /** + * Retourne le ColorScheme opposé de celui passé entre paramètre + * @param {String} scheme + * @returns {'light'|'dark'|null} + */ static getOppositeState( scheme ) { - return isset( scheme ) ? scheme === 'light' ? 'dark' : 'light' : null; + return scheme && scheme !== '' ? scheme === 'light' ? 'dark' : 'light' : null; } + /** + * Détecte si le ColorScheme doit être configurée de manière automatique + * @returns {Boolean} + */ static getAutoState() { return hasClass( document.body, 'color-scheme-auto' ); } + /** + * Retourne le ColorScheme pré-configuré + * @returns {'light'|'dark'|null} + */ static getClassState() { if ( hasClass( document.body, 'color-scheme-light' ) ) { return 'light'; @@ -737,53 +1085,78 @@ class ColorScheme { return null; } + /** + * Détecte si l'utilisateur a la possibilité de changer le ColorScheme et retourne vrai si un sélécteur de ColorScheme a été trouvé + * @returns {Boolean} + */ static getUserState() { if ( aemi instanceof Environment ) { return aemi.assert( 'csh-sel' ); } - return is( byId( 'color-scheme-selector' ) ); + return !!document.getElementById( 'color-scheme-selector' ); } + /** + * Retourne le cookie de ColorScheme configuré + */ static getCookiesState() { const preference = Cookies.get( 'color-scheme' ); - return isset( preference ) ? preference : null; + return preference && preference !== '' ? preference : null; } + /** + * Retourne le ColorScheme système compris par le navigateur ou null + * @returns {'light'|'dark'|null} + */ static getBrowerState() { try { const matchMedia = window.matchMedia( '(prefers-color-scheme: light' ); return matchMedia.matches !== 'not all' ? matchMedia.matches ? 'light' : 'dark' : null; } catch ( error ) { - catchError( error ); + console.error( error ); return null; } } + /** + * Retourne le ColorScheme actuellement utilisé + * @returns {'light'|'dark'|null} + */ static getState() { - + /* Récupère la classe de ColorScheme préconfigurée si existante, sinon null */ const classState = ColorScheme.getClassState(); + /* Récupère la possibilité qu'a l'utilisateur de changer le ColorScheme, sinon null */ const userState = ColorScheme.getUserState(); + /* Récupère le cookie de préférence du */ const cookieState = ColorScheme.getCookiesState(); const browserState = ColorScheme.getBrowerState(); - - if ( isset( classState ) ) { + /* Si une classe de ColorScheme est trouvée sur le document.body */ + if ( classState ) { + /* Si l'utilisateur a la possibilité de changer le ColorScheme */ if ( userState ) { - if ( isset( cookieState ) ) { + /* Si un cookie est déjà configuré */ + if ( cookieState ) { + /* Retourne le cookie de préférence du ColorScheme */ return cookieState; } - } else { + } + /*C'est bon je suis là */ + /* Lis les commentaires depuis le début de ColorScheme */ + /* Sinon supprimer un cookie qui peut avoir existé avant --> ca évite un comportement indéfini */ + else { ColorScheme.deleteCookie(); } + /* Retourne la classe de ColorScheme actuelle */ return classState; } if ( userState ) { - if ( isset( cookieState ) ) { + if ( cookieState ) { return cookieState; } } else { ColorScheme.deleteCookie(); } - if ( isset( browserState ) ) { + if ( browserState ) { return browserState; } return 'light'; @@ -843,16 +1216,16 @@ class ColorScheme { } static init() { - const support = isset( ColorScheme.getBrowerState() ); + const support = ColorScheme.getBrowerState() ? true : false; const matchMedia = window.matchMedia( '(prefers-color-scheme: light)' ); if ( support && 'addEventListener' in matchMedia ) { - matchMedia.addEventListener( 'change', ( event ) => { + matchMedia.addEventListener( 'change', () => { const autoState = ColorScheme.getAutoState(); const classState = ColorScheme.getClassState(); const userState = ColorScheme.getUserState(); const cookieState = ColorScheme.getCookiesState(); const browserState = ColorScheme.getBrowerState(); - if ( userState && isset( cookieState ) && cookieState === 'auto' || !( isset( classState ) && !autoState || userState ) ) { + if ( userState && cookieState && cookieState === 'auto' || !( classState && !autoState || userState ) ) { ColorScheme.change( browserState, false ); } } ); @@ -897,7 +1270,7 @@ aemi.push( async function aemi_color_scheme() { if ( aemi instanceof Environment ) { support = aemi.assert( 'csh-sel' ); } else { - support = isset( byId( 'color-scheme-selector' ) ); + support = !!document.getElementById( 'color-scheme-selector' ); } if ( support ) { aemi.get( `csh-${scheme}` ).checked = true; @@ -913,825 +1286,10 @@ aemi.push( async function aemi_color_scheme() { } } ); -class Lightbox { - constructor ( options, name ) { - this.Pr = { - na: name || 'lightbox', - bo: document.body, - te: null, - Cu: { - ir: null, - gr: null, - th: null, - im: {}, - is: [] - }, - An: { - el: null, - in: null, - ch: [], - ti: null - }, - Co: { - nb: null, - pb: null - }, - Re: { - mh: null, - mw: null, - nh: null, - nw: null - }, - St: { op: false } - }; - this.Pr.te = ecs( { - id: this.in( 'content-wrapper' ), - class: [this.cn( 'content-wrapper' )] - } ); - this.Pu = { - op: options || {}, - bo: null, - wr: null, - th: [] - }; - Object.preventExtensions( this.Pr ); - Object.preventExtensions( this.Pu ); - } - /** Lightbox Name */ - get name() { - return this.Pr.na; - } - /** Document Body */ - get body() { - return this.Pr.bo; - } - /** Template */ - get template() { - return this.Pr.te; - } - /** Current Image Ratio */ - get currImgRatio() { - return this.Pr.Cu.ir; - } - set currImgRatio( value ) { - this.Pr.Cu.ir = value; - } - /** Current Group */ - get currGroup() { - return this.Pr.Cu.gr; - } - set currGroup( value ) { - this.Pr.Cu.gr = value; - } - /** First Clicked Image */ - get currThumbnail() { - return this.Pr.Cu.th; - } - set currThumbnail( value ) { - this.Pr.Cu.th = value; - } - /** Currently Shown Image */ - get currImage() { - return this.Pr.Cu.im; - } - set currImage( value ) { - this.Pr.Cu.im = value; - } - /** Images belonging to current group */ - get currImages() { - return this.Pr.Cu.is; - } - /** Images belonging to current group */ - set currImages( value ) { - this.Pr.Cu.is = value; - } - /** Reference to Animation Element */ - get animElement() { - return this.Pr.An.el; - } - set animElement( value ) { - this.Pr.An.el = value; - } - /** Animation Interval */ - get animInterval() { - return this.Pr.An.in; - } - set animInterval( value ) { - this.Pr.An.in = value; - } - /** Childs to Animate */ - get animChildren() { - return this.Pr.An.ch; - } - /** Timeout until animation starts */ - get animTimeout() { - return this.Pr.An.ti; - } - set animTimeout( value ) { - this.Pr.An.ti = value; - } - /** Next Button */ - get nextButton() { - return this.Pr.Co.nb; - } - set nextButton( value ) { - this.Pr.Co.nb = value; - } - /** Previous Button */ - get prevButton() { - return this.Pr.Co.pb; - } - set prevButton( value ) { - this.Pr.Co.pb = value; - } - get maxHeight() { - return this.Pr.Re.mh; - } - set maxHeight( value ) { - this.Pr.Re.mh = value; - } - get maxWidth() { - return this.Pr.Re.mw; - } - set maxWidth( value ) { - this.Pr.Re.mw = value; - } - get newImageHeight() { - return this.Pr.Re.nh; - } - set newImageHeight( value ) { - this.Pr.Re.nh = value; - } - get newImageWidth() { - return this.Pr.Re.nw; - } - set newImageWidth( value ) { - this.Pr.Re.nw = value; - } - /** Is box opened ? */ - get isOpen() { - return this.Pr.St.op; - } - set isOpen( value ) { - this.Pr.St.op = value; - } - /** Lightbox Options */ - get options() { - return this.Pu.op; - } - set options( value ) { - this.Pu.op = value; - } - /** Lightbox */ - get box() { - return this.Pu.bo; - } - set box( value ) { - this.Pu.bo = value; - } - /** Lightbox Wrapper */ - get wrapper() { - return this.Pu.wr; - } - set wrapper( value ) { - this.Pu.wr = value; - } - /** List of Thumbnails */ - get thumbnails() { - return this.Pu.th; - } - /** Window Height */ - get height() { - return window.innerHeight; - } - /** Window Width */ - get width() { - return window.innerWidth; - } - in( _ ) { - return `${this.name}${isset( _ ) ? '-' + _ : ''}`; - } - cn( _ ) { - return `${this.name}${isset( _ ) ? '-' + _ : ''}`; - } - dn( _ ) { - return `data-${this.cn( _ )}`; - } - push() { - for ( const el of arguments ) { - el.addEventListener( 'click', ( event ) => { - inhibitEvent( event ); - this.currGroup = attr( el, this.dn( 'group' ) ) || false; - this.currThumbnail = el; - this.open( el, false, false, false ); - } ); - } - this.thumbnails.push( ...arguments ); - } - getByGroup( g ) { - return [ - ...this.thumbnails.filter( ( t ) => attr( t, this.dn( 'group' ) ) === g ) - ]; - } - getPosition( t, g ) { - const ts = this.getByGroup( g ); - for ( let i = 0, l = ts.length; i < l; i += 1 ) { - const c1 = attr( t, 'src' ) === attr( ts[i], 'src' ); - const c2 = - attr( t, this.dn( 'index' ) ) === attr( ts[i], this.dn( 'index' ) ); - const c3 = attr( t, this.dn() ) === attr( ts[i], this.dn() ); - if ( c1 && c2 && c3 ) { - return i; - } - } - } - prepare( arg ) { - const { - wrapperSelectors: wsl, - itemSelectors: isl, - captionSelectors: csl - } = arg; - const jws = ( wsl[0] ? [...wsl] : [wsl] ).join( ',' ); - const jis = ( isl[0] ? [...isl] : [isl] ).join( ',' ); - const jcs = ( csl[0] ? [...csl] : [csl] ).join( ',' ); - const qws = [...document.querySelectorAll( jws )]; - if ( qws.length > 0 ) { - qws.forEach( ( qwsi, i ) => { - for ( const item of qwsi.querySelectorAll( jis ) ) { - const el = - item.getElementsByTagName( 'a' )[0] || - item.getElementsByTagName( 'img' )[0]; - if ( el.tagName === 'A' ) { - if ( /\.(jpg|gif|png)$/.test( el.href ) ) { - attr( el, this.dn(), el.href ); - attr( el, this.dn( 'group' ), i ); - const caption = item.querySelector( jcs ); - if ( is( caption ) ) { - attr( el, this.dn( 'caption' ), caption.innerText ); - } - } - } else { - attr( el, this.dn(), el.src ); - attr( el, this.dn( 'group' ), i ); - const caption = item.querySelector( jcs ); - if ( is( caption ) ) { - attr( el, this.dn( 'caption' ), caption.innerText ); - } - } - } - } ); - this.load(); - for ( const arg of qws ) { - for ( const item of arg.querySelectorAll( jis ) ) { - const caption = item.querySelector( jcs ); - if ( is( caption ) ) { - caption.addEventListener( 'click', ( event ) => { - inhibitEvent( event ); - item.querySelector( 'a, img' ).dispatchEvent( - new Event( 'click' ) - ); - } ); - } - } - } - } - } - preload() { - const { - currGroup: cg, - currImages: cis, - currThumbnail: ct - } = this; - if ( !cg ) { - return false; - } - const prev = new Image(); - const next = new Image(); - const pos = this.getPosition( ct, cg ); - if ( pos === cis.length - 1 ) { - prev.src = - attr( cis[cis.length - 1], this.dn() ) || cis[cis.length - 1].src; - next.src = attr( cis[0], this.dn() ) || cis[0].src; - } else if ( pos === 0 ) { - prev.src = - attr( cis[cis.length - 1], this.dn() ) || cis[cis.length - 1].src; - next.src = attr( cis[1], this.dn() ) || cis[1].src; - } else { - prev.src = attr( cis[pos - 1], this.dn() ) || cis[pos - 1].src; - next.src = attr( cis[pos + 1], this.dn() ) || cis[pos + 1].src; - } - } - startAnimation() { - const { options: { loadingAnimation: lA } } = this; - this.stopAnimation(); - this.animTimeout = setTimeout( () => { - addClass( this.box, this.cn( 'loading' ) ); - if ( isNumber( lA ) ) { - let index = 0; - this.animInterval = setInterval( () => { - addClass( this.animChildren[index], this.cn( 'active' ) ); - setTimeout( () => { - removeClass( - this.animChildren[index], - this.cn( 'active' ) - ); - }, lA ); - index = index >= this.animChildren.length ? 0 : ++index; - }, lA ); - } - }, 500 ); - } - stopAnimation() { - const { options: { loadingAnimation: lA } } = this; - removeClass( this.box, this.cn( 'loading' ) ); - if ( !isString( lA ) && lA ) { - clearInterval( this.animInterval ); - for ( const child of this.animChildren ) { - removeClass( child, this.cn( 'active' ) ); - } - } - } - initializeControls() { - if ( !this.nextButton ) { - const ni = this.options.nextImage; - this.nextButton = ecs( { - $: 'span', - class: [this.cn( 'next' ), ...!ni ? [this.cn( 'no-img' )] : []], - _: [ - ...ni ? - [{ - $: 'img', - attr: { src: this.options.nextImage } - }] : - [] - ], - events: [ - [ - 'click', - ( ev ) => { - inhibitEvent( ev ); - this.next(); - } - ] - ] - } ); - this.box.appendChild( this.nextButton ); - } - addClass( this.nextButton, this.cn( 'active' ) ); - if ( !this.prevButton ) { - const pi = this.options.prevImage; - this.prevButton = ecs( { - $: 'span', - class: [this.cn( 'prev' ), ...!pi ? [this.cn( 'no-img' )] : []], - _: [ - ...pi ? - [{ - $: 'img', - attr: { src: this.options.prevImage } - }] : - [] - ], - events: [ - [ - 'click', - ( ev ) => { - inhibitEvent( ev ); - this.prev(); - } - ] - ] - } ); - this.box.appendChild( this.prevButton ); - } - addClass( this.prevButton, this.cn( 'active' ) ); - } - repositionControls() { - if ( this.options.responsive && this.nextButton && this.prevButton ) { - const shift = this.height / 2 - this.nextButton.offsetHeight / 2; - this.nextButton.style.top = shift + 'px'; - this.prevButton.style.top = shift + 'px'; - } - } - setOptions( _ ) { - _ = _ || {}; - - function setBooleanValue( variable, def ) { - return isBoolean( variable ) ? variable : def || false; - } - - function setStringValue( variable, def ) { - return isString( variable ) ? variable : def || false; - } - this.options = { - boxId: _.boxId || false, - controls: setBooleanValue( _.controls, true ), - dimensions: setBooleanValue( _.dimensions, true ), - captions: setBooleanValue( _.captions, true ), - prevImage: setStringValue( _.prevImage, false ), - nextImage: setStringValue( _.nextImage, false ), - hideCloseButton: _.hideCloseButton || false, - closeOnClick: setBooleanValue( _.closeOnClick, true ), - nextOnClick: setBooleanValue( _.nextOnClick, true ), - loadingAnimation: is( _.loadingAnimation ) ? - _.loadingAnimation : - true, - animationElementCount: _.animationElementCount || 4, - preload: setBooleanValue( _.preload, true ), - carousel: setBooleanValue( _.carousel, true ), - animation: isNumber( _.animation ) || _.animation === false ? - _.animation : - 400, - responsive: setBooleanValue( _.responsive, true ), - maxImageSize: _.maxImageSize || 0.8, - keyControls: setBooleanValue( _.keyControls, true ), - hideOverflow: _.hideOverflow || true, - onopen: _.onopen || false, - onclose: _.onclose || false, - onload: _.onload || false, - onresize: _.onresize || false, - onloaderror: _.onloaderror || false, - onimageclick: isFunction( _.onimageclick ) ? _.onimageclick : false - }; - let { - boxId, - controls, - dimensions, - captions, - prevImage, - nextImage, - hideCloseButton, - closeOnClick, - nextOnClick, - loadingAnimation, - animationElementCount, - preload, - carousel, - animation, - responsive, - maxImageSize, - keyControls, - hideOverflow, - onopen, - onclose, - onload, - onresize, - onloaderror, - onimageclick - } = this.options; - if ( boxId ) { - this.box = byId( this.options.boxId ); - addClass( this.box, this.cn() ); - } else if ( !this.box ) { - const el = byId( this.in() ) || ecs( { id: this.in() } ); - addClass( el, this.cn() ); - this.box = el; - this.body.appendChild( this.box ); - } - this.box.appendChild( this.template ); - this.wrapper = byId( this.in( 'content-wrapper' ) ); - if ( !hideCloseButton ) { - this.box.appendChild( - ecs( { - $: 'span', - id: this.in( 'close' ), - class: [this.cn( 'close' )], - _: ['✗'], - events: [ - [ - 'click', - ( ev ) => { - inhibitEvent( ev ); - this.close(); - } - ] - ] - } ) - ); - } - if ( closeOnClick ) { - this.box.addEventListener( 'click', ( ev ) => { - inhibitEvent( ev ); - this.close(); - } ); - } - if ( isString( loadingAnimation ) ) { - this.animElement = new Image(); - this.animElement.src = loadingAnimation; - addClass( this.animElement, this.cn( 'loading-animation' ) ); - this.box.appendChild( this.animElement ); - } else if ( loadingAnimation ) { - loadingAnimation = isNumber( loadingAnimation ) ? - loadingAnimation : - 200; - this.animElement = ecs( { class: [this.cn( 'loading-animation' )] } ); - for ( let i = 0; i < animationElementCount; i += 1 ) { - this.animChildren.push( - this.animElement.appendChild( document.createElement( 'span' ) ) - ); - } - this.box.appendChild( this.animElement ); - } - if ( responsive ) { - window.addEventListener( 'resize', () => { - this.resize(); - if ( this.isOpen ) { - blockScroll( this.options.env ); - } - } ); - } - if ( keyControls ) { - document.addEventListener( 'keydown', ( ev ) => { - if ( this.isOpen ) { - inhibitEvent( ev ); - ( { - 27: () => this.close(), - 37: () => this.prev(), - 39: () => this.next() - }[ev.keyCode]() ); - } - } ); - } - } - open( el, gr, callback, event ) { - if ( el && gr ) { - gr = false; - } - if ( !el && !gr ) { - return false; - } - this.currGroup = gr || this.currGroup || attr( el, this.dn( 'group' ) ); - if ( this.currGroup ) { - this.currImages = this.getByGroup( this.currGroup ); - if ( el === false ) { - el = this.currImages[0]; - } - } - this.currImage.img = new Image(); - this.currThumbnail = el; - let src; - if ( isString( el ) ) { - src = el; - } else if ( attr( el, this.dn() ) ) { - src = attr( el, this.dn() ); - } else { - src = el.src; - } - this.currImgRatio = false; - if ( !this.isOpen ) { - if ( isNumber( this.options.animation ) ) { - addClass( this.currImage.img, this.cn( 'animate-transition' ) ); - addClass( this.currImage.img, this.cn( 'animate-init' ) ); - } - this.isOpen = true; - if ( this.options.onopen ) { - this.options.onopen( this.currImage ); - } - } - if ( - !this.options || - !is( this.options.hideOverflow ) || - this.options.hideOverflow - ) { - blockScroll( this.body ); - } - this.box.style.paddingTop = '0'; - this.wrapper.innerHTML = ''; - this.wrapper.appendChild( this.currImage.img ); - if ( this.options.animation ) { - addClass( this.wrapper, this.cn( 'animate' ) ); - } - const captionText = attr( el, this.dn( 'caption' ) ); - if ( captionText && this.options.captions ) { - this.wrapper.appendChild( - ecs( { - $: 'p', - class: [this.cn( 'caption' )], - _: [captionText] - } ) - ); - } - addClass( this.box, this.cn( 'active' ) ); - if ( this.options.controls && this.currImages.length > 1 ) { - this.initializeControls(); - this.repositionControls(); - } - this.currImage.img.addEventListener( 'error', ( imageErrorEvent ) => { - if ( this.options.onloaderror ) { - imageErrorEvent._happenedWhile = event ? event : false; - this.options.onloaderror( imageErrorEvent ); - } - } ); - this.currImage.img.addEventListener( 'load', ( ev ) => { - const { target } = ev; - this.currImage.originalWidth = target.naturalWidth || target.width; - this.currImage.originalHeight = - target.naturalHeight || target.height; - const checkClassInt = setInterval( () => { - if ( hasClass( this.box, this.cn( 'active' ) ) ) { - addClass( this.wrapper, this.cn( 'wrapper-active' ) ); - if ( isNumber( this.options.animation ) ) { - addClass( - this.currImage.img, - this.cn( 'animate-transition' ) - ); - } - if ( callback ) { - callback(); - } - this.stopAnimation(); - clearTimeout( this.animTimeout ); - if ( this.options.preload ) { - this.preload(); - } - if ( this.options.nextOnClick ) { - addClass( this.currImage.img, this.cn( 'next-on-click' ) ); - this.currImage.img.addEventListener( 'click', ( ev ) => { - inhibitEvent( ev ); - this.next(); - } ); - } - if ( this.options.onimageclick ) { - this.currImage.img.addEventListener( 'click', ( ev ) => { - inhibitEvent( ev ); - this.options.onimageclick( this.currImage ); - } ); - } - if ( this.options.onload ) { - this.options.onload( event ); - } - clearInterval( checkClassInt ); - this.resize(); - } - }, 10 ); - } ); - this.currImage.img.src = src; - this.startAnimation(); - } - load( _ ) { - _ = _ || this.options; - this.setOptions( _ ); - this.push( - ...[...document.querySelectorAll( '[' + this.dn() + ']' )].map( - ( item, index ) => { - if ( attr( item, this.dn() ) ) { - attr( item, this.dn( 'index' ), index ); - } - return item; - } - ) - ); - } - resize() { - if ( !this.currImage.img ) { - return; - } - this.maxWidth = this.width; - this.maxHeight = this.height; - const boxWidth = this.box.offsetWidth; - const boxHeight = this.box.offsetHeight; - if ( - !this.currImgRatio && - this.currImage.img && - this.currImage.img.offsetWidth && - this.currImage.img.offsetHeight - ) { - this.currImgRatio = - this.currImage.img.offsetWidth / - this.currImage.img.offsetHeight; - } - // Height of image is too big to fit in viewport - if ( Math.floor( boxWidth / this.currImgRatio ) > boxHeight ) { - this.newImageWidth = boxHeight * this.currImgRatio; - this.newImageHeight = boxHeight; - } else { // Width of image is too big to fit in viewport - this.newImageHeight = boxWidth; - this.newImageWidth = boxWidth / this.currImgRatio; - } - // decrease size with modifier - this.newImageWidth = Math.floor( - this.newImageWidth * this.options.maxImageSize - ); - this.newImageHeight = Math.floor( - this.newImageHeight * this.options.maxImageSize - ); - // check if image exceeds maximum size - if ( - this.options.dimensions && - this.newImageHeight > this.currImage.originalHeight || - this.options.dimensions && - this.newImageWidth > this.currImage.originalWidth - ) { - this.newImageHeight = this.currImage.originalHeight; - this.newImageWidth = this.currImage.originalWidth; - } - attr( this.currImage.img, 'width', this.newImageWidth ); - attr( this.currImage.img, 'height', this.newImageHeight ); - // reposition controls after timeout - setTimeout( () => { - this.repositionControls(); - }, 200 ); - if ( this.options.onresize ) { - this.options.onresize( this.currImage ); - } - } - next() { - if ( !this.currGroup ) { - return; - } - const $0 = this.getPosition( this.currThumbnail, this.currGroup ) + 1; - if ( this.currImages[$0] ) { - this.currThumbnail = this.currImages[$0]; - } else if ( this.options.carousel ) { - this.currThumbnail = this.currImages[0]; - } else { - return; - } - if ( isNumber( this.options.animation ) ) { - removeClass( this.currImage.img, this.cn( 'animating-next' ) ); - setTimeout( () => { - this.open( - this.currThumbnail, - false, - () => { - setTimeout( () => { - addClass( - this.currImage.img, - this.cn( 'animating-next' ) - ); - }, this.options.animation / 2 ); - }, - 'next' - ); - }, this.options.animation / 2 ); - } else { - this.open( this.currThumbnail, false, false, 'next' ); - } - } - prev() { - if ( !this.currGroup ) { - return; - } - const $0 = this.getPosition( this.currThumbnail, this.currGroup ) - 1; - if ( this.currImages[$0] ) { - this.currThumbnail = this.currImages[$0]; - } else if ( this.options.carousel ) { - this.currThumbnail = this.currImages[this.currImages.length - 1]; - } else { - return; - } - if ( isNumber( this.options.animation ) ) { - removeClass( this.currImage.img, this.cn( 'animating-next' ) ); - setTimeout( () => { - this.open( - this.currThumbnail, - false, - () => { - setTimeout( () => { - addClass( - this.currImage.img, - this.cn( 'animating-next' ) - ); - }, this.options.animation / 2 ); - }, - 'prev' - ); - }, this.options.animation / 2 ); - } else { - this.open( this.currThumbnail, false, false, 'prev' ); - } - } - close() { - this.currGroup = null; - this.currThumbnail = null; - const img = this.currImage; - this.currImage = {}; - while ( this.currImages.length ) { - this.currImages.pop(); - } - this.isOpen = false; - removeClass( this.box, this.cn( 'active' ) ); - removeClass( this.wrapper, this.cn( 'wrapper-active' ) ); - removeClass( this.nextButton, this.cn( 'active' ) ); - removeClass( this.prevButton, this.cn( 'active' ) ); - this.box.style.paddingTop = '0px'; - this.stopAnimation(); - if ( - !this.options || - !is( this.options.hideCloseButton ) || - this.options.hideOverflow - ) { - freeScroll( this.body ); - } - if ( this.options.onclose ) { - this.options.onclose( img ); - } - } -} - aemi.push( async function aemi_menu() { - for ( const menu of byClass( 'menu' ) ) { + for ( const menu of document.getElementsByClassName( 'menu' ) ) { if ( !['header-menu', 'header-social', 'footer-menu'].includes( menu.id ) ) { - for ( const parent of byClass( 'menu-item-has-children', menu ) ) { + for ( const parent of document.getElementsByClassName( 'menu-item-has-children', menu ) ) { if ( parent.getElementsByTagName( 'li' ).length > 0 ) { parent.insertBefore( ecs( { @@ -1752,7 +1310,7 @@ aemi.push( async function aemi_loop() { const entries = loop.getElementsByClassName( 'entry' ); for ( const entry of entries ) { const anchor = entry.getElementsByTagName( 'a' )[0]; - if ( is( anchor ) ) { + if ( anchor ) { entry.addEventListener( 'click', () => { anchor.click(); } ); @@ -1772,35 +1330,16 @@ aemi.push( async function aemi_form_fix() { } } ); aemi.push( async function aemi_toggle() { - for ( const toggler of byClass( 'toggle' ) ) { + for ( const toggler of document.getElementsByClassName( 'toggle' ) ) { toggler.addEventListener( 'click', () => { const id = data( toggler, 'target' ); - if ( isset( id ) ) { - doToggle( byId( id ) ); + if ( id && id !== '' ) { + doToggle( document.getElementById( id ) ); } doToggle( toggler ); } ); } } ); -aemi.push( async function aemi_galleries() { - return new Lightbox( { env: aemi } ).prepare( { - wrapperSelectors: [ - '.gallery', - '.blocks-gallery-grid', - '.wp-block-gallery', - '.justified-gallery' - ], - itemSelectors: [ - '.gallery-item', - '.blocks-gallery-item', - '.jg-entry' - ], - captionSelectors: [ - 'figcaption', - '.gallery-caption' - ] - } ); -} ); aemi.push( async function aemi_view_handler() { const classScrolled = 'page-scrolled'; const classHidden = 'header-hidden'; @@ -1858,7 +1397,7 @@ aemi.push( async function aemi_view_handler() { ); } ); const aemi_progress_bar = () => - requestFrame( () => { + requestAnimationFrame( () => { const totalHeight = document.body.clientHeight - window.innerHeight; const progress = window.scrollY / totalHeight; @@ -1895,10 +1434,10 @@ aemi.push( async function aemi_mutation_observer() { function togglerHandler( mutationRecords ) { for ( const { target } of mutationRecords ) { - const alts = toggleFilter.filter( e => e !== target ); + const alts = toggleFilter.filter( e => e && e !== target ); if ( isToggled( target ) ) { for ( const alt of alts.filter( e => isToggled( e ) ) ) { - doToggle( byId( data( alt, 'target' ) ) ); + doToggle( document.getElementById( data( alt, 'target' ) ) ); doToggle( alt ); } } @@ -1911,17 +1450,21 @@ aemi.push( async function aemi_mutation_observer() { } } const togglerObserver = new MutationObserver( togglerHandler ); - togglerObserver.observe( aemi.get( 'nav-toggle' ), { - attributes: true, - attributeFilter: ['class'] - } ); - togglerObserver.observe( aemi.get( 'sea-toggle' ), { - attributes: true, - attributeFilter: ['class'] - } ); + if ( aemi.assert( 'nav-toggle' ) ) { + togglerObserver.observe( aemi.get( 'nav-toggle' ), { + attributes: true, + attributeFilter: ['class'] + } ); + } + if ( aemi.assert( 'sea-toggle' ) ) { + togglerObserver.observe( aemi.get( 'sea-toggle' ), { + attributes: true, + attributeFilter: ['class'] + } ); + } function colorSchemeHandler( mutationRecords ) { - for ( const { target } of mutationRecords ) { + for ( const _ of mutationRecords ) { if ( schemeCoherenceCondition() ) { changeHeaderScheme(); } else if ( hasClass( document.body, 'has-post-thumbnail' ) ) { @@ -1948,12 +1491,11 @@ aemi.push( async function aemi_link_tweaking() { hash = url.hash; external = window.location.origin !== url.origin; scrollable = !external && - window.location.pathname === url.pathname && - isset( hash ); + window.location.pathname === url.pathname && !!hash; } catch ( _ ) { if ( link.href.indexOf( '#' ) >= 0 ) { hash = link.href.split( '?' )[0]; - scrollable = isset( hash ); + scrollable = !!hash; } } if ( external ) {