diff --git a/.jshintrc b/.jshintrc
index 4159bce..24eaf85 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -5,6 +5,7 @@
"boss": true,
"browser": true,
"curly": true,
+ "esversion": 6,
"eqeqeq": true,
"eqnull": true,
"expr": true,
diff --git a/build.js b/build.js
index 79886a6..43d8afd 100644
--- a/build.js
+++ b/build.js
@@ -1,21 +1,34 @@
var buildify = require('buildify');
+
buildify()
.load('src/impress.js')
// Libraries from src/lib
.concat(['src/lib/gc.js'])
.concat(['src/lib/util.js'])
// Plugins from src/plugins
- .concat(['src/plugins/goto/goto.js',
+ .concat(['src/plugins/autoplay/autoplay.js',
+ 'src/plugins/blackout/blackout.js',
+ 'src/plugins/extras/extras.js',
+ 'src/plugins/form/form.js',
+ 'src/plugins/goto/goto.js',
+ 'src/plugins/help/help.js',
+ 'src/plugins/impressConsole/impressConsole.js',
'src/plugins/mobile/mobile.js',
+ 'src/plugins/mouse-timeout/mouse-timeout.js',
'src/plugins/navigation/navigation.js',
+ 'src/plugins/navigation-ui/navigation-ui.js',
+ 'src/plugins/progress/progress.js',
'src/plugins/rel/rel.js',
'src/plugins/resize/resize.js',
+ 'src/plugins/skip/skip.js',
'src/plugins/stop/stop.js',
- 'src/plugins/touch/touch.js'])
+ 'src/plugins/substep/substep.js',
+ 'src/plugins/touch/touch.js',
+ 'src/plugins/toolbar/toolbar.js'])
.save('js/impress.js');
/*
* Disabled until uglify supports ES6: https://github.com/mishoo/UglifyJS2/issues/448
.uglify()
.save('js/impress.min.js');
-*/
\ No newline at end of file
+*/
diff --git a/js/impress.js b/js/impress.js
index 888b6e7..4449278 100644
--- a/js/impress.js
+++ b/js/impress.js
@@ -1251,6 +1251,380 @@
} )( document, window );
+/**
+ * Autoplay plugin - Automatically advance slideshow after N seconds
+ *
+ * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi
+ * Released under the MIT license.
+ */
+/* global clearTimeout, setTimeout, document */
+
+( function( document ) {
+ "use strict";
+
+ var autoplayDefault = 0;
+ var currentStepTimeout = 0;
+ var api = null;
+ var timeoutHandle = null;
+ var root = null;
+ var util;
+
+ // On impress:init, check whether there is a default setting, as well as
+ // handle step-1.
+ document.addEventListener( "impress:init", function( event ) {
+ util = event.detail.api.lib.util;
+
+ // Getting API from event data instead of global impress().init().
+ // You don't even need to know what is the id of the root element
+ // or anything. `impress:init` event data gives you everything you
+ // need to control the presentation that was just initialized.
+ api = event.detail.api;
+ root = event.target;
+
+ // Element attributes starting with "data-", become available under
+ // element.dataset. In addition hyphenized words become camelCased.
+ var data = root.dataset;
+
+ if ( data.autoplay ) {
+ autoplayDefault = util.toNumber( data.autoplay, 0 );
+ }
+
+ var toolbar = document.querySelector( "#impress-toolbar" );
+ if ( toolbar ) {
+ addToolbarButton( toolbar );
+ }
+
+ api.lib.gc.addCallback( function() {
+ clearTimeout( timeoutHandle );
+ } );
+
+ // Note that right after impress:init event, also impress:stepenter is
+ // triggered for the first slide, so that's where code flow continues.
+ }, false );
+
+ // If default autoplay time was defined in the presentation root, or
+ // in this step, set timeout.
+ var reloadTimeout = function( event ) {
+ var step = event.target;
+ currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );
+ if ( status === "paused" ) {
+ setAutoplayTimeout( 0 );
+ } else {
+ setAutoplayTimeout( currentStepTimeout );
+ }
+ };
+
+ document.addEventListener( "impress:stepenter", function( event ) {
+ reloadTimeout( event );
+ }, false );
+
+ document.addEventListener( "impress:substep:stepleaveaborted", function( event ) {
+ reloadTimeout( event );
+ }, false );
+
+ /**
+ * Set timeout after which we move to next() step.
+ */
+ var setAutoplayTimeout = function( timeout ) {
+ if ( timeoutHandle ) {
+ clearTimeout( timeoutHandle );
+ }
+
+ if ( timeout > 0 ) {
+ timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );
+ }
+ setButtonText();
+ };
+
+ /*** Toolbar plugin integration *******************************************/
+ var status = "not clicked";
+ var toolbarButton = null;
+
+ // Copied from core impress.js. Good candidate for moving to a utilities collection.
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( "CustomEvent" );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ var makeDomElement = function( html ) {
+ var tempDiv = document.createElement( "div" );
+ tempDiv.innerHTML = html;
+ return tempDiv.firstChild;
+ };
+
+ var toggleStatus = function() {
+ if ( currentStepTimeout > 0 && status !== "paused" ) {
+ status = "paused";
+ } else {
+ status = "playing";
+ }
+ };
+
+ var getButtonText = function() {
+ if ( currentStepTimeout > 0 && status !== "paused" ) {
+ return "||"; // Pause
+ } else {
+ return "▶"; // Play
+ }
+ };
+
+ var setButtonText = function() {
+ if ( toolbarButton ) {
+
+ // Keep button size the same even if label content is changing
+ var buttonWidth = toolbarButton.offsetWidth;
+ var buttonHeight = toolbarButton.offsetHeight;
+ toolbarButton.innerHTML = getButtonText();
+ if ( !toolbarButton.style.width ) {
+ toolbarButton.style.width = buttonWidth + "px";
+ }
+ if ( !toolbarButton.style.height ) {
+ toolbarButton.style.height = buttonHeight + "px";
+ }
+ }
+ };
+
+ var addToolbarButton = function( toolbar ) {
+ var html = '"; // jshint ignore:line
+ toolbarButton = makeDomElement( html );
+ toolbarButton.addEventListener( "click", function() {
+ toggleStatus();
+ if ( status === "playing" ) {
+ if ( autoplayDefault === 0 ) {
+ autoplayDefault = 7;
+ }
+ if ( currentStepTimeout === 0 ) {
+ currentStepTimeout = autoplayDefault;
+ }
+ setAutoplayTimeout( currentStepTimeout );
+ } else if ( status === "paused" ) {
+ setAutoplayTimeout( 0 );
+ }
+ } );
+
+ triggerEvent( toolbar, "impress:toolbar:appendChild",
+ { group: 10, element: toolbarButton } );
+ };
+
+} )( document );
+
+/**
+ * Blackout plugin
+ *
+ * Press Ctrl+b to hide all slides, and Ctrl+b again to show them.
+ * Also navigating to a different slide will show them again (impress:stepleave).
+ *
+ * Copyright 2014 @Strikeskids
+ * Released under the MIT license.
+ */
+/* global document */
+
+( function( document ) {
+ "use strict";
+
+ var canvas = null;
+ var blackedOut = false;
+
+ // While waiting for a shared library of utilities, copying these 2 from main impress.js
+ var css = function( el, props ) {
+ var key, pkey;
+ for ( key in props ) {
+ if ( props.hasOwnProperty( key ) ) {
+ pkey = pfx( key );
+ if ( pkey !== null ) {
+ el.style[ pkey ] = props[ key ];
+ }
+ }
+ }
+ return el;
+ };
+
+ var pfx = ( function() {
+
+ var style = document.createElement( "dummy" ).style,
+ prefixes = "Webkit Moz O ms Khtml".split( " " ),
+ memory = {};
+
+ return function( prop ) {
+ if ( typeof memory[ prop ] === "undefined" ) {
+
+ var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
+ props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
+
+ memory[ prop ] = null;
+ for ( var i in props ) {
+ if ( style[ props[ i ] ] !== undefined ) {
+ memory[ prop ] = props[ i ];
+ break;
+ }
+ }
+
+ }
+
+ return memory[ prop ];
+ };
+
+ } )();
+
+ var removeBlackout = function() {
+ if ( blackedOut ) {
+ css( canvas, {
+ display: "block"
+ } );
+ blackedOut = false;
+ }
+ };
+
+ var blackout = function() {
+ if ( blackedOut ) {
+ removeBlackout();
+ } else {
+ css( canvas, {
+ display: ( blackedOut = !blackedOut ) ? "none" : "block"
+ } );
+ blackedOut = true;
+ }
+ };
+
+ // Wait for impress.js to be initialized
+ document.addEventListener( "impress:init", function( event ) {
+ var api = event.detail.api;
+ var root = event.target;
+ canvas = root.firstElementChild;
+ var gc = api.lib.gc;
+
+ gc.addEventListener( document, "keydown", function( event ) {
+ if ( event.ctrlKey && event.keyCode === 66 ) {
+ event.preventDefault();
+ if ( !blackedOut ) {
+ blackout();
+ } else {
+
+ // Note: This doesn't work on Firefox. It will set display:block,
+ // but slides only become visible again upon next transition, which
+ // forces some kind of redraw. Works as intended on Chrome.
+ removeBlackout();
+ }
+ }
+ }, false );
+
+ gc.addEventListener( document, "keyup", function( event ) {
+ if ( event.ctrlKey && event.keyCode === 66 ) {
+ event.preventDefault();
+ }
+ }, false );
+
+ }, false );
+
+ document.addEventListener( "impress:stepleave", function() {
+ removeBlackout();
+ }, false );
+
+} )( document );
+
+
+/**
+ * Extras Plugin
+ *
+ * This plugin performs initialization (like calling mermaid.initialize())
+ * for the extras/ plugins if they are loaded into a presentation.
+ *
+ * See README.md for details.
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global markdown, hljs, mermaid, impress, document, window */
+
+( function( document, window ) {
+ "use strict";
+
+ var preInit = function() {
+ if ( window.markdown ) {
+
+ // Unlike the other extras, Markdown.js doesn't by default do anything in
+ // particular. We do it ourselves here.
+ // In addition, we use "-----" as a delimiter for new slide.
+
+ // Query all .markdown elements and translate to HTML
+ var markdownDivs = document.querySelectorAll( ".markdown" );
+ for ( var idx = 0; idx < markdownDivs.length; idx++ ) {
+ var element = markdownDivs[ idx ];
+
+ var slides = element.textContent.split( /^-----$/m );
+ var i = slides.length - 1;
+ element.innerHTML = markdown.toHTML( slides[ i ] );
+
+ // If there's an id, unset it for last, and all other, elements,
+ // and then set it for the first.
+ var id = null;
+ if ( element.id ) {
+ id = element.id;
+ element.id = "";
+ }
+ i--;
+ while ( i >= 0 ) {
+ var newElement = element.cloneNode( false );
+ newElement.innerHTML = markdown.toHTML( slides[ i ] );
+ element.parentNode.insertBefore( newElement, element );
+ element = newElement;
+ i--;
+ }
+ if ( id !== null ) {
+ element.id = id;
+ }
+ }
+ } // Markdown
+
+ if ( window.hljs ) {
+ hljs.initHighlightingOnLoad();
+ }
+
+ if ( window.mermaid ) {
+ mermaid.initialize( { startOnLoad:true } );
+ }
+ };
+
+ // Register the plugin to be called in pre-init phase
+ // Note: Markdown.js should run early/first, because it creates new div elements.
+ // So add this with a lower-than-default weight.
+ impress.addPreInitPlugin( preInit, 1 );
+
+} )( document, window );
+
+
+/**
+ * Form support
+ *
+ * Functionality to better support use of input, textarea, button... elements in a presentation.
+ *
+ * Currently this does only one single thing: On impress:stepleave, de-focus any potentially active
+ * element. This is to prevent the focus from being left in a form element that is no longer visible
+ * in the window, and user therefore typing garbage into the form.
+ *
+ * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and
+ * in particular the navigation plugin, unfortunately must fully take control of the tab key,
+ * otherwise a user could cause the browser to scroll to a link or button that's not on the current
+ * step. However, it could be possible to allow tab navigation between form elements, as long as
+ * they are on the active step. This is a topic for further study.
+ *
+ * Copyright 2016 Henrik Ingo
+ * MIT License
+ */
+/* global document */
+( function( document ) {
+ "use strict";
+
+ document.addEventListener( "impress:stepleave", function() {
+ document.activeElement.blur();
+ }, false );
+
+} )( document );
+
+
/**
* Goto Plugin
*
@@ -1426,6 +1800,877 @@
} )( document, window );
+/**
+ * Help popup plugin
+ *
+ * Example:
+ *
+ *
+ *
+ *
+ * For developers:
+ *
+ * Typical use for this plugin, is for plugins that support some keypress, to add a line
+ * to the help popup produced by this plugin. For example "P: Presenter console".
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global window, document */
+
+( function( document, window ) {
+ "use strict";
+ var rows = [];
+ var timeoutHandle;
+
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( "CustomEvent" );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ var renderHelpDiv = function() {
+ var helpDiv = document.getElementById( "impress-help" );
+ if ( helpDiv ) {
+ var html = [];
+ for ( var row in rows ) {
+ for ( var arrayItem in row ) {
+ html.push( rows[ row ][ arrayItem ] );
+ }
+ }
+ if ( html ) {
+ helpDiv.innerHTML = "
\n" + html.join( "\n" ) + "
\n";
+ }
+ }
+ };
+
+ var toggleHelp = function() {
+ var helpDiv = document.getElementById( "impress-help" );
+ if ( !helpDiv ) {
+ return;
+ }
+
+ if ( helpDiv.style.display === "block" ) {
+ helpDiv.style.display = "none";
+ } else {
+ helpDiv.style.display = "block";
+ window.clearTimeout( timeoutHandle );
+ }
+ };
+
+ document.addEventListener( "keyup", function( event ) {
+
+ // Check that event target is html or body element.
+ if ( event.target.nodeName === "BODY" || event.target.nodeName === "HTML" ) {
+ if ( event.keyCode === 72 ) { // "h"
+ event.preventDefault();
+ toggleHelp();
+ }
+ }
+ }, false );
+
+ // API
+ // Other plugins can add help texts, typically if they support an action on a keypress.
+ /**
+ * Add a help text to the help popup.
+ *
+ * :param: e.detail.command Example: "H"
+ * :param: e.detail.text Example: "Show this help."
+ * :param: e.detail.row Row index from 0 to 9 where to place this help text. Example: 0
+ */
+ document.addEventListener( "impress:help:add", function( e ) {
+
+ // The idea is for the sender of the event to supply a unique row index, used for sorting.
+ // But just in case two plugins would ever use the same row index, we wrap each row into
+ // its own array. If there are more than one entry for the same index, they are shown in
+ // first come, first serve ordering.
+ var rowIndex = e.detail.row;
+ if ( typeof rows[ rowIndex ] !== "object" || !rows[ rowIndex ].isArray ) {
+ rows[ rowIndex ] = [];
+ }
+ rows[ e.detail.row ].push( "
" + e.detail.command + "
" +
+ e.detail.text + "
" );
+ renderHelpDiv();
+ } );
+
+ document.addEventListener( "impress:init", function( e ) {
+ renderHelpDiv();
+
+ // At start, show the help for 7 seconds.
+ var helpDiv = document.getElementById( "impress-help" );
+ if ( helpDiv ) {
+ helpDiv.style.display = "block";
+ timeoutHandle = window.setTimeout( function() {
+ var helpDiv = document.getElementById( "impress-help" );
+ helpDiv.style.display = "none";
+ }, 7000 );
+
+ // Regster callback to empty the help div on teardown
+ var api = e.detail.api;
+ api.lib.gc.addCallback( function() {
+ window.clearTimeout( timeoutHandle );
+ helpDiv.style.display = "";
+ helpDiv.innerHTML = "";
+ rows = [];
+ } );
+ }
+
+ // Use our own API to register the help text for "h"
+ triggerEvent( document, "impress:help:add",
+ { command: "H", text: "Show this help", row: 0 } );
+ } );
+
+} )( document, window );
+
+
+/**
+ * Adds a presenter console to impress.js
+ *
+ * MIT Licensed, see license.txt.
+ *
+ * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)
+ *
+ * version: 1.3-dev
+ *
+ */
+
+// This file contains so much HTML, that we will just respectfully disagree about js
+/* jshint quotmark:single */
+/* global navigator, top, setInterval, clearInterval, document, window */
+
+( function( document, window ) {
+ 'use strict';
+
+ // TODO: Move this to src/lib/util.js
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( 'CustomEvent' );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ // Create Language object depending on browsers language setting
+ var lang;
+ switch ( navigator.language ) {
+ case 'de':
+ lang = {
+ 'noNotes': '
+ if ( root.dataset.consoleAutolaunch === 'true' ) {
+ window.open();
+ }
+ };
+
+ var init = function( cssConsole, cssIframe ) {
+ if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&
+ ( cssIframe === undefined || cssIframe === cssFileIframeOldDefault ) ) {
+ window.console.log( 'impressConsole.init() is deprecated. ' +
+ 'impressConsole is now initialized automatically when you ' +
+ 'call impress().init().' );
+ }
+ _init( cssConsole, cssIframe );
+ };
+
+ document.addEventListener( 'impress:init', function() {
+ _init();
+
+ // Add 'P' to the help popup
+ triggerEvent( document, 'impress:help:add',
+ { command: 'P', text: 'Presenter console', row: 10 } );
+ } );
+
+ // New API for impress.js plugins is based on using events
+ root.addEventListener( 'impress:console:open', function() {
+ window.open();
+ } );
+
+ /**
+ * Register a key code to an event handler
+ *
+ * :param: event.detail.keyCodes List of key codes
+ * :param: event.detail.handler A function registered as the event handler
+ * :param: event.detail.window The console window to register the keycode in
+ */
+ root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {
+ registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );
+ } );
+
+ // Return the object
+ allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,
+ registerKeyEvent: registerKeyEvent };
+ return allConsoles[ rootId ];
+
+ };
+
+ // Returns a string to be used inline as a css `;
+ };
+
+ impressConsole();
+
+} )( document, window );
+
/**
* Mobile devices support
*
@@ -1516,6 +2761,74 @@
} )( document );
+/**
+ * Mouse timeout plugin
+ *
+ * After 3 seconds of mouse inactivity, add the css class
+ * `body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the
+ * class.
+ *
+ * The use case for this plugin is to use CSS to hide elements from the screen
+ * and only make them visible when the mouse is moved. Examples where this
+ * might be used are: the toolbar from the toolbar plugin, and the mouse cursor
+ * itself.
+ *
+ * Example CSS:
+ *
+ * body.impress-mouse-timeout {
+ * cursor: none;
+ * }
+ * body.impress-mouse-timeout div#impress-toolbar {
+ * display: none;
+ * }
+ *
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global window, document */
+( function( document, window ) {
+ "use strict";
+ var timeout = 3;
+ var timeoutHandle;
+
+ var hide = function() {
+
+ // Mouse is now inactive
+ document.body.classList.add( "impress-mouse-timeout" );
+ };
+
+ var show = function() {
+ if ( timeoutHandle ) {
+ window.clearTimeout( timeoutHandle );
+ }
+
+ // Mouse is now active
+ document.body.classList.remove( "impress-mouse-timeout" );
+
+ // Then set new timeout after which it is considered inactive again
+ timeoutHandle = window.setTimeout( hide, timeout * 1000 );
+ };
+
+ document.addEventListener( "impress:init", function( event ) {
+ var api = event.detail.api;
+ var gc = api.lib.gc;
+ gc.addEventListener( document, "mousemove", show );
+ gc.addEventListener( document, "click", show );
+ gc.addEventListener( document, "touch", show );
+
+ // Set first timeout
+ show();
+
+ // Unset all this on teardown
+ gc.addCallback( function() {
+ window.clearTimeout( timeoutHandle );
+ document.body.classList.remove( "impress-mouse-timeout" );
+ } );
+ }, false );
+
+} )( document, window );
+
/**
* Navigation events plugin
*
@@ -1688,6 +3001,189 @@
} )( document );
+/**
+ * Navigation UI plugin
+ *
+ * This plugin provides UI elements "back", "forward" and a list to select
+ * a specific slide number.
+ *
+ * The navigation controls are added to the toolbar plugin via DOM events. User must enable the
+ * toolbar in a presentation to have them visible.
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+// This file contains so much HTML, that we will just respectfully disagree about js
+/* jshint quotmark:single */
+/* global document */
+
+( function( document ) {
+ 'use strict';
+ var toolbar;
+ var api;
+ var root;
+ var steps;
+ var hideSteps = [];
+ var prev;
+ var select;
+ var next;
+
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( 'CustomEvent' );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ var makeDomElement = function( html ) {
+ var tempDiv = document.createElement( 'div' );
+ tempDiv.innerHTML = html;
+ return tempDiv.firstChild;
+ };
+
+ var selectOptionsHtml = function() {
+ var options = '';
+ for ( var i = 0; i < steps.length; i++ ) {
+
+ // Omit steps that are listed as hidden from select widget
+ if ( hideSteps.indexOf( steps[ i ] ) < 0 ) {
+ options = options + '' + '\n'; // jshint ignore:line
+ }
+ }
+ return options;
+ };
+
+ var addNavigationControls = function( event ) {
+ api = event.detail.api;
+ var gc = api.lib.gc;
+ root = event.target;
+ steps = root.querySelectorAll( '.step' );
+
+ var prevHtml = '';
+ var selectHtml = '';
+ var nextHtml = '';
+
+ prev = makeDomElement( prevHtml );
+ prev.addEventListener( 'click',
+ function() {
+ api.prev();
+ } );
+ select = makeDomElement( selectHtml );
+ select.addEventListener( 'change',
+ function( event ) {
+ api.goto( event.target.value );
+ } );
+ gc.addEventListener( root, 'impress:steprefresh', function( event ) {
+
+ // As impress.js core now allows to dynamically edit the steps, including adding,
+ // removing, and reordering steps, we need to requery and redraw the select list on
+ // every stepenter event.
+ steps = root.querySelectorAll( '.step' );
+ select.innerHTML = '\n' + selectOptionsHtml();
+
+ // Make sure the list always shows the step we're actually on, even if it wasn't
+ // selected from the list
+ select.value = event.target.id;
+ } );
+ next = makeDomElement( nextHtml );
+ next.addEventListener( 'click',
+ function() {
+ api.next();
+ } );
+
+ triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev } );
+ triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: select } );
+ triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: next } );
+
+ };
+
+ // API for not listing given step in the select widget.
+ // For example, if you set class="skip" on some element, you may not want it to show up in the
+ // list either. Otoh we cannot assume that, or anything else, so steps that user wants omitted
+ // must be specifically added with this API call.
+ document.addEventListener( 'impress:navigation-ui:hideStep', function( event ) {
+ hideSteps.push( event.target );
+ if ( select ) {
+ select.innerHTML = selectOptionsHtml();
+ }
+ }, false );
+
+ // Wait for impress.js to be initialized
+ document.addEventListener( 'impress:init', function( event ) {
+ toolbar = document.querySelector( '#impress-toolbar' );
+ if ( toolbar ) {
+ addNavigationControls( event );
+ }
+ }, false );
+
+} )( document );
+
+
+/* global document */
+( function( document ) {
+ "use strict";
+ var root;
+ var stepids = [];
+
+ // Get stepids from the steps under impress root
+ var getSteps = function() {
+ stepids = [];
+ var steps = root.querySelectorAll( ".step" );
+ for ( var i = 0; i < steps.length; i++ )
+ {
+ stepids[ i + 1 ] = steps[ i ].id;
+ }
+ };
+
+ // Wait for impress.js to be initialized
+ document.addEventListener( "impress:init", function( event ) {
+ root = event.target;
+ getSteps();
+ var gc = event.detail.api.lib.gc;
+ gc.addCallback( function() {
+ stepids = [];
+ if ( progressbar ) {
+ progressbar.style.width = "";
+ }
+ if ( progress ) {
+ progress.innerHTML = "";
+ }
+ } );
+ } );
+
+ var progressbar = document.querySelector( "div.impress-progressbar div" );
+ var progress = document.querySelector( "div.impress-progress" );
+
+ if ( null !== progressbar || null !== progress ) {
+ document.addEventListener( "impress:stepleave", function( event ) {
+ updateProgressbar( event.detail.next.id );
+ } );
+
+ document.addEventListener( "impress:steprefresh", function( event ) {
+ getSteps();
+ updateProgressbar( event.target.id );
+ } );
+
+ }
+
+ function updateProgressbar( slideId ) {
+ var slideNumber = stepids.indexOf( slideId );
+ if ( null !== progressbar ) {
+ var width = 100 / ( stepids.length - 1 ) * ( slideNumber );
+ progressbar.style.width = width.toFixed( 2 ) + "%";
+ }
+ if ( null !== progress ) {
+ progress.innerHTML = slideNumber + "/" + ( stepids.length - 1 );
+ }
+ }
+} )( document );
+
/**
* Relative Positioning Plugin
*
@@ -1897,6 +3393,91 @@
} )( document, window );
+/**
+ * Skip Plugin
+ *
+ * Example:
+ *
+ *
+ *
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document, window */
+
+( function( document, window ) {
+ "use strict";
+ var util;
+
+ document.addEventListener( "impress:init", function( event ) {
+ util = event.detail.api.lib.util;
+ }, false );
+
+ var getNextStep = function( el ) {
+ var steps = document.querySelectorAll( ".step" );
+ for ( var i = 0; i < steps.length; i++ ) {
+ if ( steps[ i ] === el ) {
+ if ( i + 1 < steps.length ) {
+ return steps[ i + 1 ];
+ } else {
+ return steps[ 0 ];
+ }
+ }
+ }
+ };
+ var getPrevStep = function( el ) {
+ var steps = document.querySelectorAll( ".step" );
+ for ( var i = steps.length - 1; i >= 0; i-- ) {
+ if ( steps[ i ] === el ) {
+ if ( i - 1 >= 0 ) {
+ return steps[ i - 1 ];
+ } else {
+ return steps[ steps.length - 1 ];
+ }
+ }
+ }
+ };
+
+ var skip = function( event ) {
+ if ( ( !event ) || ( !event.target ) ) {
+ return;
+ }
+
+ if ( event.detail.next.classList.contains( "skip" ) ) {
+ if ( event.detail.reason === "next" ) {
+
+ // Go to the next next step instead
+ event.detail.next = getNextStep( event.detail.next );
+
+ // Recursively call this plugin again, until there's a step not to skip
+ skip( event );
+ } else if ( event.detail.reason === "prev" ) {
+
+ // Go to the previous previous step instead
+ event.detail.next = getPrevStep( event.detail.next );
+ skip( event );
+ }
+
+ // If the new next element has its own transitionDuration, we're responsible for setting
+ // that on the event as well
+ event.detail.transitionDuration = util.toNumber(
+ event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+ );
+ }
+ };
+
+ // Register the plugin to be called in pre-stepleave phase
+ // The weight makes this plugin run early. This is a good thing, because this plugin calls
+ // itself recursively.
+ window.impress.addPreStepLeavePlugin( skip, 1 );
+
+} )( document, window );
+
+
/**
* Stop Plugin
*
@@ -1933,6 +3514,118 @@
} )( document, window );
+/**
+ * Substep Plugin
+ *
+ * Copyright 2017 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document, window */
+
+( function( document, window ) {
+ "use strict";
+
+ // Copied from core impress.js. Good candidate for moving to src/lib/util.js.
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( "CustomEvent" );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ var activeStep = null;
+ document.addEventListener( "impress:stepenter", function( event ) {
+ activeStep = event.target;
+ }, false );
+
+ var substep = function( event ) {
+ if ( ( !event ) || ( !event.target ) ) {
+ return;
+ }
+
+ var step = event.target;
+ var el; // Needed by jshint
+ if ( event.detail.reason === "next" ) {
+ el = showSubstepIfAny( step );
+ if ( el ) {
+
+ // Send a message to others, that we aborted a stepleave event.
+ // Autoplay will reload itself from this, as there won't be a stepenter event now.
+ triggerEvent( step, "impress:substep:stepleaveaborted",
+ { reason: "next", substep: el } );
+
+ // Returning false aborts the stepleave event
+ return false;
+ }
+ }
+ if ( event.detail.reason === "prev" ) {
+ el = hideSubstepIfAny( step );
+ if ( el ) {
+ triggerEvent( step, "impress:substep:stepleaveaborted",
+ { reason: "prev", substep: el } );
+ return false;
+ }
+ }
+ };
+
+ var showSubstepIfAny = function( step ) {
+ var substeps = step.querySelectorAll( ".substep" );
+ var visible = step.querySelectorAll( ".substep-visible" );
+ if ( substeps.length > 0 ) {
+ return showSubstep( substeps, visible );
+ }
+ };
+
+ var showSubstep = function( substeps, visible ) {
+ if ( visible.length < substeps.length ) {
+ var el = substeps[ visible.length ];
+ el.classList.add( "substep-visible" );
+ return el;
+ }
+ };
+
+ var hideSubstepIfAny = function( step ) {
+ var substeps = step.querySelectorAll( ".substep" );
+ var visible = step.querySelectorAll( ".substep-visible" );
+ if ( substeps.length > 0 ) {
+ return hideSubstep( visible );
+ }
+ };
+
+ var hideSubstep = function( visible ) {
+ if ( visible.length > 0 ) {
+ var el = visible[ visible.length - 1 ];
+ el.classList.remove( "substep-visible" );
+ return el;
+ }
+ };
+
+ // Register the plugin to be called in pre-stepleave phase.
+ // The weight makes this plugin run before other preStepLeave plugins.
+ window.impress.addPreStepLeavePlugin( substep, 1 );
+
+ // When entering a step, in particular when re-entering, make sure that all substeps are hidden
+ // at first
+ document.addEventListener( "impress:stepenter", function( event ) {
+ var step = event.target;
+ var visible = step.querySelectorAll( ".substep-visible" );
+ for ( var i = 0; i < visible.length; i++ ) {
+ visible[ i ].classList.remove( "substep-visible" );
+ }
+ }, false );
+
+ // API for others to reveal/hide next substep ////////////////////////////////////////////////
+ document.addEventListener( "impress:substep:show", function() {
+ showSubstepIfAny( activeStep );
+ }, false );
+
+ document.addEventListener( "impress:substep:hide", function() {
+ hideSubstepIfAny( activeStep );
+ }, false );
+
+} )( document, window );
+
+
/**
* Support for swipe and tap on touch devices
*
@@ -2001,3 +3694,161 @@
} );
} )( document, window );
+
+/**
+ * Toolbar plugin
+ *
+ * This plugin provides a generic graphical toolbar. Other plugins that
+ * want to expose a button or other widget, can add those to this toolbar.
+ *
+ * Using a single consolidated toolbar for all GUI widgets makes it easier
+ * to position and style the toolbar rather than having to do that for lots
+ * of different divs.
+ *
+ *
+ * *** For presentation authors: *****************************************
+ *
+ * To add/activate the toolbar in your presentation, add this div:
+ *
+ *
+ *
+ * Styling the toolbar is left to presentation author. Here's an example CSS:
+ *
+ * .impress-enabled div#impress-toolbar {
+ * position: fixed;
+ * right: 1px;
+ * bottom: 1px;
+ * opacity: 0.6;
+ * }
+ * .impress-enabled div#impress-toolbar > span {
+ * margin-right: 10px;
+ * }
+ *
+ * The [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide
+ * the toolbar from sight, and only make it visible when mouse is moved.
+ *
+ * body.impress-mouse-timeout div#impress-toolbar {
+ * display: none;
+ * }
+ *
+ *
+ * *** For plugin authors **********************************************
+ *
+ * To add a button to the toolbar, trigger the `impress:toolbar:appendChild`
+ * or `impress:toolbar:insertBefore` events as appropriate. The detail object
+ * should contain following parameters:
+ *
+ * { group : 1, // integer. Widgets with the same group are grouped inside
+ * // the same element.
+ * html : "", // The html to add.
+ * callback : "mycallback", // Toolbar plugin will trigger event
+ * // `impress:toolbar:added:mycallback` when done.
+ * before: element } // The reference element for an insertBefore() call.
+ *
+ * You should also listen to the `impress:toolbar:added:mycallback` event. At
+ * this point you can find the new widget in the DOM, and for example add an
+ * event listener to it.
+ *
+ * You are free to use any integer for the group. It's ok to leave gaps. It's
+ * ok to co-locate with widgets for another plugin, if you think they belong
+ * together.
+ *
+ * See navigation-ui for an example.
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document */
+
+( function( document ) {
+ "use strict";
+ var toolbar = document.getElementById( "impress-toolbar" );
+ var groups = [];
+
+ /**
+ * Get the span element that is a child of toolbar, identified by index.
+ *
+ * If span element doesn't exist yet, it is created.
+ *
+ * Note: Because of Run-to-completion, this is not a race condition.
+ * https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop#Run-to-completion
+ *
+ * :param: index Method will return the element
+ */
+ var getGroupElement = function( index ) {
+ var id = "impress-toolbar-group-" + index;
+ if ( !groups[ index ] ) {
+ groups[ index ] = document.createElement( "span" );
+ groups[ index ].id = id;
+ var nextIndex = getNextGroupIndex( index );
+ if ( nextIndex === undefined ) {
+ toolbar.appendChild( groups[ index ] );
+ } else {
+ toolbar.insertBefore( groups[ index ], groups[ nextIndex ] );
+ }
+ }
+ return groups[ index ];
+ };
+
+ /**
+ * Get the span element from groups[] that is immediately after given index.
+ *
+ * This can be used to find the reference node for an insertBefore() call.
+ * If no element exists at a larger index, returns undefined. (In this case,
+ * you'd use appendChild() instead.)
+ *
+ * Note that index needn't itself exist in groups[].
+ */
+ var getNextGroupIndex = function( index ) {
+ var i = index + 1;
+ while ( !groups[ i ] && i < groups.length ) {
+ i++;
+ }
+ if ( i < groups.length ) {
+ return i;
+ }
+ };
+
+ // API
+ // Other plugins can add and remove buttons by sending them as events.
+ // In return, toolbar plugin will trigger events when button was added.
+ if ( toolbar ) {
+ /**
+ * Append a widget inside toolbar span element identified by given group index.
+ *
+ * :param: e.detail.group integer specifying the span element where widget will be placed
+ * :param: e.detail.element a dom element to add to the toolbar
+ */
+ toolbar.addEventListener( "impress:toolbar:appendChild", function( e ) {
+ var group = getGroupElement( e.detail.group );
+ group.appendChild( e.detail.element );
+ } );
+
+ /**
+ * Add a widget to toolbar using insertBefore() DOM method.
+ *
+ * :param: e.detail.before the reference dom element, before which new element is added
+ * :param: e.detail.element a dom element to add to the toolbar
+ */
+ toolbar.addEventListener( "impress:toolbar:insertBefore", function( e ) {
+ toolbar.insertBefore( e.detail.element, e.detail.before );
+ } );
+
+ /**
+ * Remove the widget in e.detail.remove.
+ */
+ toolbar.addEventListener( "impress:toolbar:removeWidget", function( e ) {
+ toolbar.removeChild( e.detail.remove );
+ } );
+
+ document.addEventListener( "impress:init", function( event ) {
+ var api = event.detail.api;
+ api.lib.gc.addCallback( function() {
+ toolbar.innerHTML = "";
+ groups = [];
+ } );
+ } );
+ } // If toolbar
+
+} )( document );