aboutsummaryrefslogtreecommitdiff
path: root/js/reveal.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/reveal.js')
-rw-r--r--js/reveal.js235
1 files changed, 189 insertions, 46 deletions
diff --git a/js/reveal.js b/js/reveal.js
index 6f572b0..0eebeb6 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -180,6 +180,13 @@
// - 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,
+
// 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
@@ -319,6 +326,12 @@
// Cached references to DOM elements
dom = {},
+ // A list of registered reveal.js plugins
+ plugins = {},
+
+ // List of asynchronously loaded reveal.js dependencies
+ asyncDependencies = [],
+
// Features supported by the browser, see #checkCapabilities()
features = {},
@@ -422,7 +435,7 @@
// Hide the address bar in mobile browsers
hideAddressBar();
- // Loads the dependencies and continues to #start() once done
+ // Loads dependencies and continues to #start() once done
load();
}
@@ -477,37 +490,22 @@
function load() {
var scripts = [],
- scriptsAsync = [],
- scriptsToPreload = 0;
-
- // Called once synchronous scripts finish loading
- function afterSynchronousScriptsLoaded() {
- // Load asynchronous scripts
- if( scriptsAsync.length ) {
- scriptsAsync.forEach( function( s ) {
- loadScript( s.src, s.callback );
- } );
- }
-
- start();
- }
-
- for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
- var s = config.dependencies[i];
+ scriptsToLoad = 0;
+ config.dependencies.forEach( function( s ) {
// Load if there's no condition or the condition is truthy
if( !s.condition || s.condition() ) {
if( s.async ) {
- scriptsAsync.push( s );
+ asyncDependencies.push( s );
}
else {
scripts.push( s );
}
}
- }
+ } );
if( scripts.length ) {
- scriptsToPreload = scripts.length;
+ scriptsToLoad = scripts.length;
// Load synchronous scripts
scripts.forEach( function( s ) {
@@ -515,22 +513,82 @@
if( typeof s.callback === 'function' ) s.callback();
- if( --scriptsToPreload === 0 ) {
-
- afterSynchronousScriptsLoaded();
-
+ if( --scriptsToLoad === 0 ) {
+ initPlugins();
}
} );
} );
}
else {
- afterSynchronousScriptsLoaded();
+ initPlugins();
}
}
/**
+ * Initializes our plugins and waits for them to be ready
+ * before proceeding.
+ */
+ function initPlugins() {
+
+ var pluginsToInitialize = Object.keys( plugins ).length;
+
+ // If there are no plugins, skip this step
+ if( pluginsToInitialize === 0 ) {
+ loadAsyncDependencies();
+ }
+ // ... otherwise initialize plugins
+ else {
+
+ var afterPlugInitialized = function() {
+ if( --pluginsToInitialize === 0 ) {
+ loadAsyncDependencies();
+ }
+ };
+
+ for( var i in plugins ) {
+
+ var plugin = plugins[i];
+
+ // If the plugin has an 'init' method, invoke it
+ if( typeof plugin.init === 'function' ) {
+ var callback = plugin.init();
+
+ // If the plugin returned a Promise, wait for it
+ if( callback && typeof callback.then === 'function' ) {
+ callback.then( afterPlugInitialized );
+ }
+ else {
+ afterPlugInitialized();
+ }
+ }
+ else {
+ afterPlugInitialized();
+ }
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Loads all async reveal.js dependencies.
+ */
+ function loadAsyncDependencies() {
+
+ if( asyncDependencies.length ) {
+ asyncDependencies.forEach( function( s ) {
+ loadScript( s.src, s.callback );
+ } );
+ }
+
+ start();
+
+ }
+
+ /**
* Loads a JavaScript file from the given URL and executes it.
*
* @param {string} url Address of the .js file to load
@@ -1520,6 +1578,53 @@
}
/**
+ * Registers a new plugin with this reveal.js instance.
+ *
+ * reveal.js waits for all regisered plugins to initialize
+ * before considering itself ready, as long as the plugin
+ * is registered before calling `Reveal.initialize()`.
+ */
+ function registerPlugin( id, plugin ) {
+
+ if( plugins[id] === undefined ) {
+ plugins[id] = plugin;
+
+ // If a plugin is registered after reveal.js is loaded,
+ // initialize it right away
+ if( loaded && typeof plugin.init === 'function' ) {
+ plugin.init();
+ }
+ }
+ else {
+ console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' );
+ }
+
+ }
+
+ /**
+ * Checks if a specific plugin has been registered.
+ *
+ * @param {String} id Unique plugin identifier
+ */
+ function hasPlugin( id ) {
+
+ return !!plugins[id];
+
+ }
+
+ /**
+ * Returns the specific plugin instance, if a plugin
+ * with the given ID has been registered.
+ *
+ * @param {String} id Unique plugin identifier
+ */
+ function getPlugin( id ) {
+
+ return plugins[id];
+
+ }
+
+ /**
* Add a custom key binding with optional description to
* be added to the help screen.
*/
@@ -1837,15 +1942,6 @@
}
/**
- * Check if this instance is being used to print a PDF with fragments.
- */
- function isPrintingPDFFragments() {
-
- return ( /print-pdf-fragments/gi ).test( window.location.search );
-
- }
-
- /**
* Hides the address bar if we're on a mobile device.
*/
function hideAddressBar() {
@@ -2935,6 +3031,9 @@
*/
function syncSlide( slide ) {
+ // Default to the current slide
+ slide = slide || currentSlide;
+
syncBackground( slide );
syncFragments( slide );
@@ -2951,10 +3050,14 @@
* after reveal.js has already initialized.
*
* @param {HTMLElement} slide
+ * @return {Array} a list of the HTML fragments that were synced
*/
function syncFragments( slide ) {
- sortFragments( slide.querySelectorAll( '.fragment' ) );
+ // Default to the current slide
+ slide = slide || currentSlide;
+
+ return sortFragments( slide.querySelectorAll( '.fragment' ) );
}
@@ -3606,6 +3709,26 @@
}
/**
+ * Should the given element be preloaded?
+ * Decides based on local element attributes and global config.
+ *
+ * @param {HTMLElement} element
+ */
+ function shouldPreload( element ) {
+
+ // Prefer an explicit global preload setting
+ var preload = config.preloadIframes;
+
+ // If no global setting is available, fall back on the element's
+ // own preload setting
+ if( typeof preload !== 'boolean' ) {
+ preload = element.hasAttribute( 'data-preload' );
+ }
+
+ return preload;
+ }
+
+ /**
* 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).
@@ -3620,10 +3743,12 @@
slide.style.display = config.display;
// Media elements with data-src attributes
- toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
- element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
- element.setAttribute( 'data-lazy-loaded', '' );
- element.removeAttribute( 'data-src' );
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) {
+ if( element.tagName !== 'IFRAME' || shouldPreload( element ) ) {
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
+ element.setAttribute( 'data-lazy-loaded', '' );
+ element.removeAttribute( 'data-src' );
+ }
} );
// Media elements with <source> children
@@ -3741,7 +3866,7 @@
}
// Reset lazy-loaded media elements with src attributes
- toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src]' ) ).forEach( function( element ) {
+ toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src], iframe[data-lazy-loaded][src]' ) ).forEach( function( element ) {
element.setAttribute( 'data-src', element.getAttribute( 'src' ) );
element.removeAttribute( 'src' );
} );
@@ -4131,7 +4256,7 @@
}
- return pastCount / ( totalCount - 1 );
+ return Math.min( pastCount / ( totalCount - 1 ), 1 );
}
@@ -4224,11 +4349,21 @@
writeURLTimeout = setTimeout( writeURL, delay );
}
else if( currentSlide ) {
+ // If we're configured to push to history OR the history
+ // API is not avaialble.
if( config.history || !window.history ) {
window.location.hash = locationHash();
}
+ // If we're configured to refelct the current slide in the
+ // URL without pushing to history.
else if( config.hash ) {
- window.history.replaceState(null, null, '#' + locationHash());
+ window.history.replaceState( null, null, '#' + locationHash() );
+ }
+ // If history and hash are both disabled, a hash may still
+ // be added to the URL by clicking on a href with a hash
+ // target. Counter this by always removing the hash.
+ else {
+ window.history.replaceState( null, null, window.location.pathname + window.location.search );
}
}
@@ -5811,6 +5946,11 @@
return dom.wrapper || document.querySelector( '.reveal' );
},
+ // Returns a hash with all registered plugins
+ getPlugins: function() {
+ return plugins;
+ },
+
// Returns true if we're currently on the first slide
isFirstSlide: function() {
return ( indexh === 0 && indexv === 0 );
@@ -5861,12 +6001,15 @@
}
},
- // Adds a custom key binding
+ // Adds/remvoes a custom key binding
addKeyBinding: addKeyBinding,
-
- // Removes a custom key binding
removeKeyBinding: removeKeyBinding,
+ // API for registering and retrieving plugins
+ registerPlugin: registerPlugin,
+ hasPlugin: hasPlugin,
+ getPlugin: getPlugin,
+
// Programatically triggers a keyboard event
triggerKey: function( keyCode ) {
onDocumentKeyDown( { keyCode: keyCode } );