update packages and add valign

This commit is contained in:
2026-04-05 20:00:27 +02:00
parent b062fb98e3
commit 03fb00e374
640 changed files with 109768 additions and 39311 deletions

View File

@@ -1,333 +0,0 @@
/**
* The default reveal.js config object.
*/
export default {
// The "normal" size of the presentation, aspect ratio will be preserved
// when the presentation is scaled to fit different resolutions
width: 960,
height: 700,
// Factor of the display size that should remain empty around the content
margin: 0.04,
// Bounds for smallest/largest possible scale to apply to content
minScale: 0.2,
maxScale: 2.0,
// Display presentation control arrows.
// - true: Display controls on all screens
// - false: Hide controls on all screens
// - "speaker-only": Only display controls in the speaker view
controls: true,
// Help the user learn the controls by providing hints, for example by
// bouncing the down arrow when they first encounter a vertical slide
controlsTutorial: true,
// Determines where controls appear, "edges" or "bottom-right"
controlsLayout: 'bottom-right',
// Visibility rule for backwards navigation arrows; "faded", "hidden"
// or "visible"
controlsBackArrows: 'faded',
// Display a presentation progress bar
progress: true,
// 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().
slideNumber: false,
// 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
showSlideNumber: 'all',
// Use 1 based indexing for # links to match slide number (default is zero
// based)
hashOneBasedIndex: false,
// Add the current slide number to the URL hash so that reloading the
// page/copying the URL will return you to the same slide
hash: false,
// Flags if we should monitor the hash and change slides accordingly
respondToHashChanges: true,
// Enable support for jump-to-slide navigation shortcuts
jumpToSlide: true,
// Push each slide change to the browser history. Implies `hash: true`
history: false,
// Enable keyboard shortcuts for navigation
keyboard: true,
// 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
keyboardCondition: null,
// Disables the default reveal.js slide layout (scaling and centering)
// so that you can use custom CSS layout
disableLayout: false,
// Enable the slide overview mode
overview: true,
// Vertical centering of slides
center: true,
// Enables touch navigation on devices with touch input
touch: true,
// Loop the presentation
loop: false,
// Change the presentation direction to be RTL
rtl: false,
// 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.
navigationMode: 'default',
// Randomizes the order of slides each time the presentation loads
shuffle: false,
// Turns fragments on and off globally
fragments: true,
// Flags whether to include the current fragment in the URL,
// so that reloading brings you to the same fragment position
fragmentInURL: true,
// Flags if the presentation is running in an embedded mode,
// i.e. contained within a limited portion of the screen
embedded: false,
// Flags if we should show a help overlay when the question-mark
// key is pressed
help: true,
// Flags if it should be possible to pause the presentation (blackout)
pause: true,
// Flags if speaker notes should be visible to all viewers
showNotes: false,
// Flags if slides with data-visibility="hidden" should be kep visible
showHiddenSlides: false,
// 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
autoPlayMedia: null,
// 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
preloadIframes: null,
// Can be used to globally disable auto-animation
autoAnimate: true,
// Optionally provide a custom element matcher that will be
// used to dictate which elements we can animate between.
autoAnimateMatcher: null,
// Default settings for our auto-animate transitions, can be
// overridden per-slide or per-element via data arguments
autoAnimateEasing: 'ease',
autoAnimateDuration: 1.0,
autoAnimateUnmatched: true,
// 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.
autoAnimateStyles: [
'opacity',
'color',
'background-color',
'padding',
'font-size',
'line-height',
'letter-spacing',
'border-width',
'border-color',
'border-radius',
'outline',
'outline-offset'
],
// 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
autoSlide: 0,
// Stop auto-sliding after user input
autoSlideStoppable: true,
// Use this method for navigation when auto-sliding (defaults to navigateNext)
autoSlideMethod: null,
// 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
defaultTiming: null,
// Enable slide navigation via mouse wheel
mouseWheel: false,
// Opens links in an iframe preview overlay
// Add `data-preview-link` and `data-preview-link="false"` to customise each link
// individually
previewLinks: false,
// Exposes the reveal.js API through window.postMessage
postMessage: true,
// Dispatches all reveal.js events to the parent window through postMessage
postMessageEvents: false,
// Focuses body when page changes visibility to ensure keyboard shortcuts work
focusBodyOnPageVisibilityChange: true,
// Transition style
transition: 'slide', // none/fade/slide/convex/concave/zoom
// Transition speed
transitionSpeed: 'default', // default/fast/slow
// Transition style for full page slide backgrounds
backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
// Parallax background image
parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
// Parallax background size
parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
// Parallax background repeat
parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
// Parallax background position
parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left"
// Amount of pixels to move the parallax background per slide step
parallaxBackgroundHorizontal: null,
parallaxBackgroundVertical: null,
// 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
view: null,
// 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
scrollLayout: 'full',
// 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.
scrollSnap: 'mandatory',
// Enables and configure the scroll view progress bar.
// - 'auto': Show the scrollbar while scrolling, hide while idle
// - true: Always show the scrollbar
// - false: Never show the scrollbar
scrollProgress: 'auto',
// Automatically activate the scroll view when we the viewport falls
// below the given width.
scrollActivationWidth: 435,
// The maximum number of pages a single slide can expand onto when printing
// to PDF, unlimited by default
pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
// Prints each fragment on a separate slide
pdfSeparateFragments: true,
// 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.
pdfPageHeightOffset: -1,
// Number of slides away from the current that are visible
viewDistance: 3,
// 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.
mobileViewDistance: 2,
// The display mode that will be used to show slides
display: 'block',
// Hide cursor if inactive
hideInactiveCursor: true,
// Time before the cursor is hidden (in ms)
hideCursorTime: 5000,
// Should we automatically sort and set indices for fragments
// at each sync? (See Reveal.sync)
sortFragmentsOnSync: true,
// Script dependencies to load
dependencies: [],
// Plugin objects to register and use for this presentation
plugins: []
}

View File

@@ -0,0 +1,973 @@
/**
* Slide transition styles.
*
* @see {@link https://revealjs.com/transitions/}
*/
type TransitionStyle = 'none' | 'fade' | 'slide' | 'convex' | 'concave' | 'zoom';
/**
* Slide transition speeds.
*
* @see {@link https://revealjs.com/transitions/}
*/
type TransitionSpeed = 'default' | 'fast' | 'slow';
/**
* Fragment animation classes.
*
* @see {@link https://revealjs.com/fragments/}
*/
type FragmentAnimation =
| 'fade-out'
| 'fade-up'
| 'fade-down'
| 'fade-left'
| 'fade-right'
| 'fade-in-then-out'
| 'fade-in-then-semi-out'
| 'grow'
| 'shrink'
| 'strike'
| 'highlight-red'
| 'highlight-blue'
| 'highlight-green'
| 'highlight-current-red'
| 'highlight-current-blue'
| 'highlight-current-green'
| (string & {});
/**
* katex - Math Plugin configuration
*
* @see {@link https://github.com/reveal/revealjs.com/blob/master/src/math.md}
* @see {@link https://github.com/hakimel/reveal.js/blob/master/plugin/math/katex.js}
*/
interface KatexConfig {
local?: string;
version?: string;
delimiters?: Array<{ left: string; right: string; display: boolean }>;
ignoredTags?: string[];
}
/**
* mathjax2 - Math Plugin configuration
*
* @see {@link https://github.com/reveal/revealjs.com/blob/master/src/math.md}
* @see {@link https://github.com/hakimel/reveal.js/blob/master/plugin/math/mathjax2.js}
*/
interface Mathjax2Config {
mathjax?: string;
config?: string;
tex2jax?: {
inlineMath?: any;
skipTags?: string[];
};
}
/**
* mathjax3 - Math Plugin configuration
*
* @see {@link https://github.com/reveal/revealjs.com/blob/master/src/math.md}
* @see {@link https://github.com/hakimel/reveal.js/blob/master/plugin/math/mathjax3.js}
*/
interface Mathjax3Config {
mathjax?: string;
tex?: { inlineMath?: any };
options?: { skipHtmlTags: string[] };
}
/**
* mathjax4 - Math Plugin configuration
*
* @see {@link https://github.com/reveal/revealjs.com/blob/master/src/math.md}
* @see {@link https://github.com/hakimel/reveal.js/blob/master/plugin/math/mathjax4.js}
*/
interface Mathjax4Config {
mathjax?: string;
tex?: {
inlineMath?: Array<[string, string]>;
displayMath?: Array<[string, string]>;
macros?: Record<string, string | [string, number]>;
};
options?: {
skipHtmlTags?: string[];
};
startup?: {
ready?: () => void;
};
output?: {
font?: string;
displayOverflow?: string;
linebreaks?: {
inline?: boolean;
width?: string;
lineleading?: number;
LinebreakVisitor?: unknown;
};
};
}
/**
* Highlight Plugin configuration
*
* @see {@link https://github.com/hakimel/reveal.js/blob/master/plugin/highlight/plugin.js}
*/
interface HighlightConfig {
highlightOnLoad?: boolean;
escapeHTML?: boolean;
beforeHighlight?: (...args: any) => any;
}
/**
* Markdown Plugin configuration
*
* @see {@link https://github.com/reveal/revealjs.com/blob/master/src/markdown.md}
* @see {@link https://marked.js.org/using_advanced}
*/
interface MarkdownConfig {
async?: boolean;
baseUrl?: string;
breaks?: boolean;
gfm?: boolean;
headerIds?: boolean;
headerPrefix?: string;
highlight?: (...args: any) => any;
langPrefix?: string;
mangle?: boolean;
pedantic?: boolean;
renderer?: object;
sanitize?: boolean;
sanitizer?: (...args: any) => any;
silent?: boolean;
smartLists?: boolean;
smartypants?: boolean;
tokenizer?: object;
walkTokens?: (...args: any) => any;
xhtml?: boolean;
separator?: string;
verticalSeparator?: string;
notesSeparator?: string;
attributes?: string;
}
/**
* Configuration object for reveal.js.
*
* @see {@link https://revealjs.com/config/}
*/
interface RevealConfig {
/**
* The "normal" size of the presentation, aspect ratio will be preserved
* when the presentation is scaled to fit different resolutions
*
* @see {@link https://revealjs.com/presentation-size/}
*
* @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
*
* @see {@link https://revealjs.com/presentation-size/}
*
* @defaultValue 700
*/
height?: number | string;
/**
* Factor of the display size that should remain empty around the content
*
* @see {@link https://revealjs.com/presentation-size/}
*
* @defaultValue 0.04
*/
margin?: number;
/**
* The smallest possible factor to scale content down by in order to fit
* the available viewport.
*
* @see {@link https://revealjs.com/presentation-size/}
*
* @defaultValue 0.2
*/
minScale?: number;
/**
* The largest possible factor to scale content up by in order to cover
* the available viewport.
*
* @see {@link https://revealjs.com/presentation-size/}
*
* @defaultValue 2.0
*/
maxScale?: number;
/**
* Display presentation control arrows
* - true: Display controls in all views
* - false: Hide controls in all views
* - 'speaker': Display controls only in the speaker view
*
* @defaultValue true
*/
controls?: boolean | 'speaker' | '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 | { [keyCode: number]: string | ((event: KeyboardEvent) => void) };
/**
* 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
*
* @see {@link https://revealjs.com/overview/}
*
* @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
*
* @see {@link https://revealjs.com/fragments/}
*
* @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;
/**
* Prevent embedded iframes from automatically focusing on themselves
*
* @defaultValue false
*/
preventIframeAutoFocus?: boolean;
/**
* Can be used to globally disable auto-animation
*
* @see {@link https://revealjs.com/auto-animate/}
*
* @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
*
* @see {@link https://revealjs.com/transitions/}
*
* @defaultValue 'slide'
*/
transition?: TransitionStyle;
/**
* Transition speed
*
* @defaultValue 'default'
*/
transitionSpeed?: TransitionSpeed;
/**
* Transition style for full page slide backgrounds
*
* @defaultValue 'fade'
*/
backgroundTransition?: TransitionStyle;
/**
* 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
*
* @see {@link https://revealjs.com/scroll-view/}
*
* @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;
/**
* Highlight plugin configuration
*/
highlight?: HighlightConfig;
/**
* Markdown plugin configuration
*/
markdown?: MarkdownConfig;
/**
* KaTeX math plugin configuration
*/
katex?: KatexConfig;
/**
* MathJax 2 plugin configuration
*/
mathjax2?: Mathjax2Config;
/**
* MathJax 3 plugin configuration
*/
mathjax3?: Mathjax3Config;
/**
* MathJax 4 plugin configuration
*/
mathjax4?: Mathjax4Config;
/**
* 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: RevealConfig = {
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 {
RevealConfig,
TransitionStyle,
TransitionSpeed,
FragmentAnimation,
KatexConfig,
Mathjax2Config,
Mathjax3Config,
Mathjax4Config,
HighlightConfig,
MarkdownConfig,
};
export { defaultConfig };

View File

@@ -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;

View File

@@ -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.

View File

@@ -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
@@ -66,9 +66,11 @@ export default class Controls {
*/
configure( config, oldConfig ) {
const speakerOnly = config.controls === 'speaker' || config.controls === 'speaker-only';
this.element.style.display = (
config.controls &&
(config.controls !== 'speaker-only' || this.Reveal.isSpeakerNotes())
(!speakerOnly || this.Reveal.isSpeakerNotes())
) ? 'block' : 'none';
this.element.setAttribute( 'data-controls-layout', config.controlsLayout );
@@ -83,9 +85,10 @@ export default class Controls {
let pointerEvents = [ 'touchstart', 'click' ];
// Only support touch for Android, fixes double navigations in
// stock browser
// stock browser. Use touchend for it to be considered a valid
// user interaction (so we're allowed to autoplay media).
if( isAndroid ) {
pointerEvents = [ 'touchstart' ];
pointerEvents = [ 'touchend' ];
}
pointerEvents.forEach( eventName => {
@@ -102,7 +105,7 @@ export default class Controls {
unbind() {
[ 'touchstart', 'click' ].forEach( eventName => {
[ 'touchstart', 'touchend', 'click' ].forEach( eventName => {
this.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) );
this.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) );
this.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) );

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { enterFullscreen } from '../utils/util.js'
import { enterFullscreen } from '../utils/util'
/**
* Handles all reveal.js keyboard interactions.
@@ -398,6 +398,12 @@ export default class Keyboard {
event.preventDefault && event.preventDefault();
}
// Enter to exit overview mode
else if (keyCode === 13 && this.Reveal.overview.isActive()) {
this.Reveal.overview.deactivate();
event.preventDefault && event.preventDefault();
}
// If auto-sliding is enabled we need to cue up
// another timeout

View File

@@ -60,11 +60,13 @@ export default class Location {
name = name.split( '/' ).shift();
}
// Ensure the named link is a valid HTML ID attribute
// Ensure the named link is a valid HTML id or data-id attribute
try {
slide = document
.getElementById( decodeURIComponent( name ) )
.closest('.slides section');
const decodedName = decodeURIComponent( name );
slide = (
document.getElementById( decodedName ) ||
document.querySelector( `[data-id="${decodedName}"]` )
).closest('.slides section');
}
catch ( error ) { }

View File

@@ -72,8 +72,8 @@ export default class Overlay {
this.viewport.innerHTML =
`<header class="r-overlay-header">
<a class="r-overlay-button r-overlay-external" href="${url}" target="_blank"><span class="icon"></span></a>
<button class="r-overlay-button r-overlay-close"><span class="icon"></span></button>
<a class="r-overlay-header-button r-overlay-external" href="${url}" target="_blank"><span class="icon"></span></a>
<button class="r-overlay-header-button r-overlay-close"><span class="icon"></span></button>
</header>
<div class="r-overlay-spinner"></div>
<div class="r-overlay-content">
@@ -125,7 +125,7 @@ export default class Overlay {
this.viewport.innerHTML =
`<header class="r-overlay-header">
<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
<button class="r-overlay-header-button r-overlay-close">Esc <span class="icon"></span></button>
</header>
<div class="r-overlay-spinner"></div>
<div class="r-overlay-content"></div>`;
@@ -262,7 +262,7 @@ export default class Overlay {
this.viewport.innerHTML = `
<header class="r-overlay-header">
<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
<button class="r-overlay-header-button r-overlay-close">Esc <span class="icon"></span></button>
</header>
<div class="r-overlay-content">
<div class="r-overlay-help-content">${html}</div>
@@ -348,7 +348,9 @@ export default class Overlay {
// Let the browser handle meta keys naturally so users can cmd+click
return;
}
let url = linkTarget.getAttribute( 'href' ) || linkTarget.getAttribute( 'data-preview-link' );
const dataPreviewLink = linkTarget.getAttribute( 'data-preview-link' );
const dataPreviewLinkIsUrl = typeof dataPreviewLink === 'string' && dataPreviewLink.startsWith( 'http' );
let url = dataPreviewLinkIsUrl ? dataPreviewLink : linkTarget.getAttribute( 'href' );
if( url ) {
this.previewIframe( url );
event.preventDefault();

View File

@@ -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

View File

@@ -1,4 +1,4 @@
import { loadScript } from '../utils/loader.js'
import { loadScript } from '../utils/loader'
/**
* Manages loading and registering of reveal.js plugins.
@@ -206,7 +206,6 @@ export default class Plugins {
else {
console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' );
}
}
/**

View File

@@ -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.

View File

@@ -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;

View File

@@ -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';
@@ -9,11 +9,44 @@ import fitty from 'fitty';
*/
export default class SlideContent {
allowedToPlayAudio = null;
constructor( Reveal ) {
this.Reveal = Reveal;
this.startEmbeddedMedia = this.startEmbeddedMedia.bind( this );
this.startEmbeddedIframe = this.startEmbeddedIframe.bind( this );
this.preventIframeAutoFocus = this.preventIframeAutoFocus.bind( this );
this.ensureMobileMediaPlaying = this.ensureMobileMediaPlaying.bind( this );
this.failedAudioPlaybackTargets = new Set();
this.failedVideoPlaybackTargets = new Set();
this.failedMutedVideoPlaybackTargets = new Set();
this.renderMediaPlayButton();
}
renderMediaPlayButton() {
this.mediaPlayButton = document.createElement( 'button' );
this.mediaPlayButton.className = 'r-overlay-button r-media-play-button';
this.mediaPlayButton.addEventListener( 'click', () => {
this.resetTemporarilyMutedMedia();
const failedTargets = new Set( [
...this.failedAudioPlaybackTargets,
...this.failedVideoPlaybackTargets,
...this.failedMutedVideoPlaybackTargets
] );
failedTargets.forEach( target => {
this.startEmbeddedMedia( { target: target } );
} );
this.clearMediaPlaybackErrors();
} );
}
@@ -51,14 +84,25 @@ export default class SlideContent {
load( slide, options = {} ) {
// Show the slide element
slide.style.display = this.Reveal.getConfig().display;
const displayValue = this.Reveal.getConfig().display;
if( displayValue.includes('!important') ) {
const value = displayValue.replace(/\s*!important\s*$/, '').trim();
slide.style.setProperty('display', value, 'important');
} else {
slide.style.display = displayValue;
}
// Media elements with data-src attributes
// Media and iframe elements with data-src attributes
queryAll( slide, 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ).forEach( element => {
if( element.tagName !== 'IFRAME' || this.shouldPreload( element ) ) {
const isIframe = element.tagName === 'IFRAME';
if( !isIframe || this.shouldPreload( element ) ) {
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
element.setAttribute( 'data-lazy-loaded', '' );
element.removeAttribute( 'data-src' );
if( isIframe ) {
element.addEventListener( 'load', this.preventIframeAutoFocus );
}
}
} );
@@ -131,12 +175,7 @@ export default class SlideContent {
}
// Enable inline playback in mobile Safari
//
// Mute is required for video to play when using
// swipe gestures to navigate since they don't
// count as direct user actions :'(
if( isMobile ) {
video.muted = true;
video.setAttribute( 'playsinline', '' );
}
@@ -318,20 +357,9 @@ export default class SlideContent {
// Mobile devices never fire a loaded event so instead
// of waiting, we initiate playback
else if( isMobile ) {
let promise = el.play();
el.addEventListener( 'canplay', this.ensureMobileMediaPlaying );
// If autoplay does not work, ensure that the controls are visible so
// that the viewer can start the media on their own
if( promise && typeof promise.catch === 'function' && el.controls === false ) {
promise.catch( () => {
el.controls = true;
// Once the video does start playing, hide the controls again
el.addEventListener( 'play', () => {
el.controls = false;
} );
} );
}
this.playMediaElement( el );
}
// If the media isn't loaded, wait before playing
else {
@@ -374,6 +402,40 @@ export default class SlideContent {
}
/**
* Ensure that an HTMLMediaElement is playing on mobile devices.
*
* This is a workaround for a bug in mobile Safari where
* the media fails to display if many videos are started
* at the same moment. When this happens, Mobile Safari
* reports the video is playing, and the current time
* advances, but nothing is visible.
*
* @param {Event} event
*/
ensureMobileMediaPlaying( event ) {
const el = event.target;
// Ignore this check incompatible browsers
if( typeof el.getVideoPlaybackQuality !== 'function' ) {
return;
}
setTimeout( () => {
const playing = el.paused === false;
const totalFrames = el.getVideoPlaybackQuality().totalVideoFrames;
if( playing && totalFrames === 0 ) {
el.load();
el.play();
}
}, 1000 );
}
/**
* Starts playing an embedded video/audio element after
* it has finished loading.
@@ -389,7 +451,7 @@ export default class SlideContent {
// Don't restart if media is already playing
if( event.target.paused || event.target.ended ) {
event.target.currentTime = 0;
event.target.play();
this.playMediaElement( event.target );
}
}
@@ -397,6 +459,55 @@ export default class SlideContent {
}
/**
* Plays the given HTMLMediaElement and handles any playback
* errors, such as the browser not allowing audio to play without
* user action.
*
* @param {HTMLElement} mediaElement
*/
playMediaElement( mediaElement ) {
const promise = mediaElement.play();
if( promise && typeof promise.catch === 'function' ) {
promise
.then( () => {
if( !mediaElement.muted ) {
this.allowedToPlayAudio = true;
}
} )
.catch( ( error ) => {
if( error.name === 'NotAllowedError' ) {
this.allowedToPlayAudio = false;
// If this is a video, we record the error and try to play it
// muted as a fallback. The user will be presented with an unmute
// button.
if( mediaElement.tagName === 'VIDEO' ) {
this.onVideoPlaybackNotAllowed( mediaElement );
let isAttachedToDOM = !!closest( mediaElement, 'html' ),
isVisible = !!closest( mediaElement, '.present' ),
isMuted = mediaElement.muted;
if( isAttachedToDOM && isVisible && !isMuted ) {
mediaElement.setAttribute( 'data-muted-by-reveal', 'true' );
mediaElement.muted = true;
mediaElement.play().catch(() => {
this.onMutedVideoPlaybackNotAllowed( mediaElement );
});
}
}
else if( mediaElement.tagName === 'AUDIO' ) {
this.onAudioPlaybackNotAllowed( mediaElement );
}
}
} );
}
}
/**
* "Starts" the content of an embedded iframe using the
* postMessage API.
@@ -407,6 +518,8 @@ export default class SlideContent {
let iframe = event.target;
this.preventIframeAutoFocus( event );
if( iframe && iframe.contentWindow ) {
let isAttachedToDOM = !!closest( event.target, 'html' ),
@@ -461,12 +574,17 @@ export default class SlideContent {
if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
el.setAttribute('data-paused-by-reveal', '');
el.pause();
if( isMobile ) {
el.removeEventListener( 'canplay', this.ensureMobileMediaPlaying );
}
}
} );
// Generic postMessage API for non-lazy loaded iframes
queryAll( element, 'iframe' ).forEach( el => {
if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );
el.removeEventListener( 'load', this.preventIframeAutoFocus );
el.removeEventListener( 'load', this.startEmbeddedIframe );
});
@@ -497,4 +615,132 @@ export default class SlideContent {
}
/**
* Checks whether media playback is blocked by the browser. This
* typically happens when media playback is initiated without a
* direct user interaction.
*/
isAllowedToPlayAudio() {
return this.allowedToPlayAudio;
}
/**
* Shows a manual button in situations where autoamtic media playback
* is not allowed by the browser.
*/
showPlayOrUnmuteButton() {
const audioTargets = this.failedAudioPlaybackTargets.size;
const videoTargets = this.failedVideoPlaybackTargets.size;
const mutedVideoTargets = this.failedMutedVideoPlaybackTargets.size;
let label = 'Play media';
if( mutedVideoTargets > 0 ) {
label = 'Play video';
}
else if( videoTargets > 0 ) {
label = 'Unmute video';
}
else if( audioTargets > 0 ) {
label = 'Play audio';
}
this.mediaPlayButton.textContent = label;
this.Reveal.getRevealElement().appendChild( this.mediaPlayButton );
}
onAudioPlaybackNotAllowed( target ) {
this.failedAudioPlaybackTargets.add( target );
this.showPlayOrUnmuteButton( target );
}
onVideoPlaybackNotAllowed( target ) {
this.failedVideoPlaybackTargets.add( target );
this.showPlayOrUnmuteButton();
}
onMutedVideoPlaybackNotAllowed( target ) {
this.failedMutedVideoPlaybackTargets.add( target );
this.showPlayOrUnmuteButton();
}
/**
* Videos may be temporarily muted by us to get around browser
* restrictions on automatic playback. This method rolls back
* all such temporary audio changes.
*/
resetTemporarilyMutedMedia() {
const failedTargets = new Set( [
...this.failedAudioPlaybackTargets,
...this.failedVideoPlaybackTargets,
...this.failedMutedVideoPlaybackTargets
] );
failedTargets.forEach( target => {
if( target.hasAttribute( 'data-muted-by-reveal' ) ) {
target.muted = false;
target.removeAttribute( 'data-muted-by-reveal' );
}
} );
}
clearMediaPlaybackErrors() {
this.resetTemporarilyMutedMedia();
this.failedAudioPlaybackTargets.clear();
this.failedVideoPlaybackTargets.clear();
this.failedMutedVideoPlaybackTargets.clear();
this.mediaPlayButton.remove();
}
/**
* Prevents iframes from automatically focusing themselves.
*
* @param {Event} event
*/
preventIframeAutoFocus( event ) {
const iframe = event.target;
if( iframe && this.Reveal.getConfig().preventIframeAutoFocus ) {
let elapsed = 0;
const interval = 100;
const maxTime = 1000;
const checkFocus = () => {
if( document.activeElement === iframe ) {
document.activeElement.blur();
} else if( elapsed < maxTime ) {
elapsed += interval;
setTimeout( checkFocus, interval );
}
};
setTimeout( checkFocus, interval );
}
}
afterSlideChanged() {
this.clearMediaPlaybackErrors();
}
}

View File

@@ -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;
@@ -216,6 +216,14 @@ export default class Touch {
*/
onTouchEnd( event ) {
// Media playback is only allowed as a direct result of a
// user interaction. Some mobile devices do not consider a
// 'touchmove' to be a direct user action. If this is the
// case, we fall back to starting playback here instead.
if( this.touchCaptured && !this.Reveal.slideContent.isAllowedToPlayAudio() ) {
this.Reveal.startEmbeddedContent( this.Reveal.getCurrentSlide() );
}
this.touchCaptured = false;
}

View File

@@ -1,58 +0,0 @@
import Deck, { VERSION } from './reveal.js'
/**
* Expose the Reveal class to the window. To create a
* new instance:
* let deck = new Reveal( document.querySelector( '.reveal' ), {
* controls: false
* } );
* deck.initialize().then(() => {
* // reveal.js is ready
* });
*/
let Reveal = Deck;
/**
* The below is a thin shell that mimics the pre 4.0
* reveal.js API and ensures backwards compatibility.
* This API only allows for one Reveal instance per
* page, whereas the new API above lets you run many
* presentations on the same page.
*
* Reveal.initialize( { controls: false } ).then(() => {
* // reveal.js is ready
* });
*/
let enqueuedAPICalls = [];
Reveal.initialize = 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 ) );
return Reveal.initialize();
}
/**
* The pre 4.0 API let you add event listener before
* initializing. We maintain the same behavior by
* queuing up premature API calls and invoking all
* of them when Reveal.initialize is called.
*/
[ 'configure', 'on', 'off', 'addEventListener', 'removeEventListener', 'registerPlugin' ].forEach( method => {
Reveal[method] = ( ...args ) => {
enqueuedAPICalls.push( deck => deck[method].call( null, ...args ) );
}
} );
Reveal.isReady = () => false;
Reveal.VERSION = VERSION;
export default Reveal;

View File

@@ -0,0 +1,74 @@
/// <reference path="./reveal.d.ts" />
import { RevealConfig } from './config.ts';
import type { RevealApi } from './reveal';
// @ts-ignore
import Deck, { VERSION } from './reveal.js';
/**
* Expose the Reveal class to the window. To create a
* new instance:
* let deck = new Reveal( document.querySelector( '.reveal' ), {
* controls: false
* } );
* deck.initialize().then(() => {
* // reveal.js is ready
* });
*/
const Reveal: {
initialize: (options?: RevealConfig) => Promise<RevealApi>;
[key: string]: any;
} = Deck;
/**
* The below is a thin shell that mimics the pre 4.0
* reveal.js API and ensures backwards compatibility.
* This API only allows for one Reveal instance per
* page, whereas the new API above lets you run many
* presentations on the same page.
*
* Reveal.initialize( { controls: false } ).then(() => {
* // reveal.js is ready
* });
*/
type RevealApiFunction = (deck: RevealApi) => any;
const enqueuedAPICalls: RevealApiFunction[] = [];
Reveal.initialize = (options?: RevealConfig) => {
const revealElement = document.querySelector('.reveal');
if (!(revealElement instanceof HTMLElement)) {
throw new Error('Unable to find presentation root (<div class="reveal">).');
}
// Create our singleton reveal.js instance
Object.assign(Reveal, new Deck(revealElement, options));
// Invoke any enqueued API calls
enqueuedAPICalls.map((method) => method(Reveal as RevealApi));
return Reveal.initialize();
};
/**
* The pre 4.0 API let you add event listener before
* initializing. We maintain the same behavior by
* queuing up premature API calls and invoking all
* of them when Reveal.initialize is called.
*/
(
['configure', 'on', 'off', 'addEventListener', 'removeEventListener', 'registerPlugin'] as const
).forEach((method) => {
Reveal[method] = (...args: any) => {
enqueuedAPICalls.push((deck) => (deck[method] as any).call(null, ...args));
};
});
Reveal.isReady = () => false;
Reveal.VERSION = VERSION;
export default Reveal;

876
scripts/reveal.js/js/reveal.d.ts vendored Normal file
View File

@@ -0,0 +1,876 @@
import type {
RevealConfig,
TransitionStyle,
TransitionSpeed,
FragmentAnimation,
KatexConfig,
Mathjax2Config,
Mathjax3Config,
Mathjax4Config,
HighlightConfig,
MarkdownConfig,
} from './config';
export type {
RevealConfig,
TransitionStyle,
TransitionSpeed,
FragmentAnimation,
KatexConfig,
Mathjax2Config,
Mathjax3Config,
Mathjax4Config,
HighlightConfig,
MarkdownConfig,
} from './config';
export default Reveal;
// The type definitions in this file are adapted from those
// originally created by the community on DefinitelyTyped:
// https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/reveal.js
/**
* reveal.js - MIT licensed
*
* Copyright (C) 2011-2026 Hakim El Hattab, https://hakim.se
*
* @see {@link https://revealjs.com}
* @see {@link https://github.com/hakimel/reveal.js/blob/master/js/reveal.js}
* @see {@link https://revealjs.com/api/}
*/
declare const Reveal: {
new (options?: RevealConfig): RevealApi;
new (revealElement: HTMLElement, options?: RevealConfig): RevealApi;
} & RevealApi;
/**
* The public reveal.js API
*
* @see {@link https://github.com/hakimel/reveal.js/blob/master/js/reveal.js}
*/
export interface RevealApi {
/**
* The reveal.js version
*
* @returns reveal.js version
*/
VERSION: string;
/**
* Starts up the presentation.
*
* @param options - RevealOption see {@link Options}
* @returns a promise
*/
initialize(options?: RevealConfig): Promise<RevealApi>;
/**
* Applies the configuration settings from the config
* object. May be called multiple times.
*
* @param options - RevealOption see {@link RevealConfig}
*/
configure(options?: RevealConfig): void;
/**
* Uninitializes reveal.js by undoing changes made to the
* DOM and removing all event listeners.
*/
destroy(): void;
/**
* Syncs the presentation with the current DOM. Useful
* when new slides or control elements are added or when
* the configuration has changed.
*/
sync(): void;
/**
* Updates reveal.js to keep in sync with new slide attributes. For
* example, if you add a new `data-background-image` you can call
* this to have reveal.js render the new background image.
*
* Similar to #sync() but more efficient when you only need to
* refresh a specific slide. Dispatches a `slidesync` event
* when syncing has completed.
*
* @param slide
* @see {@link sync}
*/
syncSlide(slide: HTMLElement): void;
/**
* Formats the fragments on the given slide so that they have
* valid indices. Call this if fragments are changed in the DOM
* after reveal.js has already initialized.
*
* @param slide
* @returns a list of the HTML fragments that were synced
*/
syncFragments(slide: HTMLElement): HTMLElement[];
/**
* Removes hidden slides (data-visibility="hidden") from the DOM.
* This happens automatically when reveal.js initialized, so only
* call this to remove hidden slides before initialization.
*/
removeHiddenSlides(): void;
/**
* Steps from the current point in the presentation to the
* slide which matches the specified horizontal and vertical
* indices.
*
* @param horizontalIndex - Horizontal index of the target slide
* @param verticalIndex - Vertical index of the target slide
* @param fragmentIndex - Index of a fragment within the target slide to activate
* @param origin - Origin for use in multimaster environments
*/
slide(
horizontalIndex?: number,
verticalIndex?: number,
fragmentIndex?: number,
origin?: number
): void;
/**
* Navigate one step to the left
*
* @param params see {@link NavigateParams}
*/
left: NavigationFunction;
/**
* Navigate one step to the right
*
* @param params see {@link NavigateParams}
*/
right: NavigationFunction;
/**
* Navigate one step up
*
* @param params see {@link NavigateParams}
*/
up: NavigationFunction;
/**
* Navigate one step down
*
* @param params see {@link NavigateParams}
*/
down: NavigationFunction;
/**
* Navigates backwards, prioritized in the following order:
* 1) Previous fragment
* 2) Previous vertical slide
* 3) Previous horizontal slide
*
* @param params see {@link NavigateParams}
*/
prev: NavigationFunction;
/**
* Navigates forwards, prioritized in the following order:
* 1) Next fragment
* 2) Next vertical slide
* 3) Next horizontal slide
*
* @param params see {@link NavigateParams}
*/
next: NavigationFunction;
// Navigation aliases
/**
* Alias for `left` see {@link left}
*/
navigateLeft: NavigationFunction;
/**
* Alias for `right` see {@link right}
*/
navigateRight: NavigationFunction;
/**
* Alias for `up` see {@link up}
*/
navigateUp: NavigationFunction;
/**
* Alias for `down` see {@link down}
*/
navigateDown: NavigationFunction;
/**
* Alias for `prev` see {@link prev}
*/
navigatePrev: NavigationFunction;
/**
* Alias for `next` see {@link next}
*/
navigateNext: NavigationFunction;
/**
* Navigate to the specified slide fragment.
*
* @param index - The index of the fragment that
* should be shown, -1 means all are invisible
* @param offset - Integer offset to apply to the
* fragment index
*
* @returns true if a change was made in any
* fragments visibility as part of this call
*/
navigateFragment(index?: number, offset?: number): boolean;
/**
* Navigate to the previous slide fragment.
*
* @returns true if there was a previous fragment,
* false otherwise
*/
prevFragment(): boolean;
/**
* Navigate to the next slide fragment.
*
* @returns true if there was a next fragment,
* false otherwise
*/
nextFragment(): boolean;
/**
* Adds a listener to one of our custom reveal.js events,
* like slidechanged and slidesync.
*
* @param type
* @param listener
* @param useCapture
*/
on: HTMLElement['addEventListener'];
/**
* Unsubscribes from a reveal.js event.
*
* @param type
* @param listener
* @param useCapture
*/
off: HTMLElement['removeEventListener'];
/**
* Legacy event binding methods left in for backwards compatibility
* Adds a listener to one of our custom reveal.js events,
* like slidechanged and slidesync.
* See: {@link on}
*
* @param type
* @param listener
* @param useCapture
*/
addEventListener: HTMLElement['addEventListener'];
/**
* Legacy event binding methods left in for backwards compatibility
* Unsubscribes from a reveal.js event.
* See: {@link off}
*
* @param type
* @param listener
* @param useCapture
*/
removeEventListener: HTMLElement['removeEventListener'];
/**
* Applies JavaScript-controlled layout rules to the
* presentation.
*/
layout(): void;
/**
* Randomly shuffles all slides in the deck.
*/
shuffle(slides?: HTMLElement[]): void;
/**
* Determine what available routes there are for navigation.
*
* @param params - If includeFragments is set, a route will be considered
* available if either a slide OR a fragment is available in the given direction
*
* @returns Available route {left, right, up, down}
*/
availableRoutes(params?: { includeFragments?: boolean }): {
down: boolean;
left: boolean;
right: boolean;
up: boolean;
};
/**
* Returns an object describing the available fragment
* directions.
*
* @returns Available fragments {prev, next}
*/
availableFragments(): { prev: boolean; next: boolean };
/**
* Open or close help overlay window.
*
* @param override - Flag which overrides the
* toggle logic and forcibly sets the desired state. True means
* help is open, false means it's closed.
*/
toggleHelp(override?: boolean): void;
/**
* Toggles the slide overview mode on and off.
*
* @param override - Flag which overrides the
* toggle logic and forcibly sets the desired state. True means
* overview is open, false means it's closed.
*/
toggleOverview(override?: boolean): void;
/**
* Toggles the paused mode on and off.
*
* @param override - Flag which overrides the
* toggle logic and forcibly sets the desired state.
*/
togglePause(override?: boolean): void;
/**
* Toggles the auto slide mode on and off.
*
* @param override - Flag which sets the desired state.
* True means autoplay starts, false means it stops.
*/
toggleAutoSlide(override?: boolean): void;
/**
* @returns true if we're currently on the first slide in
* the presentation.
*/
isFirstSlide(): boolean;
/**
* @returns Returns true if we're currently on the last slide in
* the presentation. If the last slide is a stack, we only
* consider this the last slide if it's at the end of the
* stack.
*/
isLastSlide(): boolean;
/**
* @returns true if we're on the last slide in the current
* vertical stack.
*/
isLastVerticalSlide(): boolean;
/**
* Checks if the current or specified slide is vertical
* (nested within another slide).
*
* @param slide - the slide to check orientation of. Defaults to the current slide.
* @return true if the current or specified slide is vertical
*/
isVerticalSlide(slide?: HTMLElement): boolean;
/**
* @returns true if we are currently in the paused mode.
*/
isPaused(): boolean;
/**
* @returns true if the auto slide mode is currently on.
*/
isAutoSliding(): boolean;
/**
* @returns true if this presentation is running inside of
* the speaker notes window.
*/
isSpeakerNotes(): boolean;
/**
* @returns true if the overview is active, false otherwise
*/
isOverview(): boolean;
/**
* Checks if the presentation is focused
*
* @returns true if the it is focused, false otherwise
*/
isFocused(): boolean;
/**
* Checks if this reveal.js instance is being used to print a PDF.
*
* @returns true if being used to print a PDF, false otherwise
*/
isPrintingPDF(): boolean;
/**
* Checks if reveal.js has been loaded and is ready for use
*
* @returns true if reveal.js is ready for use, false otherwise
*/
isReady(): boolean;
/**
* Called when the given slide is within the configured view
* distance. Shows the slide element and loads any content
* that is set to load lazily (data-src).
*
* @param slide - Slide to show
*/
loadSlide(slide: HTMLElement, options?: { excludeIframes?: boolean }): void;
/**
* Unloads and hides the given slide. This is called when the
* slide is moved outside of the configured view distance.
*
* @param slide
*/
unloadSlide(slide: HTMLElement): void;
/**
* Opens a preview window for the target URL.
*
* @param url - url for preview iframe src
*/
showPreview(url: string): void;
/**
* Closes any currently open overlay.
*/
hidePreview(): void;
/**
* Binds all internal event listeners.
*/
addEventListeners(): void;
/**
* Unbinds all internal event listeners.
*/
removeEventListeners(): void;
/**
* Dispatches an event of the specified type from the
* reveal DOM element.
*/
dispatchEvent({
target,
type,
data,
bubbles,
}: {
/** `revealElement` by default */
target?: HTMLElement;
type: string;
data?: unknown;
bubbles?: boolean;
}): Event;
/**
* Retrieves the current state of the presentation as
* an object. This state can then be restored at any
* time.
*
* @returns The current state - {indexh, indexv, indexf, paused, overview}
*/
getState(): RevealState;
/**
* Restores the presentation to the given state.
*
* @param object - state as generated by getState()
* @see {@link getState} generates the parameter `state`
*/
setState(object: RevealState): void;
/**
* Returns a value ranging from 0-1 that represents
* how far into the presentation we have navigated.
*
* @returns a value ranging from 0-1 that represents
* how far into the presentation we have navigated.
*/
getProgress(): number;
/**
* Retrieves the h/v location and fragment of the current,
* or specified, slide.
*
* @param slide - if specified, the returned index will
* be for this slide rather than the currently active one
*
* @return h/v location and fragment of the current,
* or specified, slide. {h, v, f}
*/
getIndices(slide?: HTMLElement): { h: number; v: number; f: number };
/**
* Returns an array of objects where each object represents the
* attributes on its respective slide.
*
* @returns an array of objects where each object represents the
* attributes on its respective slide.
*/
getSlidesAttributes(): Record<string, string>[];
/**
* Returns the number of past slides. This can be used as a global
* flattened index for slides.
*
* @param [slide] - The slide we're counting before, defaults to current slide
*
* @returns Past slide count
*/
getSlidePastCount(slide?: HTMLElement): number;
/**
* Retrieves the total number of slides in this presentation.
*
* @returns the total number of slides in this presentation.
*/
getTotalSlides(): number;
/**
* Returns the slide element matching the specified index.
*
* @param x - slide index
* @param [y] - slide index
*
* @returns the slide element matching the specified index
*/
getSlide(x: number, y?: number): HTMLElement | undefined;
/**
* Returns the previous slide element, may be null
*
* @returns the previous slide element, may be null
*/
getPreviousSlide(): HTMLElement | null;
/**
* Returns the current slide element
*
* @returns the current slide element
*/
getCurrentSlide(): HTMLElement;
/**
* Returns the background element for the given slide.
* All slides, even the ones with no background properties
* defined, have a background element so as long as the
* index is valid an element will be returned.
*
* @param element A slide
* @returns the background element for the given slide
*/
getSlideBackground(element: HTMLElement): HTMLElement | undefined;
/**
* Returns the background element for the given slide.
* All slides, even the ones with no background properties
* defined, have a background element so as long as the
* index is valid an element will be returned.
*
* @param x - Horizontal background index OR a slide
* HTML element
* @param [y] - Vertical background index
* @returns the background element for the given slide
*/
getSlideBackground(x: number, y?: number): HTMLElement | undefined;
/**
* Retrieves the speaker notes from a slide. Notes can be
* defined in two ways:
* 1. As a data-notes attribute on the slide <section>
* 2. As an <aside class="notes"> inside of the slide
*
* @param [slide] - defaults to current slide
* @returns the speaker notes from a slide
*/
getSlideNotes(slide?: HTMLElement): string | null;
/**
* Retrieves all slides in this presentation.
*
* @returns all slides in this presentation
*/
getSlides(): HTMLElement[];
/**
* Returns a list of all horizontal slides in the deck. Each
* vertical stack is included as one horizontal slide in the
* resulting array.
*
* @returns a list of all horizontal slides in the deck
*/
getHorizontalSlides(): HTMLElement[];
/**
* Returns all vertical slides that exist within this deck.
*
* @returns all vertical slides that exist within this deck
*/
getVerticalSlides(): HTMLElement[];
/**
* Returns true if there are at least two horizontal slides.
*
* @returns true if there are at least two horizontal slides
*/
hasHorizontalSlides(): boolean;
/**
* Returns true if there are at least two vertical slides.
*
* @returns true if there are at least two vertical slides
*/
hasVerticalSlides(): boolean;
/**
* Checks if the deck has navigated on either axis at least once
*
* @returns true if the deck has navigated on either horizontal axis
* at least once
*/
hasNavigatedHorizontally(): boolean;
/**
* Checks if the deck has navigated on either axis at least once
*
* @returns true if the deck has navigated on either vertically axis
* at least once
*/
hasNavigatedVertically(): boolean;
/**
* Add a custom key binding with optional description to
* be added to the help screen.
*
* @param binding
* @param callback
*/
addKeyBinding(
keyCode: number | { keyCode: number; key: string; description: string },
callback: string | ((event: KeyboardEvent) => void)
): void;
/**
* Removes the specified custom key binding.
*
* @param keyCode
*/
removeKeyBinding(keyCode: number): void;
/**
* Programmatically triggers a keyboard event
*
* @param keyCode
*/
triggerKey(keyCode: number): void;
/**
* Registers a new shortcut to include in the help overlay
*
* @param key
* @param value
*/
registerKeyboardShortcut(key: string, value: string): void;
/**
* Calculates the computed pixel size of our slides. These
* values are based on the width and height configuration
* options.
*
* @param [presentationWidth=dom.wrapper.offsetWidth]
* @param [presentationHeight=dom.wrapper.offsetHeight]
* @returns the computed pixel size of the slides
*/
getComputedSlideSize(
presentationWidth?: number,
presentationHeight?: number
): ComputedSlideSize;
/**
* Returns the current scale of the presentation content
*
* @returns the current scale of the presentation content
*/
getScale(): number;
/**
* Returns the current configuration object
*
* @returns the current configuration object
*/
getConfig(): RevealConfig;
/**
* Returns a key:value hash of all query params.
*
* @returns a key:value hash of all query params
*/
getQueryHash(): Record<string, string>;
/**
* Return a hash URL that will resolve to the given slide location.
*
* @param slide - the slide to link to
* @returns a hash URL that will resolve to the given slide location
*/
getSlidePath(slide?: HTMLElement): string;
/**
* @returns reveal.js DOM element
*/
getRevealElement(): HTMLElement | null;
/**
* @returns reveal.js DOM element
*/
getSlidesElement(): HTMLElement | null;
/**
* @returns reveal.js DOM element
*/
getViewportElement(): HTMLElement | null;
/**
* @returns reveal.js DOM element
*/
getBackgroundsElement(): HTMLDivElement | undefined;
/**
* Registers a new plugin with this reveal.js instance.
*
* reveal.js waits for all registered plugins to initialize
* before considering itself ready, as long as the plugin
* is registered before calling `Reveal.initialize()`.
*
* @param plugin
*/
registerPlugin(plugin: RevealPlugin): void;
/**
* Checks if a specific plugin has been registered.
*
* @param id - unique plugin identifier
* @returns true if a specific plugin has been registered.
*/
hasPlugin(id: string): boolean;
/**
* Returns the specific plugin instance, if a plugin
* with the given ID has been registered.
*
* @param id - unique plugin identifier
* @returns plugin instance
*/
getPlugin(id: string): RevealPlugin | undefined;
/**
* @returns id:plugin hash of all plugins
*/
getPlugins(): Record<string, RevealPlugin>;
}
/**
* Options for navigation
*/
export interface NavigateParams {
skipFragments?: boolean;
}
export type NavigationFunction = (params?: NavigateParams) => void;
/**
* Multiplex configuration
*
* @see {@link https://github.com/reveal/multiplex}
*/
export interface MultiplexConfig {
// Obtained from the socket.io server. Gives this (the master) control of the presentation
secret: string | null;
// Obtained from the socket.io server
id: string;
// Location of socket.io server
url: string;
}
/**
* Reveal Dependency
*
* @see {@link https://revealjs.com/plugins/#dependencies}
*/
export interface RevealDependency {
src: string;
async?: boolean;
callback?: () => void;
condition?: () => boolean;
}
export interface ComputedSlideSize {
width: number;
height: number;
presentationWidth: number;
presentationHeight: number;
}
export interface RevealState {
indexh: number;
indexv: number;
indexf: number;
paused: boolean;
overview: boolean;
/**
* URL of an iframe being previewed
*/
previewIframe?: string;
/**
* URL of an image being previewed
*/
previewImage?: string;
/**
* URL of a video being previewed
*/
previewVideo?: string;
/**
* Fit mode of the previewed media
*/
previewFit?: 'none' | 'scale-down' | 'contain' | 'cover';
}
export interface SlideSyncEvent extends Event {
slide: HTMLElement;
}
// NOTE: it is possible to extend type definitions depend on the plugin
/**
* Reveal Plugin
*
* @see {@link https://revealjs.com/creating-plugins/}
*/
export interface RevealPlugin {
id: string;
init?(reveal: RevealApi): void | Promise<void>;
destroy?(): void;
}
export type RevealPluginFactory = () => RevealPlugin;

View File

@@ -1,42 +1,41 @@
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 defaultConfig from './config.js'
import * as Util from './utils/util.js'
import * as Device from './utils/device.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'
import * as Device from './utils/device'
import {
SLIDES_SELECTOR,
HORIZONTAL_SLIDES_SELECTOR,
VERTICAL_SLIDES_SELECTOR,
POST_MESSAGE_METHOD_BLACKLIST
} from './utils/constants.js'
// The reveal.js version
export const VERSION = '5.2.1';
} from './utils/constants'
import { version as VERSION } from '../package.json';
export { VERSION };
/**
* reveal.js
* https://revealjs.com
* MIT licensed
*
* Copyright (C) 2011-2022 Hakim El Hattab, https://hakim.se
* Copyright (C) 2011-2026 Hakim El Hattab, https://hakim.se
*/
export default function( revealElement, options ) {
@@ -394,7 +393,7 @@ export default function( revealElement, options ) {
// Text node
if( node.nodeType === 3 ) {
text += node.textContent;
text += node.textContent.trim();
}
// Element node
else if( node.nodeType === 1 ) {
@@ -403,10 +402,25 @@ export default function( revealElement, options ) {
let isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
if( isAriaHidden !== 'true' && !isDisplayHidden ) {
// Capture alt text from img and video elements
if( node.tagName === 'IMG' || node.tagName === 'VIDEO' ) {
let altText = node.getAttribute( 'alt' );
if( altText ) {
text += ensurePunctuation( altText );
}
}
Array.from( node.childNodes ).forEach( child => {
text += getStatusText( child );
} );
// Add period after block-level text elements to improve
// screen reader experience
const textElements = ['P', 'DIV', 'UL', 'OL', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE'];
if( textElements.includes( node.tagName ) && text.trim() !== '' ) {
text = ensurePunctuation( text );
}
}
}
@@ -417,6 +431,22 @@ export default function( revealElement, options ) {
}
/**
* Ensures text ends with proper punctuation by adding a period
* if it doesn't already end with punctuation.
*/
function ensurePunctuation( text ) {
const trimmedText = text.trim();
if( trimmedText === '' ) {
return text;
}
return !/[.!?]$/.test(trimmedText) ? trimmedText + '.' : trimmedText;
}
/**
* This is an unfortunate necessity. Some actions such as
* an input field being focused in an iframe or using the
@@ -1377,6 +1407,7 @@ export default function( revealElement, options ) {
}
if( slideChanged ) {
slideContent.afterSlideChanged();
dispatchSlideChanged( origin );
}
@@ -1470,6 +1501,8 @@ export default function( revealElement, options ) {
// Start or stop embedded content like videos and iframes
if( slideChanged ) {
slideContent.afterSlideChanged();
if( previousSlide ) {
slideContent.stopEmbeddedContent( previousSlide );
slideContent.stopEmbeddedContent( previousSlide.slideBackgroundElement );
@@ -1517,6 +1550,14 @@ export default function( revealElement, options ) {
fragments.sortAll();
}
// Re-apply slide state classes for the current indices.
// This ensures dynamically inserted/removed slides receive
// proper past/present/future classes on sync.
if( typeof indexh !== 'undefined' ) {
indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, indexh );
indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, indexv );
}
controls.update();
progress.update();
@@ -1541,6 +1582,8 @@ export default function( revealElement, options ) {
overview.layout();
}
dispatchEvent({ type: 'sync' });
}
/**
@@ -1563,6 +1606,13 @@ export default function( revealElement, options ) {
backgrounds.update();
notes.update();
dispatchEvent({
type: 'slidesync',
data: {
slide
}
});
}
/**
@@ -1782,14 +1832,16 @@ export default function( revealElement, options ) {
if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
const isOverview = overview.isActive();
// The number of steps away from the present slide that will
// be visible
let viewDistance = overview.isActive() ? 10 : config.viewDistance;
let viewDistance = isOverview ? 10 : config.viewDistance;
// Shorten the view distance on devices that typically have
// less resources
if( Device.isMobile ) {
viewDistance = overview.isActive() ? 6 : config.mobileViewDistance;
viewDistance = isOverview ? 6 : config.mobileViewDistance;
}
// All slides need to be visible when exporting to PDF
@@ -1822,7 +1874,7 @@ export default function( revealElement, options ) {
if( verticalSlidesLength ) {
let oy = getPreviousVerticalIndex( horizontalSlide );
let oy = isOverview ? 0 : getPreviousVerticalIndex( horizontalSlide );
for( let y = 0; y < verticalSlidesLength; y++ ) {
let verticalSlide = verticalSlides[y];
@@ -2845,6 +2897,9 @@ export default function( revealElement, options ) {
getComputedSlideSize,
setCurrentScrollPage,
// Allows for manually removing slides prior to reveal.js initialization
removeHiddenSlides,
// Returns the current scale of the presentation content
getScale: () => scale,

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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';

View File

@@ -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 );

View File

@@ -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);

View File

@@ -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 );
}

View File

@@ -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);
}
};

View File

@@ -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()}`
);
}

View File

@@ -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()}`);
};