Selaa lähdekoodia

migrated utils to ts, exclude filendings in imports

Hakim El Hattab 2 viikkoa sitten
vanhempi
commit
a4130839ee

+ 1 - 1
css/theme/template/settings.scss

@@ -48,7 +48,7 @@ $selection-color: #fff !default;
 $overlay-element-bg-color: 240, 240, 240 !default;
 $overlay-element-fg-color: 0, 0, 0 !default;
 
-// Expose all variables to the DOM
+// Expose all SCSS variables as CSS custom properties
 :root {
 	// Background of the presentation
 	--r-background: #{$background};

+ 730 - 730
js/config.ts

@@ -1,743 +1,743 @@
 interface Config {
-  /**
-   * The "normal" size of the presentation, aspect ratio will be preserved
-   * when the presentation is scaled to fit different resolutions
-   *
-   * @defaultValue 960
-   */
-  width?: number | string;
-
-  /**
-   * The "normal" size of the presentation, aspect ratio will be preserved
-   * when the presentation is scaled to fit different resolutions
-   *
-   * @defaultValue 700
-   */
-  height?: number | string;
-
-  /**
-   * Factor of the display size that should remain empty around the content
-   *
-   * @defaultValue 0.04
-   */
-  margin?: number;
-
-  /**
-   * Bounds for smallest/largest possible scale to apply to content
-   *
-   * @defaultValue 0.2
-   */
-  minScale?: number;
-
-  /**
-   *
-   * @defaultValue 2.0
-   */
-  maxScale?: number;
-
-  /**
-   * Display presentation control arrows
+	/**
+	 * The "normal" size of the presentation, aspect ratio will be preserved
+	 * when the presentation is scaled to fit different resolutions
+	 *
+	 * @defaultValue 960
+	 */
+	width?: number | string;
+
+	/**
+	 * The "normal" size of the presentation, aspect ratio will be preserved
+	 * when the presentation is scaled to fit different resolutions
+	 *
+	 * @defaultValue 700
+	 */
+	height?: number | string;
+
+	/**
+	 * Factor of the display size that should remain empty around the content
+	 *
+	 * @defaultValue 0.04
+	 */
+	margin?: number;
+
+	/**
+	 * Bounds for smallest/largest possible scale to apply to content
+	 *
+	 * @defaultValue 0.2
+	 */
+	minScale?: number;
+
+	/**
+	 *
+	 * @defaultValue 2.0
+	 */
+	maxScale?: number;
+
+	/**
+	 * Display presentation control arrows
 	 * - true: Display controls in all views
 	 * - false: Hide controls in all views
 	 * - 'speaker-only': Display controls only in the speaker view
-   *
-   * @defaultValue true
-   */
-  controls?: boolean | 'speaker-only';
-
-  /**
-   * Help the user learn the controls by providing hints, for example by
-   * bouncing the down arrow when they first encounter a vertical slide
-   *
-   * @defaultValue true
-   */
-  controlsTutorial?: boolean;
-
-  /**
-   * Determines where controls appear, "edges" or "bottom-right"
-   *
-   * @defaultValue 'bottom-right'
-   */
-  controlsLayout?: 'edges' | 'bottom-right';
-
-  /**
-   * Visibility rule for backwards navigation arrows; "faded", "hidden"
-   * or "visible"
-   *
-   * @defaultValue 'faded'
-   */
-  controlsBackArrows?: 'faded' | 'hidden' | 'visible';
-
-  /**
-   * Display a presentation progress bar
-   *
-   * @defaultValue true
-   */
-  progress?: boolean;
-
-  /**
-   * Display the page number of the current slide
-   * - true:    Show slide number
-   * - false:   Hide slide number
-   *
-   * Can optionally be set as a string that specifies the number formatting:
-   * - "h.v":	  Horizontal . vertical slide number (default)
-   * - "h/v":	  Horizontal / vertical slide number
-   * - "c":	  Flattened slide number
-   * - "c/t":	  Flattened slide number / total slides
-   *
-   * Alternatively, you can provide a function that returns the slide
-   * number for the current slide. The function should take in a slide
-   * object and return an array with one string [slideNumber] or
-   * three strings [n1,delimiter,n2]. See #formatSlideNumber().
-   *
-   * @defaultValue false
-   */
-  slideNumber?:
-    | boolean
-    | 'h.v'
-    | 'h/v'
-    | 'c'
-    | 'c/t'
-    | ((slide: any) => string | [string, string, string]);
-
-  /**
-   * Can be used to limit the contexts in which the slide number appears
-   * - "all":      Always show the slide number
-   * - "print":    Only when printing to PDF
-   * - "speaker":  Only in the speaker view
-   *
-   * @defaultValue 'all'
-   */
-  showSlideNumber?: 'all' | 'print' | 'speaker';
-
-  /**
-   * Use 1 based indexing for # links to match slide number (default is zero
-   * based)
-   *
-   * @defaultValue false
-   */
-  hashOneBasedIndex?: boolean;
-
-  /**
-   * Add the current slide number to the URL hash so that reloading the
-   * page/copying the URL will return you to the same slide
-   *
-   * @defaultValue false
-   */
-  hash?: boolean;
-
-  /**
-   * Flags if we should monitor the hash and change slides accordingly
-   *
-   * @defaultValue true
-   */
-  respondToHashChanges?: boolean;
-
-  /**
-   * Enable support for jump-to-slide navigation shortcuts
-   *
-   * @defaultValue true
-   */
-  jumpToSlide?: boolean;
-
-  /**
-   * Push each slide change to the browser history. Implies `hash: true`
-   *
-   * @defaultValue false
-   */
-  history?: boolean;
-
-  /**
-   * Enable keyboard shortcuts for navigation
-   *
-   * @defaultValue true
-   */
-  keyboard?: boolean;
-
-  /**
-   * Optional function that blocks keyboard events when returning false
-   *
-   * If you set this to 'focused', we will only capture keyboard events
-   * for embedded decks when they are in focus
-   *
-   * @defaultValue null
-   */
-  keyboardCondition?: null | 'focused' | ((event: KeyboardEvent) => boolean);
-
-  /**
-   * Disables the default reveal.js slide layout (scaling and centering)
-   * so that you can use custom CSS layout
-   *
-   * @defaultValue false
-   */
-  disableLayout?: boolean;
-
-  /**
-   * Enable the slide overview mode
-   *
-   * @defaultValue true
-   */
-  overview?: boolean;
-
-  /**
-   * Vertical centering of slides
-   *
-   * @defaultValue true
-   */
-  center?: boolean;
-
-  /**
-   * Enables touch navigation on devices with touch input
-   *
-   * @defaultValue true
-   */
-  touch?: boolean;
-
-  /**
-   * Loop the presentation
-   *
-   * @defaultValue false
-   */
-  loop?: boolean;
-
-  /**
-   * Change the presentation direction to be RTL
-   *
-   * @defaultValue false
-   */
-  rtl?: boolean;
-
-  /**
-   * Changes the behavior of our navigation directions.
-   *
-   * "default"
-   * Left/right arrow keys step between horizontal slides, up/down
-   * arrow keys step between vertical slides. Space key steps through
-   * all slides (both horizontal and vertical).
-   *
-   * "linear"
-   * Removes the up/down arrows. Left/right arrows step through all
-   * slides (both horizontal and vertical).
-   *
-   * "grid"
-   * When this is enabled, stepping left/right from a vertical stack
-   * to an adjacent vertical stack will land you at the same vertical
-   * index.
-   *
-   * Consider a deck with six slides ordered in two vertical stacks:
-   * 1.1    2.1
-   * 1.2    2.2
-   * 1.3    2.3
-   *
-   * If you're on slide 1.3 and navigate right, you will normally move
-   * from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
-   * from 1.3 -> 2.3.
-   *
-   * @defaultValue 'default'
-   */
-  navigationMode?: 'default' | 'linear' | 'grid';
-
-  /**
-   * Randomizes the order of slides each time the presentation loads
-   *
-   * @defaultValue false
-   */
-  shuffle?: boolean;
-
-  /**
-   * Turns fragments on and off globally
-   *
-   * @defaultValue true
-   */
-  fragments?: boolean;
-
-  /**
-   * Flags whether to include the current fragment in the URL,
-   * so that reloading brings you to the same fragment position
-   *
-   * @defaultValue true
-   */
-  fragmentInURL?: boolean;
-
-  /**
-   * Flags if the presentation is running in an embedded mode,
-   * i.e. contained within a limited portion of the screen
-   *
-   * @defaultValue false
-   */
-  embedded?: boolean;
-
-  /**
-   * Flags if we should show a help overlay when the question-mark
-   * key is pressed
-   *
-   * @defaultValue true
-   */
-  help?: boolean;
-
-  /**
-   * Flags if it should be possible to pause the presentation (blackout)
-   *
-   * @defaultValue true
-   */
-  pause?: boolean;
-
-  /**
-   * Flags if speaker notes should be visible to all viewers
-   *
-   * @defaultValue false
-   */
-  showNotes?: boolean;
-
-  /**
-   * Flags if slides with data-visibility="hidden" should be kept visible
-   *
-   * @defaultValue false
-   */
-  showHiddenSlides?: boolean;
-
-  /**
-   * Global override for autoplaying embedded media (video/audio/iframe)
-   * - null:   Media will only autoplay if data-autoplay is present
-   * - true:   All media will autoplay, regardless of individual setting
-   * - false:  No media will autoplay, regardless of individual setting
-   *
-   * @defaultValue null
-   */
-  autoPlayMedia?: null | boolean;
-
-  /**
-   * Global override for preloading lazy-loaded iframes
-   * - null:   Iframes with data-src AND data-preload will be loaded when within
-   *           the viewDistance, iframes with only data-src will be loaded when visible
-   * - true:   All iframes with data-src will be loaded when within the viewDistance
-   * - false:  All iframes with data-src will be loaded only when visible
-   *
-   * @defaultValue null
-   */
-  preloadIframes?: null | boolean;
-
-  /**
-   * Can be used to globally disable auto-animation
-   *
-   * @defaultValue true
-   */
-  autoAnimate?: boolean;
-
-  /**
-   * Optionally provide a custom element matcher that will be
-   * used to dictate which elements we can animate between.
-   *
-   * @defaultValue null
-   */
-  autoAnimateMatcher?: null | Function;
-
-  /**
-   * Default settings for our auto-animate transitions, can be
-   * overridden per-slide or per-element via data arguments
-   *
-   * @defaultValue 'ease'
-   */
-  autoAnimateEasing?: 'ease' | string;
-
-  /**
-   * Number of seconds to animate each element.
-   *
-   * @defaultValue 1.0
-   */
-  autoAnimateDuration?: number;
-
-  /**
-   * Should unmatched elements be faded in?
-   *
-   * @defaultValue true
-   */
-  autoAnimateUnmatched?: boolean;
-
-  /**
-   * CSS properties that can be auto-animated. Position & scale
-   * is matched separately so there's no need to include styles
-   * like top/right/bottom/left, width/height or margin.
-   *
-   * @defaultValue ['opacity', 'color', 'background-color', 'padding', 'font-size', 'line-height', 'letter-spacing', 'border-width', 'border-color', 'border-radius', 'outline', 'outline-offset']
-   */
-  autoAnimateStyles?: string[];
-
-  /**
-   * Controls automatic progression to the next slide
-   * - 0:      Auto-sliding only happens if the data-autoslide HTML attribute
-   *           is present on the current slide or fragment
-   * - 1+:     All slides will progress automatically at the given interval
-   * - false:  No auto-sliding, even if data-autoslide is present
-   *
-   * @defaultValue 0
-   */
-  autoSlide?: number | false;
-
-  /**
-   * Stop auto-sliding after user input
-   *
-   * @defaultValue true
-   */
-  autoSlideStoppable?: boolean;
-
-  /**
-   * Use this method for navigation when auto-sliding (defaults to navigateNext)
-   *
-   * @defaultValue null
-   */
-  autoSlideMethod?: null | Function;
-
-  /**
-   * Specify the average time in seconds that you think you will spend
-   * presenting each slide. This is used to show a pacing timer in the
-   * speaker view
-   *
-   * @defaultValue null
-   */
-  defaultTiming?: null;
-
-  /**
-   * Enable slide navigation via mouse wheel
-   *
-   * @defaultValue false
-   */
-  mouseWheel?: boolean;
-
-  /**
-   * Opens links in an iframe preview overlay
-   * Add `data-preview-link` and `data-preview-link="false"` to customize each link
-   * individually
-   *
-   * @defaultValue false
-   */
-  previewLinks?: boolean;
-
-  /**
-   * Exposes the reveal.js API through window.postMessage
-   *
-   * @defaultValue true
-   */
-  postMessage?: boolean;
-
-  /**
-   * Dispatches all reveal.js events to the parent window through postMessage
-   *
-   * @defaultValue false
-   */
-  postMessageEvents?: boolean;
-
-  /**
-   * Focuses body when page changes visibility to ensure keyboard shortcuts work
-   *
-   * @defaultValue true
-   */
-  focusBodyOnPageVisibilityChange?: boolean;
-
-  /**
-   * Transition style
-   *
-   * @defaultValue 'slide'
-   */
-  transition?: 'none' | 'fade' | 'slide' | 'convex' | 'concave' | 'zoom';
-
-  /**
-   * Transition speed
-   *
-   * @defaultValue 'default'
-   */
-  transitionSpeed?: 'default' | 'fast' | 'slow';
-
-  /**
-   * Transition style for full page slide backgrounds
-   *
-   * @defaultValue 'fade'
-   */
-  backgroundTransition?: 'fade' | 'none' | 'slide' | 'convex' | 'concave' | 'zoom';
-
-  /**
-   * Parallax background image
-   *
-   * @defaultValue ''
-   */
-  parallaxBackgroundImage?: null | string; // CSS syntax, e.g. "a.jpg"
-
-  /**
-   * Parallax background size
-   *
-   * @defaultValue ''
-   */
-  parallaxBackgroundSize?: null | string; // CSS syntax, e.g. "3000px 2000px"
-
-  /**
-   * Parallax background repeat
-   *
-   * @defaultValue ''
-   */
-  parallaxBackgroundRepeat?: null | string; // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
-
-  /**
-   * Parallax background position
-   *
-   * @defaultValue ''
-   */
-  parallaxBackgroundPosition?: null | string; // CSS syntax, e.g. "top left"
-
-  /**
-   * Amount of pixels to move the parallax background per slide step
-   *
-   * @defaultValue null
-   */
-  parallaxBackgroundHorizontal?: null | number;
-  /**
-   *
-   * @defaultValue null
-   */
-  parallaxBackgroundVertical?: null | number;
-
-  /**
-   * Can be used to initialize reveal.js in one of the following views:
-   * - print:   Render the presentation so that it can be printed to PDF
-   * - scroll:  Show the presentation as a tall scrollable page with scroll
-   *            triggered animations
-   *
-   * @defaultValue null
-   */
-  view?: null | 'print' | 'scroll';
-
-  /**
-   * Adjusts the height of each slide in the scroll view.
-   * - full:       Each slide is as tall as the viewport
-   * - compact:    Slides are as small as possible, allowing multiple slides
-   *               to be visible in parallel on tall devices
-   *
-   * @defaultValue 'full'
-   */
-  scrollLayout?: 'full' | 'compact';
-
-  /**
-   * Control how scroll snapping works in the scroll view.
-   * - false:   	No snapping, scrolling is continuous
-   * - proximity:  Snap when close to a slide
-   * - mandatory:  Always snap to the closest slide
-   *
-   * Only applies to presentations in scroll view.
-   *
-   * @defaultValue 'mandatory'
-   */
-  scrollSnap?: false | 'proximity' | 'mandatory';
-
-  /**
-   * Enables and configures the scroll view progress bar.
-   * - 'auto':    Show the scrollbar while scrolling, hide while idle
-   * - true:      Always show the scrollbar
-   * - false:     Never show the scrollbar
-   *
-   * @defaultValue 'auto'
-   */
-  scrollProgress?: 'auto' | boolean;
-
-  /**
-   * Automatically activate the scroll view when we the viewport falls
-   * below the given width.
-   *
-   * @defaultValue 435
-   */
-  scrollActivationWidth?: number;
-
-  /**
-   * The maximum number of pages a single slide can expand onto when printing
-   * to PDF, unlimited by default
-   *
-   * @defaultValue Number.POSITIVE_INFINITY
-   */
-  pdfMaxPagesPerSlide?: number;
-
-  /**
-   * Prints each fragment on a separate slide
-   *
-   * @defaultValue true
-   */
-  pdfSeparateFragments?: boolean;
-
-  /**
-   * Offset used to reduce the height of content within exported PDF pages.
-   * This exists to account for environment differences based on how you
-   * print to PDF. CLI printing options, like phantomjs and wkpdf, can end
-   * on precisely the total height of the document whereas in-browser
-   * printing has to end one pixel before.
-   *
-   * @defaultValue -1
-   */
-  pdfPageHeightOffset?: number;
-
-  /**
-   * Number of slides away from the current that are visible
-   *
-   * @defaultValue 3
-   */
-  viewDistance?: number;
-
-  /**
-   * Number of slides away from the current that are visible on mobile
-   * devices. It is advisable to set this to a lower number than
-   * viewDistance in order to save resources.
-   *
-   * @defaultValue 2
-   */
-  mobileViewDistance?: number;
-
-  /**
-   * The display mode that will be used to show slides
-   *
-   * @defaultValue 'block'
-   */
-  display?: string;
-
-  /**
-   * Hide cursor if inactive
-   *
-   * @defaultValue true
-   */
-  hideInactiveCursor?: boolean;
-
-  /**
-   * Time before the cursor is hidden (in ms)
-   *
-   * @defaultValue 5000
-   */
-  hideCursorTime?: number;
-
-  /**
-   * Should we automatically sort and set indices for fragments
-   * at each sync? (See Reveal.sync)
-   *
-   * @defaultValue true
-   */
-  sortFragmentsOnSync?: boolean;
-
-  /**
-   * Script dependencies to load
-   *
-   * @defaultValue []
-   */
-  dependencies?: any[];
-
-  /**
-   * Plugin objects to register and use for this presentation
-   *
-   * @defaultValue []
-   */
-  plugins?: any[];
+	 *
+	 * @defaultValue true
+	 */
+	controls?: boolean | 'speaker-only';
+
+	/**
+	 * Help the user learn the controls by providing hints, for example by
+	 * bouncing the down arrow when they first encounter a vertical slide
+	 *
+	 * @defaultValue true
+	 */
+	controlsTutorial?: boolean;
+
+	/**
+	 * Determines where controls appear, "edges" or "bottom-right"
+	 *
+	 * @defaultValue 'bottom-right'
+	 */
+	controlsLayout?: 'edges' | 'bottom-right';
+
+	/**
+	 * Visibility rule for backwards navigation arrows; "faded", "hidden"
+	 * or "visible"
+	 *
+	 * @defaultValue 'faded'
+	 */
+	controlsBackArrows?: 'faded' | 'hidden' | 'visible';
+
+	/**
+	 * Display a presentation progress bar
+	 *
+	 * @defaultValue true
+	 */
+	progress?: boolean;
+
+	/**
+	 * Display the page number of the current slide
+	 * - true:    Show slide number
+	 * - false:   Hide slide number
+	 *
+	 * Can optionally be set as a string that specifies the number formatting:
+	 * - "h.v":	  Horizontal . vertical slide number (default)
+	 * - "h/v":	  Horizontal / vertical slide number
+	 * - "c":	  Flattened slide number
+	 * - "c/t":	  Flattened slide number / total slides
+	 *
+	 * Alternatively, you can provide a function that returns the slide
+	 * number for the current slide. The function should take in a slide
+	 * object and return an array with one string [slideNumber] or
+	 * three strings [n1,delimiter,n2]. See #formatSlideNumber().
+	 *
+	 * @defaultValue false
+	 */
+	slideNumber?:
+		| boolean
+		| 'h.v'
+		| 'h/v'
+		| 'c'
+		| 'c/t'
+		| ((slide: any) => string | [string, string, string]);
+
+	/**
+	 * Can be used to limit the contexts in which the slide number appears
+	 * - "all":      Always show the slide number
+	 * - "print":    Only when printing to PDF
+	 * - "speaker":  Only in the speaker view
+	 *
+	 * @defaultValue 'all'
+	 */
+	showSlideNumber?: 'all' | 'print' | 'speaker';
+
+	/**
+	 * Use 1 based indexing for # links to match slide number (default is zero
+	 * based)
+	 *
+	 * @defaultValue false
+	 */
+	hashOneBasedIndex?: boolean;
+
+	/**
+	 * Add the current slide number to the URL hash so that reloading the
+	 * page/copying the URL will return you to the same slide
+	 *
+	 * @defaultValue false
+	 */
+	hash?: boolean;
+
+	/**
+	 * Flags if we should monitor the hash and change slides accordingly
+	 *
+	 * @defaultValue true
+	 */
+	respondToHashChanges?: boolean;
+
+	/**
+	 * Enable support for jump-to-slide navigation shortcuts
+	 *
+	 * @defaultValue true
+	 */
+	jumpToSlide?: boolean;
+
+	/**
+	 * Push each slide change to the browser history. Implies `hash: true`
+	 *
+	 * @defaultValue false
+	 */
+	history?: boolean;
+
+	/**
+	 * Enable keyboard shortcuts for navigation
+	 *
+	 * @defaultValue true
+	 */
+	keyboard?: boolean;
+
+	/**
+	 * Optional function that blocks keyboard events when returning false
+	 *
+	 * If you set this to 'focused', we will only capture keyboard events
+	 * for embedded decks when they are in focus
+	 *
+	 * @defaultValue null
+	 */
+	keyboardCondition?: null | 'focused' | ((event: KeyboardEvent) => boolean);
+
+	/**
+	 * Disables the default reveal.js slide layout (scaling and centering)
+	 * so that you can use custom CSS layout
+	 *
+	 * @defaultValue false
+	 */
+	disableLayout?: boolean;
+
+	/**
+	 * Enable the slide overview mode
+	 *
+	 * @defaultValue true
+	 */
+	overview?: boolean;
+
+	/**
+	 * Vertical centering of slides
+	 *
+	 * @defaultValue true
+	 */
+	center?: boolean;
+
+	/**
+	 * Enables touch navigation on devices with touch input
+	 *
+	 * @defaultValue true
+	 */
+	touch?: boolean;
+
+	/**
+	 * Loop the presentation
+	 *
+	 * @defaultValue false
+	 */
+	loop?: boolean;
+
+	/**
+	 * Change the presentation direction to be RTL
+	 *
+	 * @defaultValue false
+	 */
+	rtl?: boolean;
+
+	/**
+	 * Changes the behavior of our navigation directions.
+	 *
+	 * "default"
+	 * Left/right arrow keys step between horizontal slides, up/down
+	 * arrow keys step between vertical slides. Space key steps through
+	 * all slides (both horizontal and vertical).
+	 *
+	 * "linear"
+	 * Removes the up/down arrows. Left/right arrows step through all
+	 * slides (both horizontal and vertical).
+	 *
+	 * "grid"
+	 * When this is enabled, stepping left/right from a vertical stack
+	 * to an adjacent vertical stack will land you at the same vertical
+	 * index.
+	 *
+	 * Consider a deck with six slides ordered in two vertical stacks:
+	 * 1.1    2.1
+	 * 1.2    2.2
+	 * 1.3    2.3
+	 *
+	 * If you're on slide 1.3 and navigate right, you will normally move
+	 * from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
+	 * from 1.3 -> 2.3.
+	 *
+	 * @defaultValue 'default'
+	 */
+	navigationMode?: 'default' | 'linear' | 'grid';
+
+	/**
+	 * Randomizes the order of slides each time the presentation loads
+	 *
+	 * @defaultValue false
+	 */
+	shuffle?: boolean;
+
+	/**
+	 * Turns fragments on and off globally
+	 *
+	 * @defaultValue true
+	 */
+	fragments?: boolean;
+
+	/**
+	 * Flags whether to include the current fragment in the URL,
+	 * so that reloading brings you to the same fragment position
+	 *
+	 * @defaultValue true
+	 */
+	fragmentInURL?: boolean;
+
+	/**
+	 * Flags if the presentation is running in an embedded mode,
+	 * i.e. contained within a limited portion of the screen
+	 *
+	 * @defaultValue false
+	 */
+	embedded?: boolean;
+
+	/**
+	 * Flags if we should show a help overlay when the question-mark
+	 * key is pressed
+	 *
+	 * @defaultValue true
+	 */
+	help?: boolean;
+
+	/**
+	 * Flags if it should be possible to pause the presentation (blackout)
+	 *
+	 * @defaultValue true
+	 */
+	pause?: boolean;
+
+	/**
+	 * Flags if speaker notes should be visible to all viewers
+	 *
+	 * @defaultValue false
+	 */
+	showNotes?: boolean;
+
+	/**
+	 * Flags if slides with data-visibility="hidden" should be kept visible
+	 *
+	 * @defaultValue false
+	 */
+	showHiddenSlides?: boolean;
+
+	/**
+	 * Global override for autoplaying embedded media (video/audio/iframe)
+	 * - null:   Media will only autoplay if data-autoplay is present
+	 * - true:   All media will autoplay, regardless of individual setting
+	 * - false:  No media will autoplay, regardless of individual setting
+	 *
+	 * @defaultValue null
+	 */
+	autoPlayMedia?: null | boolean;
+
+	/**
+	 * Global override for preloading lazy-loaded iframes
+	 * - null:   Iframes with data-src AND data-preload will be loaded when within
+	 *           the viewDistance, iframes with only data-src will be loaded when visible
+	 * - true:   All iframes with data-src will be loaded when within the viewDistance
+	 * - false:  All iframes with data-src will be loaded only when visible
+	 *
+	 * @defaultValue null
+	 */
+	preloadIframes?: null | boolean;
+
+	/**
+	 * Can be used to globally disable auto-animation
+	 *
+	 * @defaultValue true
+	 */
+	autoAnimate?: boolean;
+
+	/**
+	 * Optionally provide a custom element matcher that will be
+	 * used to dictate which elements we can animate between.
+	 *
+	 * @defaultValue null
+	 */
+	autoAnimateMatcher?: null | Function;
+
+	/**
+	 * Default settings for our auto-animate transitions, can be
+	 * overridden per-slide or per-element via data arguments
+	 *
+	 * @defaultValue 'ease'
+	 */
+	autoAnimateEasing?: 'ease' | string;
+
+	/**
+	 * Number of seconds to animate each element.
+	 *
+	 * @defaultValue 1.0
+	 */
+	autoAnimateDuration?: number;
+
+	/**
+	 * Should unmatched elements be faded in?
+	 *
+	 * @defaultValue true
+	 */
+	autoAnimateUnmatched?: boolean;
+
+	/**
+	 * CSS properties that can be auto-animated. Position & scale
+	 * is matched separately so there's no need to include styles
+	 * like top/right/bottom/left, width/height or margin.
+	 *
+	 * @defaultValue ['opacity', 'color', 'background-color', 'padding', 'font-size', 'line-height', 'letter-spacing', 'border-width', 'border-color', 'border-radius', 'outline', 'outline-offset']
+	 */
+	autoAnimateStyles?: string[];
+
+	/**
+	 * Controls automatic progression to the next slide
+	 * - 0:      Auto-sliding only happens if the data-autoslide HTML attribute
+	 *           is present on the current slide or fragment
+	 * - 1+:     All slides will progress automatically at the given interval
+	 * - false:  No auto-sliding, even if data-autoslide is present
+	 *
+	 * @defaultValue 0
+	 */
+	autoSlide?: number | false;
+
+	/**
+	 * Stop auto-sliding after user input
+	 *
+	 * @defaultValue true
+	 */
+	autoSlideStoppable?: boolean;
+
+	/**
+	 * Use this method for navigation when auto-sliding (defaults to navigateNext)
+	 *
+	 * @defaultValue null
+	 */
+	autoSlideMethod?: null | Function;
+
+	/**
+	 * Specify the average time in seconds that you think you will spend
+	 * presenting each slide. This is used to show a pacing timer in the
+	 * speaker view
+	 *
+	 * @defaultValue null
+	 */
+	defaultTiming?: null;
+
+	/**
+	 * Enable slide navigation via mouse wheel
+	 *
+	 * @defaultValue false
+	 */
+	mouseWheel?: boolean;
+
+	/**
+	 * Opens links in an iframe preview overlay
+	 * Add `data-preview-link` and `data-preview-link="false"` to customize each link
+	 * individually
+	 *
+	 * @defaultValue false
+	 */
+	previewLinks?: boolean;
+
+	/**
+	 * Exposes the reveal.js API through window.postMessage
+	 *
+	 * @defaultValue true
+	 */
+	postMessage?: boolean;
+
+	/**
+	 * Dispatches all reveal.js events to the parent window through postMessage
+	 *
+	 * @defaultValue false
+	 */
+	postMessageEvents?: boolean;
+
+	/**
+	 * Focuses body when page changes visibility to ensure keyboard shortcuts work
+	 *
+	 * @defaultValue true
+	 */
+	focusBodyOnPageVisibilityChange?: boolean;
+
+	/**
+	 * Transition style
+	 *
+	 * @defaultValue 'slide'
+	 */
+	transition?: 'none' | 'fade' | 'slide' | 'convex' | 'concave' | 'zoom';
+
+	/**
+	 * Transition speed
+	 *
+	 * @defaultValue 'default'
+	 */
+	transitionSpeed?: 'default' | 'fast' | 'slow';
+
+	/**
+	 * Transition style for full page slide backgrounds
+	 *
+	 * @defaultValue 'fade'
+	 */
+	backgroundTransition?: 'fade' | 'none' | 'slide' | 'convex' | 'concave' | 'zoom';
+
+	/**
+	 * Parallax background image
+	 *
+	 * @defaultValue ''
+	 */
+	parallaxBackgroundImage?: null | string; // CSS syntax, e.g. "a.jpg"
+
+	/**
+	 * Parallax background size
+	 *
+	 * @defaultValue ''
+	 */
+	parallaxBackgroundSize?: null | string; // CSS syntax, e.g. "3000px 2000px"
+
+	/**
+	 * Parallax background repeat
+	 *
+	 * @defaultValue ''
+	 */
+	parallaxBackgroundRepeat?: null | string; // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
+
+	/**
+	 * Parallax background position
+	 *
+	 * @defaultValue ''
+	 */
+	parallaxBackgroundPosition?: null | string; // CSS syntax, e.g. "top left"
+
+	/**
+	 * Amount of pixels to move the parallax background per slide step
+	 *
+	 * @defaultValue null
+	 */
+	parallaxBackgroundHorizontal?: null | number;
+	/**
+	 *
+	 * @defaultValue null
+	 */
+	parallaxBackgroundVertical?: null | number;
+
+	/**
+	 * Can be used to initialize reveal.js in one of the following views:
+	 * - print:   Render the presentation so that it can be printed to PDF
+	 * - scroll:  Show the presentation as a tall scrollable page with scroll
+	 *            triggered animations
+	 *
+	 * @defaultValue null
+	 */
+	view?: null | 'print' | 'scroll';
+
+	/**
+	 * Adjusts the height of each slide in the scroll view.
+	 * - full:       Each slide is as tall as the viewport
+	 * - compact:    Slides are as small as possible, allowing multiple slides
+	 *               to be visible in parallel on tall devices
+	 *
+	 * @defaultValue 'full'
+	 */
+	scrollLayout?: 'full' | 'compact';
+
+	/**
+	 * Control how scroll snapping works in the scroll view.
+	 * - false:   	No snapping, scrolling is continuous
+	 * - proximity:  Snap when close to a slide
+	 * - mandatory:  Always snap to the closest slide
+	 *
+	 * Only applies to presentations in scroll view.
+	 *
+	 * @defaultValue 'mandatory'
+	 */
+	scrollSnap?: false | 'proximity' | 'mandatory';
+
+	/**
+	 * Enables and configures the scroll view progress bar.
+	 * - 'auto':    Show the scrollbar while scrolling, hide while idle
+	 * - true:      Always show the scrollbar
+	 * - false:     Never show the scrollbar
+	 *
+	 * @defaultValue 'auto'
+	 */
+	scrollProgress?: 'auto' | boolean;
+
+	/**
+	 * Automatically activate the scroll view when we the viewport falls
+	 * below the given width.
+	 *
+	 * @defaultValue 435
+	 */
+	scrollActivationWidth?: number;
+
+	/**
+	 * The maximum number of pages a single slide can expand onto when printing
+	 * to PDF, unlimited by default
+	 *
+	 * @defaultValue Number.POSITIVE_INFINITY
+	 */
+	pdfMaxPagesPerSlide?: number;
+
+	/**
+	 * Prints each fragment on a separate slide
+	 *
+	 * @defaultValue true
+	 */
+	pdfSeparateFragments?: boolean;
+
+	/**
+	 * Offset used to reduce the height of content within exported PDF pages.
+	 * This exists to account for environment differences based on how you
+	 * print to PDF. CLI printing options, like phantomjs and wkpdf, can end
+	 * on precisely the total height of the document whereas in-browser
+	 * printing has to end one pixel before.
+	 *
+	 * @defaultValue -1
+	 */
+	pdfPageHeightOffset?: number;
+
+	/**
+	 * Number of slides away from the current that are visible
+	 *
+	 * @defaultValue 3
+	 */
+	viewDistance?: number;
+
+	/**
+	 * Number of slides away from the current that are visible on mobile
+	 * devices. It is advisable to set this to a lower number than
+	 * viewDistance in order to save resources.
+	 *
+	 * @defaultValue 2
+	 */
+	mobileViewDistance?: number;
+
+	/**
+	 * The display mode that will be used to show slides
+	 *
+	 * @defaultValue 'block'
+	 */
+	display?: string;
+
+	/**
+	 * Hide cursor if inactive
+	 *
+	 * @defaultValue true
+	 */
+	hideInactiveCursor?: boolean;
+
+	/**
+	 * Time before the cursor is hidden (in ms)
+	 *
+	 * @defaultValue 5000
+	 */
+	hideCursorTime?: number;
+
+	/**
+	 * Should we automatically sort and set indices for fragments
+	 * at each sync? (See Reveal.sync)
+	 *
+	 * @defaultValue true
+	 */
+	sortFragmentsOnSync?: boolean;
+
+	/**
+	 * Script dependencies to load
+	 *
+	 * @defaultValue []
+	 */
+	dependencies?: any[];
+
+	/**
+	 * Plugin objects to register and use for this presentation
+	 *
+	 * @defaultValue []
+	 */
+	plugins?: any[];
 }
 
 /**
  * The default reveal.js config object.
  */
 const defaultConfig: Config = {
-  width: 960,
-  height: 700,
-  margin: 0.04,
-  minScale: 0.2,
-  maxScale: 2.0,
-
-  controls: true,
-  controlsTutorial: true,
-  controlsLayout: 'bottom-right',
-  controlsBackArrows: 'faded',
-  progress: true,
-
-  slideNumber: false,
-  showSlideNumber: 'all',
-  hashOneBasedIndex: false,
-  hash: false,
-  respondToHashChanges: true,
-  jumpToSlide: true,
-  history: false,
-  keyboard: true,
-  keyboardCondition: null,
-  disableLayout: false,
-  overview: true,
-  center: true,
-  touch: true,
-  loop: false,
-  rtl: false,
-  navigationMode: 'default',
-  shuffle: false,
-  fragments: true,
-  fragmentInURL: true,
-  embedded: false,
-  help: true,
-  pause: true,
-  showNotes: false,
-  showHiddenSlides: false,
-  autoPlayMedia: null,
-  preloadIframes: null,
-  mouseWheel: false,
-  previewLinks: false,
-  viewDistance: 3,
-  mobileViewDistance: 2,
-  display: 'block',
-  hideInactiveCursor: true,
-  hideCursorTime: 5000,
-  sortFragmentsOnSync: true,
-
-  autoAnimate: true,
-  autoAnimateMatcher: null,
-  autoAnimateEasing: 'ease',
-  autoAnimateDuration: 1.0,
-  autoAnimateUnmatched: true,
-
-  autoAnimateStyles: [
-    'opacity',
-    'color',
-    'background-color',
-    'padding',
-    'font-size',
-    'line-height',
-    'letter-spacing',
-    'border-width',
-    'border-color',
-    'border-radius',
-    'outline',
-    'outline-offset',
-  ],
-
-  autoSlide: 0,
-  autoSlideStoppable: true,
-  autoSlideMethod: null,
-  defaultTiming: null,
-
-  postMessage: true,
-  postMessageEvents: false,
-
-  focusBodyOnPageVisibilityChange: true,
-
-  transition: 'slide',
-  transitionSpeed: 'default',
-  backgroundTransition: 'fade',
-
-  parallaxBackgroundImage: '',
-  parallaxBackgroundSize: '',
-  parallaxBackgroundRepeat: '',
-  parallaxBackgroundPosition: '',
-  parallaxBackgroundHorizontal: null,
-  parallaxBackgroundVertical: null,
-
-  view: null,
-
-  scrollLayout: 'full',
-  scrollSnap: 'mandatory',
-  scrollProgress: 'auto',
-  scrollActivationWidth: 435,
-
-  pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
-  pdfSeparateFragments: true,
-  pdfPageHeightOffset: -1,
-
-  dependencies: [],
-  plugins: [],
+	width: 960,
+	height: 700,
+	margin: 0.04,
+	minScale: 0.2,
+	maxScale: 2.0,
+
+	controls: true,
+	controlsTutorial: true,
+	controlsLayout: 'bottom-right',
+	controlsBackArrows: 'faded',
+	progress: true,
+
+	slideNumber: false,
+	showSlideNumber: 'all',
+	hashOneBasedIndex: false,
+	hash: false,
+	respondToHashChanges: true,
+	jumpToSlide: true,
+	history: false,
+	keyboard: true,
+	keyboardCondition: null,
+	disableLayout: false,
+	overview: true,
+	center: true,
+	touch: true,
+	loop: false,
+	rtl: false,
+	navigationMode: 'default',
+	shuffle: false,
+	fragments: true,
+	fragmentInURL: true,
+	embedded: false,
+	help: true,
+	pause: true,
+	showNotes: false,
+	showHiddenSlides: false,
+	autoPlayMedia: null,
+	preloadIframes: null,
+	mouseWheel: false,
+	previewLinks: false,
+	viewDistance: 3,
+	mobileViewDistance: 2,
+	display: 'block',
+	hideInactiveCursor: true,
+	hideCursorTime: 5000,
+	sortFragmentsOnSync: true,
+
+	autoAnimate: true,
+	autoAnimateMatcher: null,
+	autoAnimateEasing: 'ease',
+	autoAnimateDuration: 1.0,
+	autoAnimateUnmatched: true,
+
+	autoAnimateStyles: [
+		'opacity',
+		'color',
+		'background-color',
+		'padding',
+		'font-size',
+		'line-height',
+		'letter-spacing',
+		'border-width',
+		'border-color',
+		'border-radius',
+		'outline',
+		'outline-offset',
+	],
+
+	autoSlide: 0,
+	autoSlideStoppable: true,
+	autoSlideMethod: null,
+	defaultTiming: null,
+
+	postMessage: true,
+	postMessageEvents: false,
+
+	focusBodyOnPageVisibilityChange: true,
+
+	transition: 'slide',
+	transitionSpeed: 'default',
+	backgroundTransition: 'fade',
+
+	parallaxBackgroundImage: '',
+	parallaxBackgroundSize: '',
+	parallaxBackgroundRepeat: '',
+	parallaxBackgroundPosition: '',
+	parallaxBackgroundHorizontal: null,
+	parallaxBackgroundVertical: null,
+
+	view: null,
+
+	scrollLayout: 'full',
+	scrollSnap: 'mandatory',
+	scrollProgress: 'auto',
+	scrollActivationWidth: 435,
+
+	pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
+	pdfSeparateFragments: true,
+	pdfPageHeightOffset: -1,
+
+	dependencies: [],
+	plugins: [],
 };
 
 export type { Config };

+ 1 - 2
js/controllers/autoanimate.js

@@ -1,5 +1,4 @@
-import { queryAll, extend, createStyleSheet, matches, closest } from '../utils/util.js'
-import { FRAGMENT_STYLE_REGEX } from '../utils/constants.js'
+import { queryAll, extend, createStyleSheet, matches, closest } from '../utils/util'
 
 // Counter used to generate unique IDs for auto-animated elements
 let autoAnimateCounter = 0;

+ 2 - 2
js/controllers/backgrounds.js

@@ -1,5 +1,5 @@
-import { queryAll } from '../utils/util.js'
-import { colorToRgb, colorBrightness } from '../utils/color.js'
+import { queryAll } from '../utils/util'
+import { colorToRgb, colorBrightness } from '../utils/color'
 
 /**
  * Creates and updates slide backgrounds.

+ 2 - 2
js/controllers/controls.js

@@ -1,5 +1,5 @@
-import { queryAll, enterFullscreen } from '../utils/util.js'
-import { isAndroid } from '../utils/device.js'
+import { queryAll, enterFullscreen } from '../utils/util'
+import { isAndroid } from '../utils/device'
 
 /**
  * Manages our presentation controls. This includes both

+ 1 - 1
js/controllers/focus.js

@@ -1,4 +1,4 @@
-import { closest } from '../utils/util.js'
+import { closest } from '../utils/util'
 
 /**
  * Manages focus when a presentation is embedded. This

+ 1 - 1
js/controllers/fragments.js

@@ -1,4 +1,4 @@
-import { extend, queryAll } from '../utils/util.js'
+import { extend, queryAll } from '../utils/util'
 
 /**
  * Handles sorting and navigation of slide fragments.

+ 1 - 1
js/controllers/keyboard.js

@@ -1,4 +1,4 @@
-import { enterFullscreen } from '../utils/util.js'
+import { enterFullscreen } from '../utils/util'
 
 /**
  * Handles all reveal.js keyboard interactions.

+ 2 - 2
js/controllers/overview.js

@@ -1,5 +1,5 @@
-import { SLIDES_SELECTOR } from '../utils/constants.js'
-import { extend, queryAll, transformElement } from '../utils/util.js'
+import { SLIDES_SELECTOR } from '../utils/constants'
+import { extend, queryAll, transformElement } from '../utils/util'
 
 /**
  * Handles all logic related to the overview mode

+ 1 - 1
js/controllers/plugins.js

@@ -1,4 +1,4 @@
-import { loadScript } from '../utils/loader.js'
+import { loadScript } from '../utils/loader'
 
 /**
  * Manages loading and registering of reveal.js plugins.

+ 2 - 2
js/controllers/printview.js

@@ -1,5 +1,5 @@
-import { SLIDES_SELECTOR } from '../utils/constants.js'
-import { queryAll, createStyleSheet } from '../utils/util.js'
+import { SLIDES_SELECTOR } from '../utils/constants'
+import { queryAll, createStyleSheet } from '../utils/util'
 
 /**
  * Setups up our presentation for printing/exporting to PDF.

+ 2 - 2
js/controllers/scrollview.js

@@ -1,5 +1,5 @@
-import { HORIZONTAL_SLIDES_SELECTOR, HORIZONTAL_BACKGROUNDS_SELECTOR } from '../utils/constants.js'
-import { queryAll } from '../utils/util.js'
+import { HORIZONTAL_SLIDES_SELECTOR, HORIZONTAL_BACKGROUNDS_SELECTOR } from '../utils/constants'
+import { queryAll } from '../utils/util'
 
 const HIDE_SCROLLBAR_TIMEOUT = 500;
 const MAX_PROGRESS_SPACING = 4;

+ 2 - 2
js/controllers/slidecontent.js

@@ -1,5 +1,5 @@
-import { extend, queryAll, closest, getMimeTypeFromFile, encodeRFC3986URI } from '../utils/util.js'
-import { isMobile } from '../utils/device.js'
+import { extend, queryAll, closest, getMimeTypeFromFile, encodeRFC3986URI } from '../utils/util'
+import { isMobile } from '../utils/device'
 
 import fitty from 'fitty';
 

+ 2 - 2
js/controllers/touch.js

@@ -1,5 +1,5 @@
-import { isAndroid } from '../utils/device.js'
-import { matches } from '../utils/util.js'
+import { isAndroid } from '../utils/device'
+import { matches } from '../utils/util'
 
 const SWIPE_THRESHOLD = 40;
 

+ 13 - 13
js/index.ts

@@ -1,6 +1,6 @@
 import { Config } from './config.ts';
 
-//@ts-ignore
+// @ts-ignore
 import Deck, { VERSION } from './reveal.js';
 
 /**
@@ -14,8 +14,8 @@ import Deck, { VERSION } from './reveal.js';
  * });
  */
 const Reveal: {
-  initialize: (options?: Config) => Promise<void>;
-  [key: string]: any;
+	initialize: (options?: Config) => Promise<void>;
+	[key: string]: any;
 } = Deck;
 
 /**
@@ -35,13 +35,13 @@ type RevealApiFunction = (...args: any[]) => any;
 const enqueuedAPICalls: RevealApiFunction[] = [];
 
 Reveal.initialize = (options?: Config) => {
-  // Create our singleton reveal.js instance
-  Object.assign(Reveal, new Deck(document.querySelector('.reveal'), options));
+	// Create our singleton reveal.js instance
+	Object.assign(Reveal, new Deck(document.querySelector('.reveal'), options));
 
-  // Invoke any enqueued API calls
-  enqueuedAPICalls.map((method) => method(Reveal));
+	// Invoke any enqueued API calls
+	enqueuedAPICalls.map((method) => method(Reveal));
 
-  return Reveal.initialize();
+	return Reveal.initialize();
 };
 
 /**
@@ -51,11 +51,11 @@ Reveal.initialize = (options?: Config) => {
  * of them when Reveal.initialize is called.
  */
 ['configure', 'on', 'off', 'addEventListener', 'removeEventListener', 'registerPlugin'].forEach(
-  (method) => {
-    Reveal[method] = (...args: any) => {
-      enqueuedAPICalls.push((deck) => deck[method].call(null, ...args));
-    };
-  }
+	(method) => {
+		Reveal[method] = (...args: any) => {
+			enqueuedAPICalls.push((deck) => deck[method].call(null, ...args));
+		};
+	}
 );
 
 Reveal.isReady = () => false;

+ 23 - 23
js/reveal.js

@@ -1,32 +1,32 @@
-import SlideContent from './controllers/slidecontent.js'
-import SlideNumber from './controllers/slidenumber.js'
-import JumpToSlide from './controllers/jumptoslide.js'
-import Backgrounds from './controllers/backgrounds.js'
-import AutoAnimate from './controllers/autoanimate.js'
-import ScrollView from './controllers/scrollview.js'
-import PrintView from './controllers/printview.js'
-import Fragments from './controllers/fragments.js'
-import Overview from './controllers/overview.js'
-import Keyboard from './controllers/keyboard.js'
-import Location from './controllers/location.js'
-import Controls from './controllers/controls.js'
-import Progress from './controllers/progress.js'
-import Pointer from './controllers/pointer.js'
-import Plugins from './controllers/plugins.js'
-import Overlay from './controllers/overlay.js'
-import Touch from './controllers/touch.js'
-import Focus from './controllers/focus.js'
-import Notes from './controllers/notes.js'
-import Playback from './components/playback.js'
+import SlideContent from './controllers/slidecontent'
+import SlideNumber from './controllers/slidenumber'
+import JumpToSlide from './controllers/jumptoslide'
+import Backgrounds from './controllers/backgrounds'
+import AutoAnimate from './controllers/autoanimate'
+import ScrollView from './controllers/scrollview'
+import PrintView from './controllers/printview'
+import Fragments from './controllers/fragments'
+import Overview from './controllers/overview'
+import Keyboard from './controllers/keyboard'
+import Location from './controllers/location'
+import Controls from './controllers/controls'
+import Progress from './controllers/progress'
+import Pointer from './controllers/pointer'
+import Plugins from './controllers/plugins'
+import Overlay from './controllers/overlay'
+import Touch from './controllers/touch'
+import Focus from './controllers/focus'
+import Notes from './controllers/notes'
+import Playback from './components/playback'
 import { defaultConfig } from './config.ts'
-import * as Util from './utils/util.js'
-import * as Device from './utils/device.js'
+import * as Util from './utils/util'
+import * as Device from './utils/device'
 import {
 	SLIDES_SELECTOR,
 	HORIZONTAL_SLIDES_SELECTOR,
 	VERTICAL_SLIDES_SELECTOR,
 	POST_MESSAGE_METHOD_BLACKLIST
-} from './utils/constants.js'
+} from './utils/constants'
 
 // The reveal.js version
 export const VERSION = '6.0.0-rc.2';

+ 0 - 77
js/utils/color.js

@@ -1,77 +0,0 @@
-/**
- * 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;
-
-}

+ 75 - 0
js/utils/color.ts

@@ -0,0 +1,75 @@
+/**
+ * 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: string) => {
+	let hex3 = color.match(/^#([0-9a-f]{3})$/i);
+	if (hex3 && hex3[1]) {
+		const hex3Value = hex3[1];
+		return {
+			r: parseInt(hex3Value.charAt(0), 16) * 0x11,
+			g: parseInt(hex3Value.charAt(1), 16) * 0x11,
+			b: parseInt(hex3Value.charAt(2), 16) * 0x11,
+		};
+	}
+
+	let hex6 = color.match(/^#([0-9a-f]{6})$/i);
+	if (hex6 && hex6[1]) {
+		const hex6Value = hex6[1];
+		return {
+			r: parseInt(hex6Value.slice(0, 2), 16),
+			g: parseInt(hex6Value.slice(2, 4), 16),
+			b: parseInt(hex6Value.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: string | { r: number; g: number; b: number } | null) => {
+	if (typeof color === 'string') color = colorToRgb(color);
+
+	if (color) {
+		return (color.r * 299 + color.g * 587 + color.b * 114) / 1000;
+	}
+
+	return null;
+};

+ 5 - 4
js/utils/constants.js → js/utils/constants.ts

@@ -1,17 +1,18 @@
-
 export const SLIDES_SELECTOR = '.slides section';
 export const HORIZONTAL_SLIDES_SELECTOR = '.slides>section';
 export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section';
 export const HORIZONTAL_BACKGROUNDS_SELECTOR = '.backgrounds>.slide-background';
 
 // Methods that may not be invoked via the postMessage API
-export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/;
+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/;
+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/;
 
 // Slide number formats
 export const SLIDE_NUMBER_FORMAT_HORIZONTAL_DOT_VERTICAL = 'h.v';
 export const SLIDE_NUMBER_FORMAT_HORIZONTAL_SLASH_VERTICAL = 'h/v';
 export const SLIDE_NUMBER_FORMAT_CURRENT = 'c';
-export const SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL = 'c/t';
+export const SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL = 'c/t';

+ 0 - 8
js/utils/device.js

@@ -1,8 +0,0 @@
-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 );

+ 9 - 0
js/utils/device.ts

@@ -0,0 +1,9 @@
+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);

+ 0 - 46
js/utils/loader.js

@@ -1,46 +0,0 @@
-/**
- * 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 );
-
-}

+ 40 - 0
js/utils/loader.ts

@@ -0,0 +1,40 @@
+/**
+ * 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: string, callback?: (error?: Error) => void) => {
+	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 = (event: Event) => {
+			if (event.type === 'load') {
+				// Kill event listeners
+				script.onload = script.onerror = null;
+
+				callback();
+			}
+		};
+
+		// Error callback
+		script.onerror = (err: Event | string) => {
+			// Kill event listeners
+			script.onload = 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');
+	if (head) {
+		head.insertBefore(script, head.lastChild);
+	}
+};

+ 0 - 313
js/utils/util.js

@@ -1,313 +0,0 @@
-/**
- * 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()}`
-	  );
-}

+ 301 - 0
js/utils/util.ts

@@ -0,0 +1,301 @@
+/**
+ * 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: Record<string, any>, b: Record<string, any>) => {
+	for (let i in b) {
+		a[i] = b[i];
+	}
+
+	return a;
+};
+
+/**
+ * querySelectorAll but returns an Array.
+ */
+export const queryAll = (el: Element | Document, selector: string): Element[] => {
+	return Array.from(el.querySelectorAll(selector));
+};
+
+/**
+ * classList.toggle() with cross browser support
+ */
+export const toggleClass = (el: Element, className: string, value: boolean) => {
+	if (value) {
+		el.classList.add(className);
+	} else {
+		el.classList.remove(className);
+	}
+};
+
+type DeserializedValue = string | number | boolean | null;
+
+/**
+ * Utility for deserializing a value.
+ *
+ * @param {*} value
+ * @return {*}
+ */
+export const deserialize = (value: string): DeserializedValue => {
+	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: { x: number; y: number },
+	b: { x: number; y: number }
+): number => {
+	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: HTMLElement, transform: string) => {
+	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: any, selector: string): boolean => {
+	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: Element | null, selector: string): Element | null => {
+	// Native Element.closest
+	if (target && typeof target.closest === 'function') {
+		return target.closest(selector);
+	}
+
+	// Polyfill
+	while (target) {
+		if (matches(target, selector)) {
+			return target;
+		}
+
+		// Keep searching
+		target = target.parentElement;
+	}
+
+	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 = element || document.documentElement;
+
+	// Check which implementation is available
+	let requestMethod =
+		(element as any).requestFullscreen ||
+		(element as any).webkitRequestFullscreen ||
+		(element as any).webkitRequestFullScreen ||
+		(element as any).mozRequestFullScreen ||
+		(element as any).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: Element,
+	tagname: string,
+	classname: string,
+	innerHTML: string = ''
+): Element => {
+	// 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: string): HTMLStyleElement => {
+	let tag = document.createElement('style');
+
+	if (value && value.length > 0) {
+		tag.appendChild(document.createTextNode(value));
+	}
+
+	document.head.appendChild(tag);
+
+	return tag;
+};
+
+/**
+ * Returns a key:value hash of all query params.
+ */
+export const getQueryHash = (): Record<string, DeserializedValue> => {
+	let query: Record<string, DeserializedValue> = {};
+
+	location.search.replace(/[A-Z0-9]+?=([\w\.%-]*)/gi, (a: string) => {
+		const key = a.split('=').shift();
+		const value = a.split('=').pop();
+		if (key && value !== undefined) {
+			query[key] = value;
+		}
+		return a;
+	});
+
+	// Basic deserialization
+	for (let i in query) {
+		let value = query[i];
+
+		query[i] = deserialize(unescape(value as string));
+	}
+
+	// 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: HTMLElement | null, height: number = 0): number => {
+	if (element) {
+		let newHeight: number,
+			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.
+		if (element.parentElement) {
+			element.parentElement.style.height = 'auto';
+		}
+
+		newHeight = height - (element.parentElement?.offsetHeight || 0);
+
+		// Restore the old height, just in case
+		element.style.height = oldHeight + 'px';
+
+		// Clear the parent (.slide) height. .removeProperty works in IE9+
+		if (element.parentElement) {
+			element.parentElement.style.removeProperty('height');
+		}
+
+		return newHeight;
+	}
+
+	return height;
+};
+
+const fileExtensionToMimeMap: Record<string, string> = {
+	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: string = ''): string | undefined => {
+	const extension = filename.split('.').pop();
+	return extension ? fileExtensionToMimeMap[extension] : undefined;
+};
+
+/**
+ * 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: string = ''): string => {
+	return encodeURI(url)
+		.replace(/%5B/g, '[')
+		.replace(/%5D/g, ']')
+		.replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
+};