util.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /**
  2. * Extend object a with the properties of object b.
  3. * If there's a conflict, object b takes precedence.
  4. *
  5. * @param {object} a
  6. * @param {object} b
  7. */
  8. export const extend = ( a, b ) => {
  9. for( let i in b ) {
  10. a[ i ] = b[ i ];
  11. }
  12. return a;
  13. }
  14. /**
  15. * querySelectorAll but returns an Array.
  16. */
  17. export const queryAll = ( el, selector ) => {
  18. return Array.from( el.querySelectorAll( selector ) );
  19. }
  20. /**
  21. * classList.toggle() with cross browser support
  22. */
  23. export const toggleClass = ( el, className, value ) => {
  24. if( value ) {
  25. el.classList.add( className );
  26. }
  27. else {
  28. el.classList.remove( className );
  29. }
  30. }
  31. /**
  32. * Utility for deserializing a value.
  33. *
  34. * @param {*} value
  35. * @return {*}
  36. */
  37. export const deserialize = ( value ) => {
  38. if( typeof value === 'string' ) {
  39. if( value === 'null' ) return null;
  40. else if( value === 'true' ) return true;
  41. else if( value === 'false' ) return false;
  42. else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
  43. }
  44. return value;
  45. }
  46. /**
  47. * Measures the distance in pixels between point a
  48. * and point b.
  49. *
  50. * @param {object} a point with x/y properties
  51. * @param {object} b point with x/y properties
  52. *
  53. * @return {number}
  54. */
  55. export const distanceBetween = ( a, b ) => {
  56. let dx = a.x - b.x,
  57. dy = a.y - b.y;
  58. return Math.sqrt( dx*dx + dy*dy );
  59. }
  60. /**
  61. * Applies a CSS transform to the target element.
  62. *
  63. * @param {HTMLElement} element
  64. * @param {string} transform
  65. */
  66. export const transformElement = ( element, transform ) => {
  67. element.style.transform = transform;
  68. }
  69. /**
  70. * Element.matches with IE support.
  71. *
  72. * @param {HTMLElement} target The element to match
  73. * @param {String} selector The CSS selector to match
  74. * the element against
  75. *
  76. * @return {Boolean}
  77. */
  78. export const matches = ( target, selector ) => {
  79. let matchesMethod = target.matches || target.matchesSelector || target.msMatchesSelector;
  80. return !!( matchesMethod && matchesMethod.call( target, selector ) );
  81. }
  82. /**
  83. * Find the closest parent that matches the given
  84. * selector.
  85. *
  86. * @param {HTMLElement} target The child element
  87. * @param {String} selector The CSS selector to match
  88. * the parents against
  89. *
  90. * @return {HTMLElement} The matched parent or null
  91. * if no matching parent was found
  92. */
  93. export const closest = ( target, selector ) => {
  94. // Native Element.closest
  95. if( typeof target.closest === 'function' ) {
  96. return target.closest( selector );
  97. }
  98. // Polyfill
  99. while( target ) {
  100. if( matches( target, selector ) ) {
  101. return target;
  102. }
  103. // Keep searching
  104. target = target.parentNode;
  105. }
  106. return null;
  107. }
  108. /**
  109. * Handling the fullscreen functionality via the fullscreen API
  110. *
  111. * @see http://fullscreen.spec.whatwg.org/
  112. * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
  113. */
  114. export const enterFullscreen = element => {
  115. element = element || document.documentElement;
  116. // Check which implementation is available
  117. let requestMethod = element.requestFullscreen ||
  118. element.webkitRequestFullscreen ||
  119. element.webkitRequestFullScreen ||
  120. element.mozRequestFullScreen ||
  121. element.msRequestFullscreen;
  122. if( requestMethod ) {
  123. requestMethod.apply( element );
  124. }
  125. }
  126. /**
  127. * Creates an HTML element and returns a reference to it.
  128. * If the element already exists the existing instance will
  129. * be returned.
  130. *
  131. * @param {HTMLElement} container
  132. * @param {string} tagname
  133. * @param {string} classname
  134. * @param {string} innerHTML
  135. *
  136. * @return {HTMLElement}
  137. */
  138. export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => {
  139. // Find all nodes matching the description
  140. let nodes = container.querySelectorAll( '.' + classname );
  141. // Check all matches to find one which is a direct child of
  142. // the specified container
  143. for( let i = 0; i < nodes.length; i++ ) {
  144. let testNode = nodes[i];
  145. if( testNode.parentNode === container ) {
  146. return testNode;
  147. }
  148. }
  149. // If no node was found, create it now
  150. let node = document.createElement( tagname );
  151. node.className = classname;
  152. node.innerHTML = innerHTML;
  153. container.appendChild( node );
  154. return node;
  155. }
  156. /**
  157. * Injects the given CSS styles into the DOM.
  158. *
  159. * @param {string} value
  160. */
  161. export const createStyleSheet = ( value ) => {
  162. let tag = document.createElement( 'style' );
  163. tag.type = 'text/css';
  164. if( value && value.length > 0 ) {
  165. if( tag.styleSheet ) {
  166. tag.styleSheet.cssText = value;
  167. }
  168. else {
  169. tag.appendChild( document.createTextNode( value ) );
  170. }
  171. }
  172. document.head.appendChild( tag );
  173. return tag;
  174. }
  175. /**
  176. * Returns a key:value hash of all query params.
  177. */
  178. export const getQueryHash = () => {
  179. let query = {};
  180. location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, a => {
  181. query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
  182. } );
  183. // Basic deserialization
  184. for( let i in query ) {
  185. let value = query[ i ];
  186. query[ i ] = deserialize( unescape( value ) );
  187. }
  188. // Do not accept new dependencies via query config to avoid
  189. // the potential of malicious script injection
  190. if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
  191. return query;
  192. }
  193. /**
  194. * Returns the remaining height within the parent of the
  195. * target element.
  196. *
  197. * remaining height = [ configured parent height ] - [ current parent height ]
  198. *
  199. * @param {HTMLElement} element
  200. * @param {number} [height]
  201. */
  202. export const getRemainingHeight = ( element, height = 0 ) => {
  203. if( element ) {
  204. let newHeight, oldHeight = element.style.height;
  205. // Change the .stretch element height to 0 in order find the height of all
  206. // the other elements
  207. element.style.height = '0px';
  208. // In Overview mode, the parent (.slide) height is set of 700px.
  209. // Restore it temporarily to its natural height.
  210. element.parentNode.style.height = 'auto';
  211. newHeight = height - element.parentNode.offsetHeight;
  212. // Restore the old height, just in case
  213. element.style.height = oldHeight + 'px';
  214. // Clear the parent (.slide) height. .removeProperty works in IE9+
  215. element.parentNode.style.removeProperty('height');
  216. return newHeight;
  217. }
  218. return height;
  219. }
  220. const fileExtensionToMimeMap = {
  221. 'mp4': 'video/mp4',
  222. 'm4a': 'video/mp4',
  223. 'ogv': 'video/ogg',
  224. 'mpeg': 'video/mpeg',
  225. 'webm': 'video/webm'
  226. }
  227. /**
  228. * Guess the MIME type for common file formats.
  229. */
  230. export const getMimeTypeFromFile = ( filename='' ) => {
  231. return fileExtensionToMimeMap[filename.split('.').pop()]
  232. }