diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/reveal.js | 235 |
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 } ); |