diff options
Diffstat (limited to 'present/js/utils')
| -rw-r--r-- | present/js/utils/color.js | 77 | ||||
| -rw-r--r-- | present/js/utils/constants.js | 10 | ||||
| -rw-r--r-- | present/js/utils/device.js | 8 | ||||
| -rw-r--r-- | present/js/utils/loader.js | 46 | ||||
| -rw-r--r-- | present/js/utils/util.js | 313 | 
5 files changed, 454 insertions, 0 deletions
| diff --git a/present/js/utils/color.js b/present/js/utils/color.js new file mode 100644 index 0000000..edf67c4 --- /dev/null +++ b/present/js/utils/color.js @@ -0,0 +1,77 @@ +/** + * Converts various color input formats to an {r:0,g:0,b:0} object. + * + * @param {string} color The string representation of a color + * @example + * colorToRgb('#000'); + * @example + * colorToRgb('#000000'); + * @example + * colorToRgb('rgb(0,0,0)'); + * @example + * colorToRgb('rgba(0,0,0)'); + * + * @return {{r: number, g: number, b: number, [a]: number}|null} + */ +export const colorToRgb = ( color ) => { + +	let hex3 = color.match( /^#([0-9a-f]{3})$/i ); +	if( hex3 && hex3[1] ) { +		hex3 = hex3[1]; +		return { +			r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11, +			g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11, +			b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11 +		}; +	} + +	let hex6 = color.match( /^#([0-9a-f]{6})$/i ); +	if( hex6 && hex6[1] ) { +		hex6 = hex6[1]; +		return { +			r: parseInt( hex6.slice( 0, 2 ), 16 ), +			g: parseInt( hex6.slice( 2, 4 ), 16 ), +			b: parseInt( hex6.slice( 4, 6 ), 16 ) +		}; +	} + +	let rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i ); +	if( rgb ) { +		return { +			r: parseInt( rgb[1], 10 ), +			g: parseInt( rgb[2], 10 ), +			b: parseInt( rgb[3], 10 ) +		}; +	} + +	let rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i ); +	if( rgba ) { +		return { +			r: parseInt( rgba[1], 10 ), +			g: parseInt( rgba[2], 10 ), +			b: parseInt( rgba[3], 10 ), +			a: parseFloat( rgba[4] ) +		}; +	} + +	return null; + +} + +/** + * Calculates brightness on a scale of 0-255. + * + * @param {string} color See colorToRgb for supported formats. + * @see {@link colorToRgb} + */ +export const colorBrightness = ( color ) => { + +	if( typeof color === 'string' ) color = colorToRgb( color ); + +	if( color ) { +		return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000; +	} + +	return null; + +}
\ No newline at end of file diff --git a/present/js/utils/constants.js b/present/js/utils/constants.js new file mode 100644 index 0000000..43fcb84 --- /dev/null +++ b/present/js/utils/constants.js @@ -0,0 +1,10 @@ + +export const SLIDES_SELECTOR = '.slides section'; +export const HORIZONTAL_SLIDES_SELECTOR = '.slides>section'; +export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section'; + +// Methods that may not be invoked via the postMessage API +export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/; + +// Regex for retrieving the fragment style from a class attribute +export const FRAGMENT_STYLE_REGEX = /fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/;
\ No newline at end of file diff --git a/present/js/utils/device.js b/present/js/utils/device.js new file mode 100644 index 0000000..f2bce20 --- /dev/null +++ b/present/js/utils/device.js @@ -0,0 +1,8 @@ +const UA = navigator.userAgent; + +export const isMobile = /(iphone|ipod|ipad|android)/gi.test( UA ) || +						( navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 ); // iPadOS + +export const isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA ); + +export const isAndroid = /android/gi.test( UA );
\ No newline at end of file diff --git a/present/js/utils/loader.js b/present/js/utils/loader.js new file mode 100644 index 0000000..58d39ac --- /dev/null +++ b/present/js/utils/loader.js @@ -0,0 +1,46 @@ +/** + * Loads a JavaScript file from the given URL and executes it. + * + * @param {string} url Address of the .js file to load + * @param {function} callback Method to invoke when the script + * has loaded and executed + */ +export const loadScript = ( url, callback ) => { + +	const script = document.createElement( 'script' ); +	script.type = 'text/javascript'; +	script.async = false; +	script.defer = false; +	script.src = url; + +	if( typeof callback === 'function' ) { + +		// Success callback +		script.onload = script.onreadystatechange = event => { +			if( event.type === 'load' || /loaded|complete/.test( script.readyState ) ) { + +				// Kill event listeners +				script.onload = script.onreadystatechange = script.onerror = null; + +				callback(); + +			} +		}; + +		// Error callback +		script.onerror = err => { + +			// Kill event listeners +			script.onload = script.onreadystatechange = script.onerror = null; + +			callback( new Error( 'Failed loading script: ' + script.src + '\n' + err ) ); + +		}; + +	} + +	// Append the script at the end of <head> +	const head = document.querySelector( 'head' ); +	head.insertBefore( script, head.lastChild ); + +}
\ No newline at end of file diff --git a/present/js/utils/util.js b/present/js/utils/util.js new file mode 100644 index 0000000..a5515e8 --- /dev/null +++ b/present/js/utils/util.js @@ -0,0 +1,313 @@ +/** + * Extend object a with the properties of object b. + * If there's a conflict, object b takes precedence. + * + * @param {object} a + * @param {object} b + */ +export const extend = ( a, b ) => { + +	for( let i in b ) { +		a[ i ] = b[ i ]; +	} + +	return a; + +} + +/** + * querySelectorAll but returns an Array. + */ +export const queryAll = ( el, selector ) => { + +	return Array.from( el.querySelectorAll( selector ) ); + +} + +/** + * classList.toggle() with cross browser support + */ +export const toggleClass = ( el, className, value ) => { +	if( value ) { +		el.classList.add( className ); +	} +	else { +		el.classList.remove( className ); +	} +} + +/** + * Utility for deserializing a value. + * + * @param {*} value + * @return {*} + */ +export const deserialize = ( value ) => { + +	if( typeof value === 'string' ) { +		if( value === 'null' ) return null; +		else if( value === 'true' ) return true; +		else if( value === 'false' ) return false; +		else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value ); +	} + +	return value; + +} + +/** + * Measures the distance in pixels between point a + * and point b. + * + * @param {object} a point with x/y properties + * @param {object} b point with x/y properties + * + * @return {number} + */ +export const distanceBetween = ( a, b ) => { + +	let dx = a.x - b.x, +		dy = a.y - b.y; + +	return Math.sqrt( dx*dx + dy*dy ); + +} + +/** + * Applies a CSS transform to the target element. + * + * @param {HTMLElement} element + * @param {string} transform + */ +export const transformElement = ( element, transform ) => { + +	element.style.transform = transform; + +} + +/** + * Element.matches with IE support. + * + * @param {HTMLElement} target The element to match + * @param {String} selector The CSS selector to match + * the element against + * + * @return {Boolean} + */ +export const matches = ( target, selector ) => { + +	let matchesMethod = target.matches || target.matchesSelector || target.msMatchesSelector; + +	return !!( matchesMethod && matchesMethod.call( target, selector ) ); + +} + +/** + * Find the closest parent that matches the given + * selector. + * + * @param {HTMLElement} target The child element + * @param {String} selector The CSS selector to match + * the parents against + * + * @return {HTMLElement} The matched parent or null + * if no matching parent was found + */ +export const closest = ( target, selector ) => { + +	// Native Element.closest +	if( typeof target.closest === 'function' ) { +		return target.closest( selector ); +	} + +	// Polyfill +	while( target ) { +		if( matches( target, selector ) ) { +			return target; +		} + +		// Keep searching +		target = target.parentNode; +	} + +	return null; + +} + +/** + * Handling the fullscreen functionality via the fullscreen API + * + * @see http://fullscreen.spec.whatwg.org/ + * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode + */ +export const enterFullscreen = element => { + +	element = element || document.documentElement; + +	// Check which implementation is available +	let requestMethod = element.requestFullscreen || +						element.webkitRequestFullscreen || +						element.webkitRequestFullScreen || +						element.mozRequestFullScreen || +						element.msRequestFullscreen; + +	if( requestMethod ) { +		requestMethod.apply( element ); +	} + +} + +/** + * Creates an HTML element and returns a reference to it. + * If the element already exists the existing instance will + * be returned. + * + * @param {HTMLElement} container + * @param {string} tagname + * @param {string} classname + * @param {string} innerHTML + * + * @return {HTMLElement} + */ +export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => { + +	// Find all nodes matching the description +	let nodes = container.querySelectorAll( '.' + classname ); + +	// Check all matches to find one which is a direct child of +	// the specified container +	for( let i = 0; i < nodes.length; i++ ) { +		let testNode = nodes[i]; +		if( testNode.parentNode === container ) { +			return testNode; +		} +	} + +	// If no node was found, create it now +	let node = document.createElement( tagname ); +	node.className = classname; +	node.innerHTML = innerHTML; +	container.appendChild( node ); + +	return node; + +} + +/** + * Injects the given CSS styles into the DOM. + * + * @param {string} value + */ +export const createStyleSheet = ( value ) => { + +	let tag = document.createElement( 'style' ); +	tag.type = 'text/css'; + +	if( value && value.length > 0 ) { +		if( tag.styleSheet ) { +			tag.styleSheet.cssText = value; +		} +		else { +			tag.appendChild( document.createTextNode( value ) ); +		} +	} + +	document.head.appendChild( tag ); + +	return tag; + +} + +/** + * Returns a key:value hash of all query params. + */ +export const getQueryHash = () => { + +	let query = {}; + +	location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, a => { +		query[ a.split( '=' ).shift() ] = a.split( '=' ).pop(); +	} ); + +	// Basic deserialization +	for( let i in query ) { +		let value = query[ i ]; + +		query[ i ] = deserialize( unescape( value ) ); +	} + +	// Do not accept new dependencies via query config to avoid +	// the potential of malicious script injection +	if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies']; + +	return query; + +} + +/** + * Returns the remaining height within the parent of the + * target element. + * + * remaining height = [ configured parent height ] - [ current parent height ] + * + * @param {HTMLElement} element + * @param {number} [height] + */ +export const getRemainingHeight = ( element, height = 0 ) => { + +	if( element ) { +		let newHeight, oldHeight = element.style.height; + +		// Change the .stretch element height to 0 in order find the height of all +		// the other elements +		element.style.height = '0px'; + +		// In Overview mode, the parent (.slide) height is set of 700px. +		// Restore it temporarily to its natural height. +		element.parentNode.style.height = 'auto'; + +		newHeight = height - element.parentNode.offsetHeight; + +		// Restore the old height, just in case +		element.style.height = oldHeight + 'px'; + +		// Clear the parent (.slide) height. .removeProperty works in IE9+ +		element.parentNode.style.removeProperty('height'); + +		return newHeight; +	} + +	return height; + +} + +const fileExtensionToMimeMap = { +	'mp4': 'video/mp4', +	'm4a': 'video/mp4', +	'ogv': 'video/ogg', +	'mpeg': 'video/mpeg', +	'webm': 'video/webm' +} + +/** + * Guess the MIME type for common file formats. + */ +export const getMimeTypeFromFile = ( filename='' ) => { +	return fileExtensionToMimeMap[filename.split('.').pop()] +} + +/** + * Encodes a string for RFC3986-compliant URL format. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encoding_for_rfc3986 + * + * @param {string} url + */ +export const encodeRFC3986URI = ( url='' ) => { +	return encodeURI(url) +	  .replace(/%5B/g, "[") +	  .replace(/%5D/g, "]") +	  .replace( +		/[!'()*]/g, +		(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}` +	  ); +}
\ No newline at end of file | 
