· 8 years ago · Jan 22, 2018, 11:20 AM
1/**
2 * impress.js
3 *
4 * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
5 * in modern browsers and inspired by the idea behind prezi.com.
6 *
7 *
8 * Copyright 2011-2012 Bartek Szopka (@bartaz)
9 *
10 * Released under the MIT and GPL Licenses.
11 *
12 * ------------------------------------------------
13 * author: Bartek Szopka
14 * version: 1.0.0-beta1
15 * url: http://bartaz.github.com/impress.js/
16 * source: http://github.com/bartaz/impress.js/
17 */
18
19// You are one of those who like to know how things work inside?
20// Let me show you the cogs that make impress.js run...
21( function( document, window ) {
22 "use strict";
23 var lib;
24
25 // HELPER FUNCTIONS
26
27 // `pfx` is a function that takes a standard CSS property name as a parameter
28 // and returns it's prefixed version valid for current browser it runs in.
29 // The code is heavily inspired by Modernizr http://www.modernizr.com/
30 var pfx = ( function() {
31
32 var style = document.createElement( "dummy" ).style,
33 prefixes = "Webkit Moz O ms Khtml".split( " " ),
34 memory = {};
35
36 return function( prop ) {
37 if ( typeof memory[ prop ] === "undefined" ) {
38
39 var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
40 props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
41
42 memory[ prop ] = null;
43 for ( var i in props ) {
44 if ( style[ props[ i ] ] !== undefined ) {
45 memory[ prop ] = props[ i ];
46 break;
47 }
48 }
49
50 }
51
52 return memory[ prop ];
53 };
54
55 } )();
56
57 var validateOrder = function( order, fallback ) {
58 var validChars = "xyz";
59 var returnStr = "";
60 if ( typeof order === "string" ) {
61 for ( var i in order.split( "" ) ) {
62 if ( validChars.indexOf( order[ i ] >= 0 ) ) {
63 returnStr += order[ i ];
64
65 // Each of x,y,z can be used only once.
66 validChars = validChars.split( order[ i ] ).join( "" );
67 }
68 }
69 }
70 if ( returnStr ) {
71 return returnStr;
72 } else if ( fallback !== undefined ) {
73 return fallback;
74 } else {
75 return "xyz";
76 }
77 };
78
79 // `css` function applies the styles given in `props` object to the element
80 // given as `el`. It runs all property names through `pfx` function to make
81 // sure proper prefixed version of the property is used.
82 var css = function( el, props ) {
83 var key, pkey;
84 for ( key in props ) {
85 if ( props.hasOwnProperty( key ) ) {
86 pkey = pfx( key );
87 if ( pkey !== null ) {
88 el.style[ pkey ] = props[ key ];
89 }
90 }
91 }
92 return el;
93 };
94
95 // `translate` builds a translate transform string for given data.
96 var translate = function( t ) {
97 return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
98 };
99
100 // `rotate` builds a rotate transform string for given data.
101 // By default the rotations are in X Y Z order that can be reverted by passing `true`
102 // as second parameter.
103 var rotate = function( r, revert ) {
104 var order = r.order ? r.order : "xyz";
105 var css = "";
106 var axes = order.split( "" );
107 if ( revert ) {
108 axes = axes.reverse();
109 }
110
111 for ( var i = 0; i < axes.length; i++ ) {
112 css += " rotate" + axes[ i ].toUpperCase() + "(" + r[ axes[ i ] ] + "deg)";
113 }
114 return css;
115 };
116
117 // `scale` builds a scale transform string for given data.
118 var scale = function( s ) {
119 return " scale(" + s + ") ";
120 };
121
122 // `computeWindowScale` counts the scale factor between window size and size
123 // defined for the presentation in the config.
124 var computeWindowScale = function( config ) {
125 var hScale = window.innerHeight / config.height,
126 wScale = window.innerWidth / config.width,
127 scale = hScale > wScale ? wScale : hScale;
128
129 if ( config.maxScale && scale > config.maxScale ) {
130 scale = config.maxScale;
131 }
132
133 if ( config.minScale && scale < config.minScale ) {
134 scale = config.minScale;
135 }
136
137 return scale;
138 };
139
140 // CHECK SUPPORT
141 var body = document.body;
142 var impressSupported =
143
144 // Browser should support CSS 3D transtorms
145 ( pfx( "perspective" ) !== null ) &&
146
147 // And `classList` and `dataset` APIs
148 ( body.classList ) &&
149 ( body.dataset );
150
151 if ( !impressSupported ) {
152
153 // We can't be sure that `classList` is supported
154 body.className += " impress-not-supported ";
155 }
156
157 // GLOBALS AND DEFAULTS
158
159 // This is where the root elements of all impress.js instances will be kept.
160 // Yes, this means you can have more than one instance on a page, but I'm not
161 // sure if it makes any sense in practice ;)
162 var roots = {};
163
164 var preInitPlugins = [];
165 var preStepLeavePlugins = [];
166
167 // Some default config values.
168 var defaults = {
169 width: 1024,
170 height: 768,
171 maxScale: 1,
172 minScale: 0,
173
174 perspective: 1000,
175
176 transitionDuration: 1000
177 };
178
179 // It's just an empty function ... and a useless comment.
180 var empty = function() { return false; };
181
182 // IMPRESS.JS API
183
184 // And that's where interesting things will start to happen.
185 // It's the core `impress` function that returns the impress.js API
186 // for a presentation based on the element with given id ("impress"
187 // by default).
188 var impress = window.impress = function( rootId ) {
189
190 // If impress.js is not supported by the browser return a dummy API
191 // it may not be a perfect solution but we return early and avoid
192 // running code that may use features not implemented in the browser.
193 if ( !impressSupported ) {
194 return {
195 init: empty,
196 goto: empty,
197 prev: empty,
198 next: empty,
199 swipe: empty,
200 tear: empty,
201 lib: {}
202 };
203 }
204
205 rootId = rootId || "impress";
206
207 // If given root is already initialized just return the API
208 if ( roots[ "impress-root-" + rootId ] ) {
209 return roots[ "impress-root-" + rootId ];
210 }
211
212 // The gc library depends on being initialized before we do any changes to DOM.
213 lib = initLibraries( rootId );
214
215 body.classList.remove( "impress-not-supported" );
216 body.classList.add( "impress-supported" );
217
218 // Data of all presentation steps
219 var stepsData = {};
220
221 // Element of currently active step
222 var activeStep = null;
223
224 // Current state (position, rotation and scale) of the presentation
225 var currentState = null;
226
227 // Array of step elements
228 var steps = null;
229
230 // Configuration options
231 var config = null;
232
233 // Scale factor of the browser window
234 var windowScale = null;
235
236 // Root presentation elements
237 var root = lib.util.byId( rootId );
238 var canvas = document.createElement( "div" );
239
240 var initialized = false;
241
242 // STEP EVENTS
243 //
244 // There are currently two step events triggered by impress.js
245 // `impress:stepenter` is triggered when the step is shown on the
246 // screen (the transition from the previous one is finished) and
247 // `impress:stepleave` is triggered when the step is left (the
248 // transition to next step just starts).
249
250 // Reference to last entered step
251 var lastEntered = null;
252
253 // `onStepEnter` is called whenever the step element is entered
254 // but the event is triggered only if the step is different than
255 // last entered step.
256 // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as
257 // after screen resize. In this case - more precisely, in any case - we trigger a
258 // `impress:steprefresh` event.
259 var onStepEnter = function( step ) {
260 if ( lastEntered !== step ) {
261 lib.util.triggerEvent( step, "impress:stepenter" );
262 lastEntered = step;
263 }
264 lib.util.triggerEvent( step, "impress:steprefresh" );
265 };
266
267 // `onStepLeave` is called whenever the currentStep element is left
268 // but the event is triggered only if the currentStep is the same as
269 // lastEntered step.
270 var onStepLeave = function( currentStep, nextStep ) {
271 if ( lastEntered === currentStep ) {
272 lib.util.triggerEvent( currentStep, "impress:stepleave", { next: nextStep } );
273 lastEntered = null;
274 }
275 };
276
277 // `initStep` initializes given step element by reading data from its
278 // data attributes and setting correct styles.
279 var initStep = function( el, idx ) {
280 var data = el.dataset,
281 step = {
282 translate: {
283 x: lib.util.toNumber( data.x ),
284 y: lib.util.toNumber( data.y ),
285 z: lib.util.toNumber( data.z )
286 },
287 rotate: {
288 x: lib.util.toNumber( data.rotateX ),
289 y: lib.util.toNumber( data.rotateY ),
290 z: lib.util.toNumber( data.rotateZ || data.rotate ),
291 order: validateOrder( data.rotateOrder )
292 },
293 scale: lib.util.toNumber( data.scale, 1 ),
294 transitionDuration: lib.util.toNumber(
295 data.transitionDuration, config.transitionDuration
296 ),
297 el: el
298 };
299
300 if ( !el.id ) {
301 el.id = "step-" + ( idx + 1 );
302 }
303
304 stepsData[ "impress-" + el.id ] = step;
305
306 css( el, {
307 position: "absolute",
308 transform: "translate(-50%,-50%)" +
309 translate( step.translate ) +
310 rotate( step.rotate ) +
311 scale( step.scale ),
312 transformStyle: "preserve-3d"
313 } );
314 };
315
316 // Initialize all steps.
317 // Read the data-* attributes, store in internal stepsData, and render with CSS.
318 var initAllSteps = function() {
319 steps = lib.util.$$( ".step", root );
320 steps.forEach( initStep );
321 };
322
323 // `init` API function that initializes (and runs) the presentation.
324 var init = function() {
325 if ( initialized ) { return; }
326 execPreInitPlugins( root );
327
328 // First we set up the viewport for mobile devices.
329 // For some reason iPad goes nuts when it is not done properly.
330 var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" );
331 meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
332 if ( meta.parentNode !== document.head ) {
333 meta.name = "viewport";
334 document.head.appendChild( meta );
335 }
336
337 // Initialize configuration object
338 var rootData = root.dataset;
339 config = {
340 width: lib.util.toNumber( rootData.width, defaults.width ),
341 height: lib.util.toNumber( rootData.height, defaults.height ),
342 maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ),
343 minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ),
344 perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ),
345 transitionDuration: lib.util.toNumber(
346 rootData.transitionDuration, defaults.transitionDuration
347 )
348 };
349
350 windowScale = computeWindowScale( config );
351
352 // Wrap steps with "canvas" element
353 lib.util.arrayify( root.childNodes ).forEach( function( el ) {
354 canvas.appendChild( el );
355 } );
356 root.appendChild( canvas );
357
358 // Set initial styles
359 document.documentElement.style.height = "100%";
360
361 css( body, {
362 height: "100%",
363 overflow: "hidden"
364 } );
365
366 var rootStyles = {
367 position: "absolute",
368 transformOrigin: "top left",
369 transition: "all 0s ease-in-out",
370 transformStyle: "preserve-3d"
371 };
372
373 css( root, rootStyles );
374 css( root, {
375 top: "50%",
376 left: "50%",
377 perspective: ( config.perspective / windowScale ) + "px",
378 transform: scale( windowScale )
379 } );
380 css( canvas, rootStyles );
381
382 body.classList.remove( "impress-disabled" );
383 body.classList.add( "impress-enabled" );
384
385 // Get and init steps
386 initAllSteps();
387
388 // Set a default initial state of the canvas
389 currentState = {
390 translate: { x: 0, y: 0, z: 0 },
391 rotate: { x: 0, y: 0, z: 0, order: "xyz" },
392 scale: 1
393 };
394
395 initialized = true;
396
397 lib.util.triggerEvent( root, "impress:init",
398 { api: roots[ "impress-root-" + rootId ] } );
399 };
400
401 // `getStep` is a helper function that returns a step element defined by parameter.
402 // If a number is given, step with index given by the number is returned, if a string
403 // is given step element with such id is returned, if DOM element is given it is returned
404 // if it is a correct step element.
405 var getStep = function( step ) {
406 if ( typeof step === "number" ) {
407 step = step < 0 ? steps[ steps.length + step ] : steps[ step ];
408 } else if ( typeof step === "string" ) {
409 step = lib.util.byId( step );
410 }
411 return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null;
412 };
413
414 // Used to reset timeout for `impress:stepenter` event
415 var stepEnterTimeout = null;
416
417 // `goto` API function that moves to step given as `el` parameter (by index, id or element).
418 // `duration` optionally given as second parameter, is the transition duration in css.
419 // `reason` is the string "next", "prev" or "goto" (default) and will be made available to
420 // preStepLeave plugins.
421 // `origEvent` may contain event that caused the call to goto, such as a key press event
422 var goto = function( el, duration, reason, origEvent ) {
423 reason = reason || "goto";
424 origEvent = origEvent || null;
425
426 if ( !initialized ) {
427 return false;
428 }
429
430 // Re-execute initAllSteps for each transition. This allows to edit step attributes
431 // dynamically, such as change their coordinates, or even remove or add steps, and have
432 // that change apply when goto() is called.
433 initAllSteps();
434
435 if ( !( el = getStep( el ) ) ) {
436 return false;
437 }
438
439 // Sometimes it's possible to trigger focus on first link with some keyboard action.
440 // Browser in such a case tries to scroll the page to make this element visible
441 // (even that body overflow is set to hidden) and it breaks our careful positioning.
442 //
443 // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
444 // whenever slide is selected
445 //
446 // If you are reading this and know any better way to handle it, I'll be glad to hear
447 // about it!
448 window.scrollTo( 0, 0 );
449
450 var step = stepsData[ "impress-" + el.id ];
451 duration = ( duration !== undefined ? duration : step.transitionDuration );
452
453 // If we are in fact moving to another step, start with executing the registered
454 // preStepLeave plugins.
455 if ( activeStep && activeStep !== el ) {
456 var event = { target: activeStep, detail: {} };
457 event.detail.next = el;
458 event.detail.transitionDuration = duration;
459 event.detail.reason = reason;
460 if ( origEvent ) {
461 event.origEvent = origEvent;
462 }
463
464 if ( execPreStepLeavePlugins( event ) === false ) {
465
466 // PreStepLeave plugins are allowed to abort the transition altogether, by
467 // returning false.
468 // see stop and substep plugins for an example of doing just that
469 return false;
470 }
471
472 // Plugins are allowed to change the detail values
473 el = event.detail.next;
474 step = stepsData[ "impress-" + el.id ];
475 duration = event.detail.transitionDuration;
476 }
477
478 if ( activeStep ) {
479 activeStep.classList.remove( "active" );
480 body.classList.remove( "impress-on-" + activeStep.id );
481 }
482 el.classList.add( "active" );
483
484 body.classList.add( "impress-on-" + el.id );
485
486 // Compute target state of the canvas based on given step
487 var target = {
488 rotate: {
489 x: -step.rotate.x,
490 y: -step.rotate.y,
491 z: -step.rotate.z,
492 order: step.rotate.order
493 },
494 translate: {
495 x: -step.translate.x,
496 y: -step.translate.y,
497 z: -step.translate.z
498 },
499 scale: 1 / step.scale
500 };
501
502 // Check if the transition is zooming in or not.
503 //
504 // This information is used to alter the transition style:
505 // when we are zooming in - we start with move and rotate transition
506 // and the scaling is delayed, but when we are zooming out we start
507 // with scaling down and move and rotation are delayed.
508 var zoomin = target.scale >= currentState.scale;
509
510 duration = lib.util.toNumber( duration, config.transitionDuration );
511 var delay = ( duration / 2 );
512
513 // If the same step is re-selected, force computing window scaling,
514 // because it is likely to be caused by window resize
515 if ( el === activeStep ) {
516 windowScale = computeWindowScale( config );
517 }
518
519 var targetScale = target.scale * windowScale;
520
521 // Trigger leave of currently active element (if it's not the same step again)
522 if ( activeStep && activeStep !== el ) {
523 onStepLeave( activeStep, el );
524 }
525
526 // Now we alter transforms of `root` and `canvas` to trigger transitions.
527 //
528 // And here is why there are two elements: `root` and `canvas` - they are
529 // being animated separately:
530 // `root` is used for scaling and `canvas` for translate and rotations.
531 // Transitions on them are triggered with different delays (to make
532 // visually nice and "natural" looking transitions), so we need to know
533 // that both of them are finished.
534 css( root, {
535
536 // To keep the perspective look similar for different scales
537 // we need to "scale" the perspective, too
538 // For IE 11 support we must specify perspective independent
539 // of transform.
540 perspective: ( config.perspective / targetScale ) + "px",
541 transform: scale( targetScale ),
542 transitionDuration: duration + "ms",
543 transitionDelay: ( zoomin ? delay : 0 ) + "ms"
544 } );
545
546 css( canvas, {
547 transform: rotate( target.rotate, true ) + translate( target.translate ),
548 transitionDuration: duration + "ms",
549 transitionDelay: ( zoomin ? 0 : delay ) + "ms"
550 } );
551
552 // Here is a tricky part...
553 //
554 // If there is no change in scale or no change in rotation and translation, it means
555 // there was actually no delay - because there was no transition on `root` or `canvas`
556 // elements. We want to trigger `impress:stepenter` event in the correct moment, so
557 // here we compare the current and target values to check if delay should be taken into
558 // account.
559 //
560 // I know that this `if` statement looks scary, but it's pretty simple when you know
561 // what is going on - it's simply comparing all the values.
562 if ( currentState.scale === target.scale ||
563 ( currentState.rotate.x === target.rotate.x &&
564 currentState.rotate.y === target.rotate.y &&
565 currentState.rotate.z === target.rotate.z &&
566 currentState.translate.x === target.translate.x &&
567 currentState.translate.y === target.translate.y &&
568 currentState.translate.z === target.translate.z ) ) {
569 delay = 0;
570 }
571
572 // Store current state
573 currentState = target;
574 activeStep = el;
575
576 // And here is where we trigger `impress:stepenter` event.
577 // We simply set up a timeout to fire it taking transition duration (and possible delay)
578 // into account.
579 //
580 // I really wanted to make it in more elegant way. The `transitionend` event seemed to
581 // be the best way to do it, but the fact that I'm using transitions on two separate
582 // elements and that the `transitionend` event is only triggered when there was a
583 // transition (change in the values) caused some bugs and made the code really
584 // complicated, cause I had to handle all the conditions separately. And it still
585 // needed a `setTimeout` fallback for the situations when there is no transition at all.
586 // So I decided that I'd rather make the code simpler than use shiny new
587 // `transitionend`.
588 //
589 // If you want learn something interesting and see how it was done with `transitionend`
590 // go back to version 0.5.2 of impress.js:
591 // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
592 window.clearTimeout( stepEnterTimeout );
593 stepEnterTimeout = window.setTimeout( function() {
594 onStepEnter( activeStep );
595 }, duration + delay );
596
597 return el;
598 };
599
600 // `prev` API function goes to previous step (in document order)
601 // `event` is optional, may contain the event that caused the need to call prev()
602 var prev = function( origEvent ) {
603 var prev = steps.indexOf( activeStep ) - 1;
604 prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];
605
606 return goto( prev, undefined, "prev", origEvent );
607 };
608
609 // `next` API function goes to next step (in document order)
610 // `event` is optional, may contain the event that caused the need to call next()
611 var next = function( origEvent ) {
612 var next = steps.indexOf( activeStep ) + 1;
613 next = next < steps.length ? steps[ next ] : steps[ 0 ];
614
615 return goto( next, undefined, "next", origEvent );
616 };
617
618 // Swipe for touch devices by @and3rson.
619 // Below we extend the api to control the animation between the currently
620 // active step and a presumed next/prev step. See touch plugin for
621 // an example of using this api.
622
623 // Helper function
624 var interpolate = function( a, b, k ) {
625 return a + ( b - a ) * k;
626 };
627
628 // Animate a swipe.
629 //
630 // Pct is a value between -1.0 and +1.0, designating the current length
631 // of the swipe.
632 //
633 // If pct is negative, swipe towards the next() step, if positive,
634 // towards the prev() step.
635 //
636 // Note that pre-stepleave plugins such as goto can mess with what is a
637 // next() and prev() step, so we need to trigger the pre-stepleave event
638 // here, even if a swipe doesn't guarantee that the transition will
639 // actually happen.
640 //
641 // Calling swipe(), with any value of pct, won't in itself cause a
642 // transition to happen, this is just to animate the swipe. Once the
643 // transition is committed - such as at a touchend event - caller is
644 // responsible for also calling prev()/next() as appropriate.
645 //
646 // Note: For now, this function is made available to be used by the swipe plugin (which
647 // is the UI counterpart to this). It is a semi-internal API and intentionally not
648 // documented in DOCUMENTATION.md.
649 var swipe = function( pct ) {
650 if ( Math.abs( pct ) > 1 ) {
651 return;
652 }
653
654 // Prepare & execute the preStepLeave event
655 var event = { target: activeStep, detail: {} };
656 event.detail.swipe = pct;
657
658 // Will be ignored within swipe animation, but just in case a plugin wants to read this,
659 // humor them
660 event.detail.transitionDuration = config.transitionDuration;
661 var idx; // Needed by jshint
662 if ( pct < 0 ) {
663 idx = steps.indexOf( activeStep ) + 1;
664 event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];
665 event.detail.reason = "next";
666 } else if ( pct > 0 ) {
667 idx = steps.indexOf( activeStep ) - 1;
668 event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];
669 event.detail.reason = "prev";
670 } else {
671
672 // No move
673 return;
674 }
675 if ( execPreStepLeavePlugins( event ) === false ) {
676
677 // If a preStepLeave plugin wants to abort the transition, don't animate a swipe
678 // For stop, this is probably ok. For substep, the plugin it self might want to do
679 // some animation, but that's not the current implementation.
680 return false;
681 }
682 var nextElement = event.detail.next;
683
684 var nextStep = stepsData[ "impress-" + nextElement.id ];
685
686 // If the same step is re-selected, force computing window scaling,
687 var nextScale = nextStep.scale * windowScale;
688 var k = Math.abs( pct );
689
690 var interpolatedStep = {
691 translate: {
692 x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),
693 y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),
694 z: interpolate( currentState.translate.z, -nextStep.translate.z, k )
695 },
696 rotate: {
697 x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),
698 y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),
699 z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),
700
701 // Unfortunately there's a discontinuity if rotation order changes. Nothing I
702 // can do about it?
703 order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
704 },
705 scale: interpolate( currentState.scale, nextScale, k )
706 };
707
708 css( root, {
709
710 // To keep the perspective look similar for different scales
711 // we need to 'scale' the perspective, too
712 perspective: config.perspective / interpolatedStep.scale + "px",
713 transform: scale( interpolatedStep.scale ),
714 transitionDuration: "0ms",
715 transitionDelay: "0ms"
716 } );
717
718 css( canvas, {
719 transform: rotate( interpolatedStep.rotate, true ) +
720 translate( interpolatedStep.translate ),
721 transitionDuration: "0ms",
722 transitionDelay: "0ms"
723 } );
724 };
725
726 // Teardown impress
727 // Resets the DOM to the state it was before impress().init() was called.
728 // (If you called impress(rootId).init() for multiple different rootId's, then you must
729 // also call tear() once for each of them.)
730 var tear = function() {
731 lib.gc.teardown();
732 delete roots[ "impress-root-" + rootId ];
733 };
734
735 // Adding some useful classes to step elements.
736 //
737 // All the steps that have not been shown yet are given `future` class.
738 // When the step is entered the `future` class is removed and the `present`
739 // class is given. When the step is left `present` class is replaced with
740 // `past` class.
741 //
742 // So every step element is always in one of three possible states:
743 // `future`, `present` and `past`.
744 //
745 // There classes can be used in CSS to style different types of steps.
746 // For example the `present` class can be used to trigger some custom
747 // animations when step is shown.
748 lib.gc.addEventListener( root, "impress:init", function() {
749
750 // STEP CLASSES
751 steps.forEach( function( step ) {
752 step.classList.add( "future" );
753 } );
754
755 lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
756 event.target.classList.remove( "past" );
757 event.target.classList.remove( "future" );
758 event.target.classList.add( "present" );
759 }, false );
760
761 lib.gc.addEventListener( root, "impress:stepleave", function( event ) {
762 event.target.classList.remove( "present" );
763 event.target.classList.add( "past" );
764 }, false );
765
766 }, false );
767
768 // Adding hash change support.
769 lib.gc.addEventListener( root, "impress:init", function() {
770
771 // Last hash detected
772 var lastHash = "";
773
774 // `#/step-id` is used instead of `#step-id` to prevent default browser
775 // scrolling to element in hash.
776 //
777 // And it has to be set after animation finishes, because in Chrome it
778 // makes transtion laggy.
779 // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
780 lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
781 window.location.hash = lastHash = "#/" + event.target.id;
782 }, false );
783
784 lib.gc.addEventListener( window, "hashchange", function() {
785
786 // When the step is entered hash in the location is updated
787 // (just few lines above from here), so the hash change is
788 // triggered and we would call `goto` again on the same element.
789 //
790 // To avoid this we store last entered hash and compare.
791 if ( window.location.hash !== lastHash ) {
792 goto( lib.util.getElementFromHash() );
793 }
794 }, false );
795
796 // START
797 // by selecting step defined in url or first step of the presentation
798 goto( lib.util.getElementFromHash() || steps[ 0 ], 0 );
799 }, false );
800
801 body.classList.add( "impress-disabled" );
802
803 // Store and return API for given impress.js root element
804 return ( roots[ "impress-root-" + rootId ] = {
805 init: init,
806 goto: goto,
807 next: next,
808 prev: prev,
809 swipe: swipe,
810 tear: tear,
811 lib: lib
812 } );
813
814 };
815
816 // Flag that can be used in JS to check if browser have passed the support test
817 impress.supported = impressSupported;
818
819 // ADD and INIT LIBRARIES
820 // Library factories are defined in src/lib/*.js, and register themselves by calling
821 // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment
822 // the API with library functions when client calls impress(rootId).
823 // See src/lib/README.md for clearer example.
824 // (Advanced usage: For different values of rootId, a different instance of the libaries are
825 // generated, in case they need to hold different state for different root elements.)
826 var libraryFactories = {};
827 impress.addLibraryFactory = function( obj ) {
828 for ( var libname in obj ) {
829 if ( obj.hasOwnProperty( libname ) ) {
830 libraryFactories[ libname ] = obj[ libname ];
831 }
832 }
833 };
834
835 // Call each library factory, and return the lib object that is added to the api.
836 var initLibraries = function( rootId ) { //jshint ignore:line
837 var lib = {};
838 for ( var libname in libraryFactories ) {
839 if ( libraryFactories.hasOwnProperty( libname ) ) {
840 if ( lib[ libname ] !== undefined ) {
841 throw "impress.js ERROR: Two libraries both tried to use libname: " + libname;
842 }
843 lib[ libname ] = libraryFactories[ libname ]( rootId );
844 }
845 }
846 return lib;
847 };
848
849 // `addPreInitPlugin` allows plugins to register a function that should
850 // be run (synchronously) at the beginning of init, before
851 // impress().init() itself executes.
852 impress.addPreInitPlugin = function( plugin, weight ) {
853 weight = parseInt( weight ) || 10;
854 if ( weight <= 0 ) {
855 throw "addPreInitPlugin: weight must be a positive integer";
856 }
857
858 if ( preInitPlugins[ weight ] === undefined ) {
859 preInitPlugins[ weight ] = [];
860 }
861 preInitPlugins[ weight ].push( plugin );
862 };
863
864 // Called at beginning of init, to execute all pre-init plugins.
865 var execPreInitPlugins = function( root ) { //jshint ignore:line
866 for ( var i = 0; i < preInitPlugins.length; i++ ) {
867 var thisLevel = preInitPlugins[ i ];
868 if ( thisLevel !== undefined ) {
869 for ( var j = 0; j < thisLevel.length; j++ ) {
870 thisLevel[ j ]( root );
871 }
872 }
873 }
874 };
875
876 // `addPreStepLeavePlugin` allows plugins to register a function that should
877 // be run (synchronously) at the beginning of goto()
878 impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line
879 weight = parseInt( weight ) || 10;
880 if ( weight <= 0 ) {
881 throw "addPreStepLeavePlugin: weight must be a positive integer";
882 }
883
884 if ( preStepLeavePlugins[ weight ] === undefined ) {
885 preStepLeavePlugins[ weight ] = [];
886 }
887 preStepLeavePlugins[ weight ].push( plugin );
888 };
889
890 // Called at beginning of goto(), to execute all preStepLeave plugins.
891 var execPreStepLeavePlugins = function( event ) { //jshint ignore:line
892 for ( var i = 0; i < preStepLeavePlugins.length; i++ ) {
893 var thisLevel = preStepLeavePlugins[ i ];
894 if ( thisLevel !== undefined ) {
895 for ( var j = 0; j < thisLevel.length; j++ ) {
896 if ( thisLevel[ j ]( event ) === false ) {
897
898 // If a plugin returns false, the stepleave event (and related transition)
899 // is aborted
900 return false;
901 }
902 }
903 }
904 }
905 };
906
907} )( document, window );
908
909// THAT'S ALL FOLKS!
910//
911// Thanks for reading it all.
912// Or thanks for scrolling down and reading the last part.
913//
914// I've learnt a lot when building impress.js and I hope this code and comments
915// will help somebody learn at least some part of it.
916
917/**
918 * Garbage collection utility
919 *
920 * This library allows plugins to add elements and event listeners they add to the DOM. The user
921 * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that
922 * the document is in the state it was before calling `impress().init()`.
923 *
924 * In addition to just adding elements and event listeners to the garbage collector, plugins
925 * can also register callback functions to do arbitrary cleanup upon teardown.
926 *
927 * Henrik Ingo (c) 2016
928 * MIT License
929 */
930
931( function( document, window ) {
932 "use strict";
933 var roots = [];
934 var rootsCount = 0;
935 var startingState = { roots: [] };
936
937 var libraryFactory = function( rootId ) {
938 if ( roots[ rootId ] ) {
939 return roots[ rootId ];
940 }
941
942 // Per root global variables (instance variables?)
943 var elementList = [];
944 var eventListenerList = [];
945 var callbackList = [];
946
947 recordStartingState( rootId );
948
949 // LIBRARY FUNCTIONS
950 // Definitions of the library functions we return as an object at the end
951
952 // `pushElement` adds a DOM element to the gc stack
953 var pushElement = function( element ) {
954 elementList.push( element );
955 };
956
957 // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement
958 var appendChild = function( parent, element ) {
959 parent.appendChild( element );
960 pushElement( element );
961 };
962
963 // `pushEventListener` adds an event listener to the gc stack
964 var pushEventListener = function( target, type, listenerFunction ) {
965 eventListenerList.push( { target:target, type:type, listener:listenerFunction } );
966 };
967
968 // `addEventListener` combines DOM addEventListener with gc.pushEventListener
969 var addEventListener = function( target, type, listenerFunction ) {
970 target.addEventListener( type, listenerFunction );
971 pushEventListener( target, type, listenerFunction );
972 };
973
974 // `pushCallback` If the above utilities are not enough, plugins can add their own callback
975 // function to do arbitrary things.
976 var pushCallback = function( callback ) {
977 callbackList.push( callback );
978 };
979 pushCallback( function( rootId ) { resetStartingState( rootId ); } );
980
981 // `teardown` will
982 // - execute all callbacks in LIFO order
983 // - call `removeChild` on all DOM elements in LIFO order
984 // - call `removeEventListener` on all event listeners in LIFO order
985 // The goal of a teardown is to return to the same state that the DOM was before
986 // `impress().init()` was called.
987 var teardown = function() {
988
989 // Execute the callbacks in LIFO order
990 var i; // Needed by jshint
991 for ( i = callbackList.length - 1; i >= 0; i-- ) {
992 callbackList[ i ]( rootId );
993 }
994 callbackList = [];
995 for ( i = 0; i < elementList.length; i++ ) {
996 elementList[ i ].parentElement.removeChild( elementList[ i ] );
997 }
998 elementList = [];
999 for ( i = 0; i < eventListenerList.length; i++ ) {
1000 var target = eventListenerList[ i ].target;
1001 var type = eventListenerList[ i ].type;
1002 var listener = eventListenerList[ i ].listener;
1003 target.removeEventListener( type, listener );
1004 }
1005 };
1006
1007 var lib = {
1008 pushElement: pushElement,
1009 appendChild: appendChild,
1010 pushEventListener: pushEventListener,
1011 addEventListener: addEventListener,
1012 pushCallback: pushCallback,
1013 teardown: teardown
1014 };
1015 roots[ rootId ] = lib;
1016 rootsCount++;
1017 return lib;
1018 };
1019
1020 // Let impress core know about the existence of this library
1021 window.impress.addLibraryFactory( { gc: libraryFactory } );
1022
1023 // CORE INIT
1024 // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()
1025 // For the purposes of teardown(), we can use this as an opportunity to save the state
1026 // of a few things in the DOM in their virgin state, before impress().init() did anything.
1027 // Note: These could also be recorded by the code in impress.js core as these values
1028 // are changed, but in an effort to not deviate too much from upstream, I'm adding
1029 // them here rather than the core itself.
1030 var recordStartingState = function( rootId ) {
1031 startingState.roots[ rootId ] = {};
1032 startingState.roots[ rootId ].steps = [];
1033
1034 // Record whether the steps have an id or not
1035 var steps = document.getElementById( rootId ).querySelectorAll( ".step" );
1036 for ( var i = 0; i < steps.length; i++ ) {
1037 var el = steps[ i ];
1038 startingState.roots[ rootId ].steps.push( {
1039 el: el,
1040 id: el.getAttribute( "id" )
1041 } );
1042 }
1043
1044 // In the rare case of multiple roots, the following is changed on first init() and
1045 // reset at last tear().
1046 if ( rootsCount === 0 ) {
1047 startingState.body = {};
1048
1049 // It is customary for authors to set body.class="impress-not-supported" as a starting
1050 // value, which can then be removed by impress().init(). But it is not required.
1051 // Remember whether it was there or not.
1052 if ( document.body.classList.contains( "impress-not-supported" ) ) {
1053 startingState.body.impressNotSupported = true;
1054 } else {
1055 startingState.body.impressNotSupported = false;
1056 }
1057
1058 // If there's a <meta name="viewport"> element, its contents will be overwritten by init
1059 var metas = document.head.querySelectorAll( "meta" );
1060 for ( i = 0; i < metas.length; i++ ) {
1061 var m = metas[ i ];
1062 if ( m.name === "viewport" ) {
1063 startingState.meta = m.content;
1064 }
1065 }
1066 }
1067 };
1068
1069 // CORE TEARDOWN
1070 var resetStartingState = function( rootId ) {
1071
1072 // Reset body element
1073 document.body.classList.remove( "impress-enabled" );
1074 document.body.classList.remove( "impress-disabled" );
1075
1076 var root = document.getElementById( rootId );
1077 var activeId = root.querySelector( ".active" ).id;
1078 document.body.classList.remove( "impress-on-" + activeId );
1079
1080 document.documentElement.style.height = "";
1081 document.body.style.height = "";
1082 document.body.style.overflow = "";
1083
1084 // Remove style values from the root and step elements
1085 // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original
1086 // values. A more sophisticated implementation could keep track of original values and then
1087 // reset those.
1088 var steps = root.querySelectorAll( ".step" );
1089 for ( var i = 0; i < steps.length; i++ ) {
1090 steps[ i ].classList.remove( "future" );
1091 steps[ i ].classList.remove( "past" );
1092 steps[ i ].classList.remove( "present" );
1093 steps[ i ].classList.remove( "active" );
1094 steps[ i ].style.position = "";
1095 steps[ i ].style.transform = "";
1096 steps[ i ].style[ "transform-style" ] = "";
1097 }
1098 root.style.position = "";
1099 root.style[ "transform-origin" ] = "";
1100 root.style.transition = "";
1101 root.style[ "transform-style" ] = "";
1102 root.style.top = "";
1103 root.style.left = "";
1104 root.style.transform = "";
1105
1106 // Reset id of steps ("step-1" id's are auto generated)
1107 steps = startingState.roots[ rootId ].steps;
1108 var step;
1109 while ( step = steps.pop() ) {
1110 if ( step.id === null ) {
1111 step.el.removeAttribute( "id" );
1112 } else {
1113 step.el.setAttribute( "id", step.id );
1114 }
1115 }
1116 delete startingState.roots[ rootId ];
1117
1118 // Move step div elements away from canvas, then delete canvas
1119 // Note: There's an implicit assumption here that the canvas div is the only child element
1120 // of the root div. If there would be something else, it's gonna be lost.
1121 var canvas = root.firstChild;
1122 var canvasHTML = canvas.innerHTML;
1123 root.innerHTML = canvasHTML;
1124
1125 if ( roots[ rootId ] !== undefined ) {
1126 delete roots[ rootId ];
1127 rootsCount--;
1128 }
1129 if ( rootsCount === 0 ) {
1130
1131 // In the rare case that more than one impress root elements were initialized, these
1132 // are only reset when all are uninitialized.
1133 document.body.classList.remove( "impress-supported" );
1134 if ( startingState.body.impressNotSupported ) {
1135 document.body.classList.add( "impress-not-supported" );
1136 }
1137
1138 // We need to remove or reset the meta element inserted by impress.js
1139 var metas = document.head.querySelectorAll( "meta" );
1140 for ( i = 0; i < metas.length; i++ ) {
1141 var m = metas[ i ];
1142 if ( m.name === "viewport" ) {
1143 if ( startingState.meta !== undefined ) {
1144 m.content = startingState.meta;
1145 } else {
1146 m.parentElement.removeChild( m );
1147 }
1148 }
1149 }
1150 }
1151
1152 };
1153
1154} )( document, window );
1155
1156/**
1157 * Common utility functions
1158 *
1159 * Copyright 2011-2012 Bartek Szopka (@bartaz)
1160 * Henrik Ingo (c) 2016
1161 * MIT License
1162 */
1163
1164( function( document, window ) {
1165 "use strict";
1166 var roots = [];
1167
1168 var libraryFactory = function( rootId ) {
1169 if ( roots[ rootId ] ) {
1170 return roots[ rootId ];
1171 }
1172
1173 // `$` returns first element for given CSS `selector` in the `context` of
1174 // the given element or whole document.
1175 var $ = function( selector, context ) {
1176 context = context || document;
1177 return context.querySelector( selector );
1178 };
1179
1180 // `$$` return an array of elements for given CSS `selector` in the `context` of
1181 // the given element or whole document.
1182 var $$ = function( selector, context ) {
1183 context = context || document;
1184 return arrayify( context.querySelectorAll( selector ) );
1185 };
1186
1187 // `arrayify` takes an array-like object and turns it into real Array
1188 // to make all the Array.prototype goodness available.
1189 var arrayify = function( a ) {
1190 return [].slice.call( a );
1191 };
1192
1193 // `byId` returns element with given `id` - you probably have guessed that ;)
1194 var byId = function( id ) {
1195 return document.getElementById( id );
1196 };
1197
1198 // `getElementFromHash` returns an element located by id from hash part of
1199 // window location.
1200 var getElementFromHash = function() {
1201
1202 // Get id from url # by removing `#` or `#/` from the beginning,
1203 // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
1204 return byId( window.location.hash.replace( /^#\/?/, "" ) );
1205 };
1206
1207 // Throttling function calls, by Remy Sharp
1208 // http://remysharp.com/2010/07/21/throttling-function-calls/
1209 var throttle = function( fn, delay ) {
1210 var timer = null;
1211 return function() {
1212 var context = this, args = arguments;
1213 window.clearTimeout( timer );
1214 timer = window.setTimeout( function() {
1215 fn.apply( context, args );
1216 }, delay );
1217 };
1218 };
1219
1220 // `toNumber` takes a value given as `numeric` parameter and tries to turn
1221 // it into a number. If it is not possible it returns 0 (or other value
1222 // given as `fallback`).
1223 var toNumber = function( numeric, fallback ) {
1224 return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
1225 };
1226
1227 // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
1228 // and triggers it on element given as `el`.
1229 var triggerEvent = function( el, eventName, detail ) {
1230 var event = document.createEvent( "CustomEvent" );
1231 event.initCustomEvent( eventName, true, true, detail );
1232 el.dispatchEvent( event );
1233 };
1234
1235 var lib = {
1236 $: $,
1237 $$: $$,
1238 arrayify: arrayify,
1239 byId: byId,
1240 getElementFromHash: getElementFromHash,
1241 throttle: throttle,
1242 toNumber: toNumber,
1243 triggerEvent: triggerEvent
1244 };
1245 roots[ rootId ] = lib;
1246 return lib;
1247 };
1248
1249 // Let impress core know about the existence of this library
1250 window.impress.addLibraryFactory( { util: libraryFactory } );
1251
1252} )( document, window );
1253
1254/**
1255 * Autoplay plugin - Automatically advance slideshow after N seconds
1256 *
1257 * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi
1258 * Released under the MIT license.
1259 */
1260/* global clearTimeout, setTimeout, document */
1261
1262( function( document ) {
1263 "use strict";
1264
1265 var autoplayDefault = 0;
1266 var currentStepTimeout = 0;
1267 var api = null;
1268 var timeoutHandle = null;
1269 var root = null;
1270 var util;
1271
1272 // On impress:init, check whether there is a default setting, as well as
1273 // handle step-1.
1274 document.addEventListener( "impress:init", function( event ) {
1275 util = event.detail.api.lib.util;
1276
1277 // Getting API from event data instead of global impress().init().
1278 // You don't even need to know what is the id of the root element
1279 // or anything. `impress:init` event data gives you everything you
1280 // need to control the presentation that was just initialized.
1281 api = event.detail.api;
1282 root = event.target;
1283
1284 // Element attributes starting with "data-", become available under
1285 // element.dataset. In addition hyphenized words become camelCased.
1286 var data = root.dataset;
1287
1288 if ( data.autoplay ) {
1289 autoplayDefault = util.toNumber( data.autoplay, 0 );
1290 }
1291
1292 var toolbar = document.querySelector( "#impress-toolbar" );
1293 if ( toolbar ) {
1294 addToolbarButton( toolbar );
1295 }
1296
1297 api.lib.gc.pushCallback( function() {
1298 clearTimeout( timeoutHandle );
1299 } );
1300
1301 // Note that right after impress:init event, also impress:stepenter is
1302 // triggered for the first slide, so that's where code flow continues.
1303 }, false );
1304
1305 // If default autoplay time was defined in the presentation root, or
1306 // in this step, set timeout.
1307 var reloadTimeout = function( event ) {
1308 var step = event.target;
1309 currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );
1310 if ( status === "paused" ) {
1311 setAutoplayTimeout( 0 );
1312 } else {
1313 setAutoplayTimeout( currentStepTimeout );
1314 }
1315 };
1316
1317 document.addEventListener( "impress:stepenter", function( event ) {
1318 reloadTimeout( event );
1319 }, false );
1320
1321 document.addEventListener( "impress:substep:stepleaveaborted", function( event ) {
1322 reloadTimeout( event );
1323 }, false );
1324
1325 /**
1326 * Set timeout after which we move to next() step.
1327 */
1328 var setAutoplayTimeout = function( timeout ) {
1329 if ( timeoutHandle ) {
1330 clearTimeout( timeoutHandle );
1331 }
1332
1333 if ( timeout > 0 ) {
1334 timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );
1335 }
1336 setButtonText();
1337 };
1338
1339 /*** Toolbar plugin integration *******************************************/
1340 var status = "not clicked";
1341 var toolbarButton = null;
1342
1343 // Copied from core impress.js. Good candidate for moving to a utilities collection.
1344 var triggerEvent = function( el, eventName, detail ) {
1345 var event = document.createEvent( "CustomEvent" );
1346 event.initCustomEvent( eventName, true, true, detail );
1347 el.dispatchEvent( event );
1348 };
1349
1350 var makeDomElement = function( html ) {
1351 var tempDiv = document.createElement( "div" );
1352 tempDiv.innerHTML = html;
1353 return tempDiv.firstChild;
1354 };
1355
1356 var toggleStatus = function() {
1357 if ( currentStepTimeout > 0 && status !== "paused" ) {
1358 status = "paused";
1359 } else {
1360 status = "playing";
1361 }
1362 };
1363
1364 var getButtonText = function() {
1365 if ( currentStepTimeout > 0 && status !== "paused" ) {
1366 return "||"; // Pause
1367 } else {
1368 return "▶"; // Play
1369 }
1370 };
1371
1372 var setButtonText = function() {
1373 if ( toolbarButton ) {
1374
1375 // Keep button size the same even if label content is changing
1376 var buttonWidth = toolbarButton.offsetWidth;
1377 var buttonHeight = toolbarButton.offsetHeight;
1378 toolbarButton.innerHTML = getButtonText();
1379 if ( !toolbarButton.style.width ) {
1380 toolbarButton.style.width = buttonWidth + "px";
1381 }
1382 if ( !toolbarButton.style.height ) {
1383 toolbarButton.style.height = buttonHeight + "px";
1384 }
1385 }
1386 };
1387
1388 var addToolbarButton = function( toolbar ) {
1389 var html = '<button id="impress-autoplay-playpause" ' + // jshint ignore:line
1390 'title="Autoplay" class="impress-autoplay">' + // jshint ignore:line
1391 getButtonText() + "</button>"; // jshint ignore:line
1392 toolbarButton = makeDomElement( html );
1393 toolbarButton.addEventListener( "click", function() {
1394 toggleStatus();
1395 if ( status === "playing" ) {
1396 if ( autoplayDefault === 0 ) {
1397 autoplayDefault = 7;
1398 }
1399 if ( currentStepTimeout === 0 ) {
1400 currentStepTimeout = autoplayDefault;
1401 }
1402 setAutoplayTimeout( currentStepTimeout );
1403 } else if ( status === "paused" ) {
1404 setAutoplayTimeout( 0 );
1405 }
1406 } );
1407
1408 triggerEvent( toolbar, "impress:toolbar:appendChild",
1409 { group: 10, element: toolbarButton } );
1410 };
1411
1412} )( document );
1413
1414/**
1415 * Blackout plugin
1416 *
1417 * Press Ctrl+b to hide all slides, and Ctrl+b again to show them.
1418 * Also navigating to a different slide will show them again (impress:stepleave).
1419 *
1420 * Copyright 2014 @Strikeskids
1421 * Released under the MIT license.
1422 */
1423/* global document */
1424
1425( function( document ) {
1426 "use strict";
1427
1428 var canvas = null;
1429 var blackedOut = false;
1430
1431 // While waiting for a shared library of utilities, copying these 2 from main impress.js
1432 var css = function( el, props ) {
1433 var key, pkey;
1434 for ( key in props ) {
1435 if ( props.hasOwnProperty( key ) ) {
1436 pkey = pfx( key );
1437 if ( pkey !== null ) {
1438 el.style[ pkey ] = props[ key ];
1439 }
1440 }
1441 }
1442 return el;
1443 };
1444
1445 var pfx = ( function() {
1446
1447 var style = document.createElement( "dummy" ).style,
1448 prefixes = "Webkit Moz O ms Khtml".split( " " ),
1449 memory = {};
1450
1451 return function( prop ) {
1452 if ( typeof memory[ prop ] === "undefined" ) {
1453
1454 var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
1455 props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
1456
1457 memory[ prop ] = null;
1458 for ( var i in props ) {
1459 if ( style[ props[ i ] ] !== undefined ) {
1460 memory[ prop ] = props[ i ];
1461 break;
1462 }
1463 }
1464
1465 }
1466
1467 return memory[ prop ];
1468 };
1469
1470 } )();
1471
1472 var removeBlackout = function() {
1473 if ( blackedOut ) {
1474 css( canvas, {
1475 display: "block"
1476 } );
1477 blackedOut = false;
1478 }
1479 };
1480
1481 var blackout = function() {
1482 if ( blackedOut ) {
1483 removeBlackout();
1484 } else {
1485 css( canvas, {
1486 display: ( blackedOut = !blackedOut ) ? "none" : "block"
1487 } );
1488 blackedOut = true;
1489 }
1490 };
1491
1492 // Wait for impress.js to be initialized
1493 document.addEventListener( "impress:init", function( event ) {
1494 var api = event.detail.api;
1495 var root = event.target;
1496 canvas = root.firstElementChild;
1497 var gc = api.lib.gc;
1498
1499 gc.addEventListener( document, "keydown", function( event ) {
1500 if ( event.keyCode === 66 ) {
1501 event.preventDefault();
1502 if ( !blackedOut ) {
1503 blackout();
1504 } else {
1505 removeBlackout();
1506 }
1507 }
1508 }, false );
1509
1510 gc.addEventListener( document, "keyup", function( event ) {
1511 if ( event.keyCode === 66 ) {
1512 event.preventDefault();
1513 }
1514 }, false );
1515
1516 }, false );
1517
1518 document.addEventListener( "impress:stepleave", function() {
1519 removeBlackout();
1520 }, false );
1521
1522} )( document );
1523
1524
1525/**
1526 * Extras Plugin
1527 *
1528 * This plugin performs initialization (like calling mermaid.initialize())
1529 * for the extras/ plugins if they are loaded into a presentation.
1530 *
1531 * See README.md for details.
1532 *
1533 * Copyright 2016 Henrik Ingo (@henrikingo)
1534 * Released under the MIT license.
1535 */
1536/* global markdown, hljs, mermaid, impress, document, window */
1537
1538( function( document, window ) {
1539 "use strict";
1540
1541 var preInit = function() {
1542 if ( window.markdown ) {
1543
1544 // Unlike the other extras, Markdown.js doesn't by default do anything in
1545 // particular. We do it ourselves here.
1546 // In addition, we use "-----" as a delimiter for new slide.
1547
1548 // Query all .markdown elements and translate to HTML
1549 var markdownDivs = document.querySelectorAll( ".markdown" );
1550 for ( var idx = 0; idx < markdownDivs.length; idx++ ) {
1551 var element = markdownDivs[ idx ];
1552
1553 var slides = element.textContent.split( /^-----$/m );
1554 var i = slides.length - 1;
1555 element.innerHTML = markdown.toHTML( slides[ i ] );
1556
1557 // If there's an id, unset it for last, and all other, elements,
1558 // and then set it for the first.
1559 var id = null;
1560 if ( element.id ) {
1561 id = element.id;
1562 element.id = "";
1563 }
1564 i--;
1565 while ( i >= 0 ) {
1566 var newElement = element.cloneNode( false );
1567 newElement.innerHTML = markdown.toHTML( slides[ i ] );
1568 element.parentNode.insertBefore( newElement, element );
1569 element = newElement;
1570 i--;
1571 }
1572 if ( id !== null ) {
1573 element.id = id;
1574 }
1575 }
1576 } // Markdown
1577
1578 if ( window.hljs ) {
1579 hljs.initHighlightingOnLoad();
1580 }
1581
1582 if ( window.mermaid ) {
1583 mermaid.initialize( { startOnLoad:true } );
1584 }
1585 };
1586
1587 // Register the plugin to be called in pre-init phase
1588 // Note: Markdown.js should run early/first, because it creates new div elements.
1589 // So add this with a lower-than-default weight.
1590 impress.addPreInitPlugin( preInit, 1 );
1591
1592} )( document, window );
1593
1594
1595/**
1596 * Form support
1597 *
1598 * Functionality to better support use of input, textarea, button... elements in a presentation.
1599 *
1600 * This plugin does two things:
1601 *
1602 * Set stopPropagation on any element that might take text input. This allows users to type, for
1603 * example, the letter 'P' into a form field, without causing the presenter console to spring up.
1604 *
1605 * On impress:stepleave, de-focus any potentially active
1606 * element. This is to prevent the focus from being left in a form element that is no longer visible
1607 * in the window, and user therefore typing garbage into the form.
1608 *
1609 * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and
1610 * in particular the navigation plugin, unfortunately must fully take control of the tab key,
1611 * otherwise a user could cause the browser to scroll to a link or button that's not on the current
1612 * step. However, it could be possible to allow tab navigation between form elements, as long as
1613 * they are on the active step. This is a topic for further study.
1614 *
1615 * Copyright 2016 Henrik Ingo
1616 * MIT License
1617 */
1618/* global document */
1619( function( document ) {
1620 "use strict";
1621 var root;
1622 var api;
1623
1624 document.addEventListener( "impress:init", function( event ) {
1625 root = event.target;
1626 api = event.detail.api;
1627 var gc = api.lib.gc;
1628
1629 var selectors = [ "input[type=text]", "textarea", "select", "[contenteditable=true]" ];
1630 for ( var selector of selectors ) {
1631 var elements = document.querySelectorAll( selector );
1632 if ( !elements ) {
1633 continue;
1634 }
1635
1636 for ( var i = 0; i < elements.length; i++ ) {
1637 var e = elements[ i ];
1638 gc.addEventListener( e, "keydown", function( event ) {
1639 event.stopPropagation();
1640 } );
1641 gc.addEventListener( e, "keyup", function( event ) {
1642 event.stopPropagation();
1643 } );
1644 }
1645 }
1646 }, false );
1647
1648 document.addEventListener( "impress:stepleave", function() {
1649 document.activeElement.blur();
1650 }, false );
1651
1652} )( document );
1653
1654
1655/**
1656 * Goto Plugin
1657 *
1658 * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,
1659 * and will alter the destination where to transition next.
1660 *
1661 * Example:
1662 *
1663 * <!-- When leaving this step, go directly to "step-5" -->
1664 * <div class="step" data-goto="step-5">
1665 *
1666 * <!-- When leaving this step with next(), go directly to "step-5", instead of next step.
1667 * If moving backwards to previous step - e.g. prev() instead of next() -
1668 * then go to "step-1". -->
1669 * <div class="step" data-goto-next="step-5" data-goto-prev="step-1">
1670 *
1671 * <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear
1672 * navigation. -->
1673 * <div class="step"
1674 * data-goto-key-list="ArrowUp ArrowDown ArrowRight ArrowLeft"
1675 * data-goto-next-list="step-4 step-3 step-2 step-5">
1676 *
1677 * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table
1678 * of what strings to use for each key.
1679 *
1680 * Copyright 2016-2017 Henrik Ingo (@henrikingo)
1681 * Released under the MIT license.
1682 */
1683/* global window, document, impress */
1684
1685( function( document, window ) {
1686 "use strict";
1687 var lib;
1688
1689 document.addEventListener( "impress:init", function( event ) {
1690 lib = event.detail.api.lib;
1691 }, false );
1692
1693 var isNumber = function( numeric ) {
1694 return !isNaN( numeric );
1695 };
1696
1697 var goto = function( event ) {
1698 if ( ( !event ) || ( !event.target ) ) {
1699 return;
1700 }
1701
1702 var data = event.target.dataset;
1703 var steps = document.querySelectorAll( ".step" );
1704
1705 // Data-goto-key-list="" & data-goto-next-list="" //////////////////////////////////////////
1706 if ( data.gotoKeyList !== undefined &&
1707 data.gotoNextList !== undefined &&
1708 event.origEvent !== undefined &&
1709 event.origEvent.key !== undefined ) {
1710 var keylist = data.gotoKeyList.split( " " );
1711 var nextlist = data.gotoNextList.split( " " );
1712
1713 if ( keylist.length !== nextlist.length ) {
1714 window.console.log(
1715 "impress goto plugin: data-goto-key-list and data-goto-next-list don't match:"
1716 );
1717 window.console.log( keylist );
1718 window.console.log( nextlist );
1719
1720 // Don't return, allow the other categories to work despite this error
1721 } else {
1722 var index = keylist.indexOf( event.origEvent.key );
1723 if ( index >= 0 ) {
1724 var next = nextlist[ index ];
1725 if ( isNumber( next ) ) {
1726 event.detail.next = steps[ next ];
1727
1728 // If the new next element has its own transitionDuration, we're responsible
1729 // for setting that on the event as well
1730 event.detail.transitionDuration = lib.util.toNumber(
1731 event.detail.next.dataset.transitionDuration,
1732 event.detail.transitionDuration
1733 );
1734 return;
1735 } else {
1736 var newTarget = document.getElementById( next );
1737 if ( newTarget && newTarget.classList.contains( "step" ) ) {
1738 event.detail.next = newTarget;
1739 event.detail.transitionDuration = lib.util.toNumber(
1740 event.detail.next.dataset.transitionDuration,
1741 event.detail.transitionDuration
1742 );
1743 return;
1744 } else {
1745 window.console.log( "impress goto plugin: " + next +
1746 " is not a step in this impress presentation." );
1747 }
1748 }
1749 }
1750 }
1751 }
1752
1753 // Data-goto-next="" & data-goto-prev="" ///////////////////////////////////////////////////
1754
1755 // Handle event.target data-goto-next attribute
1756 if ( isNumber( data.gotoNext ) && event.detail.reason === "next" ) {
1757 event.detail.next = steps[ data.gotoNext ];
1758
1759 // If the new next element has its own transitionDuration, we're responsible for setting
1760 // that on the event as well
1761 event.detail.transitionDuration = lib.util.toNumber(
1762 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1763 );
1764 return;
1765 }
1766 if ( data.gotoNext && event.detail.reason === "next" ) {
1767 var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line
1768 if ( newTarget && newTarget.classList.contains( "step" ) ) {
1769 event.detail.next = newTarget;
1770 event.detail.transitionDuration = lib.util.toNumber(
1771 event.detail.next.dataset.transitionDuration,
1772 event.detail.transitionDuration
1773 );
1774 return;
1775 } else {
1776 window.console.log( "impress goto plugin: " + data.gotoNext +
1777 " is not a step in this impress presentation." );
1778 }
1779 }
1780
1781 // Handle event.target data-goto-prev attribute
1782 if ( isNumber( data.gotoPrev ) && event.detail.reason === "prev" ) {
1783 event.detail.next = steps[ data.gotoPrev ];
1784 event.detail.transitionDuration = lib.util.toNumber(
1785 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1786 );
1787 return;
1788 }
1789 if ( data.gotoPrev && event.detail.reason === "prev" ) {
1790 var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line
1791 if ( newTarget && newTarget.classList.contains( "step" ) ) {
1792 event.detail.next = newTarget;
1793 event.detail.transitionDuration = lib.util.toNumber(
1794 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1795 );
1796 return;
1797 } else {
1798 window.console.log( "impress goto plugin: " + data.gotoPrev +
1799 " is not a step in this impress presentation." );
1800 }
1801 }
1802
1803 // Data-goto="" ///////////////////////////////////////////////////////////////////////////
1804
1805 // Handle event.target data-goto attribute
1806 if ( isNumber( data.goto ) ) {
1807 event.detail.next = steps[ data.goto ];
1808 event.detail.transitionDuration = lib.util.toNumber(
1809 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1810 );
1811 return;
1812 }
1813 if ( data.goto ) {
1814 var newTarget = document.getElementById( data.goto ); // jshint ignore:line
1815 if ( newTarget && newTarget.classList.contains( "step" ) ) {
1816 event.detail.next = newTarget;
1817 event.detail.transitionDuration = lib.util.toNumber(
1818 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1819 );
1820 return;
1821 } else {
1822 window.console.log( "impress goto plugin: " + data.goto +
1823 " is not a step in this impress presentation." );
1824 }
1825 }
1826 };
1827
1828 // Register the plugin to be called in pre-stepleave phase
1829 impress.addPreStepLeavePlugin( goto );
1830
1831} )( document, window );
1832
1833
1834/**
1835 * Help popup plugin
1836 *
1837 * Example:
1838 *
1839 * <!-- Show a help popup at start, or if user presses "H" -->
1840 * <div id="impress-help"></div>
1841 *
1842 * For developers:
1843 *
1844 * Typical use for this plugin, is for plugins that support some keypress, to add a line
1845 * to the help popup produced by this plugin. For example "P: Presenter console".
1846 *
1847 * Copyright 2016 Henrik Ingo (@henrikingo)
1848 * Released under the MIT license.
1849 */
1850/* global window, document */
1851
1852( function( document, window ) {
1853 "use strict";
1854 var rows = [];
1855 var timeoutHandle;
1856
1857 var triggerEvent = function( el, eventName, detail ) {
1858 var event = document.createEvent( "CustomEvent" );
1859 event.initCustomEvent( eventName, true, true, detail );
1860 el.dispatchEvent( event );
1861 };
1862
1863 var renderHelpDiv = function() {
1864 var helpDiv = document.getElementById( "impress-help" );
1865 if ( helpDiv ) {
1866 var html = [];
1867 for ( var row in rows ) {
1868 for ( var arrayItem in row ) {
1869 html.push( rows[ row ][ arrayItem ] );
1870 }
1871 }
1872 if ( html ) {
1873 helpDiv.innerHTML = "<table>\n" + html.join( "\n" ) + "</table>\n";
1874 }
1875 }
1876 };
1877
1878 var toggleHelp = function() {
1879 var helpDiv = document.getElementById( "impress-help" );
1880 if ( !helpDiv ) {
1881 return;
1882 }
1883
1884 if ( helpDiv.style.display === "block" ) {
1885 helpDiv.style.display = "none";
1886 } else {
1887 helpDiv.style.display = "block";
1888 window.clearTimeout( timeoutHandle );
1889 }
1890 };
1891
1892 document.addEventListener( "keyup", function( event ) {
1893
1894 if ( event.keyCode === 72 ) { // "h"
1895 event.preventDefault();
1896 toggleHelp();
1897 }
1898 }, false );
1899
1900 // API
1901 // Other plugins can add help texts, typically if they support an action on a keypress.
1902 /**
1903 * Add a help text to the help popup.
1904 *
1905 * :param: e.detail.command Example: "H"
1906 * :param: e.detail.text Example: "Show this help."
1907 * :param: e.detail.row Row index from 0 to 9 where to place this help text. Example: 0
1908 */
1909 document.addEventListener( "impress:help:add", function( e ) {
1910
1911 // The idea is for the sender of the event to supply a unique row index, used for sorting.
1912 // But just in case two plugins would ever use the same row index, we wrap each row into
1913 // its own array. If there are more than one entry for the same index, they are shown in
1914 // first come, first serve ordering.
1915 var rowIndex = e.detail.row;
1916 if ( typeof rows[ rowIndex ] !== "object" || !rows[ rowIndex ].isArray ) {
1917 rows[ rowIndex ] = [];
1918 }
1919 rows[ e.detail.row ].push( "<tr><td><strong>" + e.detail.command + "</strong></td><td>" +
1920 e.detail.text + "</td></tr>" );
1921 renderHelpDiv();
1922 } );
1923
1924 document.addEventListener( "impress:init", function( e ) {
1925 renderHelpDiv();
1926
1927 // At start, show the help for 7 seconds.
1928 var helpDiv = document.getElementById( "impress-help" );
1929 if ( helpDiv ) {
1930 helpDiv.style.display = "block";
1931 timeoutHandle = window.setTimeout( function() {
1932 var helpDiv = document.getElementById( "impress-help" );
1933 helpDiv.style.display = "none";
1934 }, 7000 );
1935
1936 // Regster callback to empty the help div on teardown
1937 var api = e.detail.api;
1938 api.lib.gc.pushCallback( function() {
1939 window.clearTimeout( timeoutHandle );
1940 helpDiv.style.display = "";
1941 helpDiv.innerHTML = "";
1942 rows = [];
1943 } );
1944 }
1945
1946 // Use our own API to register the help text for "h"
1947 triggerEvent( document, "impress:help:add",
1948 { command: "H", text: "Show this help", row: 0 } );
1949 } );
1950
1951} )( document, window );
1952
1953
1954/**
1955 * Adds a presenter console to impress.js
1956 *
1957 * MIT Licensed, see license.txt.
1958 *
1959 * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)
1960 *
1961 * version: 1.3-dev
1962 *
1963 */
1964
1965// This file contains so much HTML, that we will just respectfully disagree about js
1966/* jshint quotmark:single */
1967/* global navigator, top, setInterval, clearInterval, document, window */
1968
1969( function( document, window ) {
1970 'use strict';
1971
1972 // TODO: Move this to src/lib/util.js
1973 var triggerEvent = function( el, eventName, detail ) {
1974 var event = document.createEvent( 'CustomEvent' );
1975 event.initCustomEvent( eventName, true, true, detail );
1976 el.dispatchEvent( event );
1977 };
1978
1979 // Create Language object depending on browsers language setting
1980 var lang;
1981 switch ( navigator.language ) {
1982 case 'de':
1983 lang = {
1984 'noNotes': '<div class="noNotes">Keine Notizen hierzu</div>',
1985 'restart': 'Neustart',
1986 'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen',
1987 'prev': 'zurück',
1988 'next': 'weiter',
1989 'loading': 'initalisiere',
1990 'ready': 'Bereit',
1991 'moving': 'in Bewegung',
1992 'useAMPM': false
1993 };
1994 break;
1995 case 'en': // jshint ignore:line
1996 default : // jshint ignore:line
1997 lang = {
1998 'noNotes': '<div class="noNotes">No notes for this step</div>',
1999 'restart': 'Restart',
2000 'clickToOpen': 'Click to open speaker console',
2001 'prev': 'Prev',
2002 'next': 'Next',
2003 'loading': 'Loading',
2004 'ready': 'Ready',
2005 'moving': 'Moving',
2006 'useAMPM': false
2007 };
2008 break;
2009 }
2010
2011 // Settings to set iframe in speaker console
2012 const preViewDefaultFactor = 0.7;
2013 const preViewMinimumFactor = 0.5;
2014 const preViewGap = 4;
2015
2016 // This is the default template for the speaker console window
2017 const consoleTemplate = '<!DOCTYPE html>' +
2018 '<html id="impressconsole"><head>' +
2019
2020 // Order is important: If user provides a cssFile, those will win, because they're later
2021 '{{cssStyle}}' +
2022 '{{cssLink}}' +
2023 '</head><body>' +
2024 '<div id="console">' +
2025 '<div id="views">' +
2026 '<iframe id="slideView" scrolling="no"></iframe>' +
2027 '<iframe id="preView" scrolling="no"></iframe>' +
2028 '<div id="blocker"></div>' +
2029 '</div>' +
2030 '<div id="notes"></div>' +
2031 '</div>' +
2032 '<div id="controls"> ' +
2033 '<div id="prev"><a href="#" onclick="impress().prev(); return false;" />' +
2034 '{{prev}}</a></div>' +
2035 '<div id="next"><a href="#" onclick="impress().next(); return false;" />' +
2036 '{{next}}</a></div>' +
2037 '<div id="clock">--:--</div>' +
2038 '<div id="timer" onclick="timerReset()">00m 00s</div>' +
2039 '<div id="status">{{loading}}</div>' +
2040 '</div>' +
2041 '</body></html>';
2042
2043 // Default css location
2044 var cssFileOldDefault = 'css/impressConsole.css';
2045 var cssFile = undefined; // jshint ignore:line
2046
2047 // Css for styling iframs on the console
2048 var cssFileIframeOldDefault = 'css/iframe.css';
2049 var cssFileIframe = undefined; // jshint ignore:line
2050
2051 // All console windows, so that you can call impressConsole() repeatedly.
2052 var allConsoles = {};
2053
2054 // Zero padding helper function:
2055 var zeroPad = function( i ) {
2056 return ( i < 10 ? '0' : '' ) + i;
2057 };
2058
2059 // The console object
2060 var impressConsole = window.impressConsole = function( rootId ) {
2061
2062 rootId = rootId || 'impress';
2063
2064 if ( allConsoles[ rootId ] ) {
2065 return allConsoles[ rootId ];
2066 }
2067
2068 // Root presentation elements
2069 var root = document.getElementById( rootId );
2070
2071 var consoleWindow = null;
2072
2073 var nextStep = function() {
2074 var classes = '';
2075 var nextElement = document.querySelector( '.active' );
2076
2077 // Return to parents as long as there is no next sibling
2078 while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
2079 nextElement = nextElement.parentNode;
2080 }
2081 nextElement = nextElement.nextElementSibling;
2082 while ( nextElement ) {
2083 classes = nextElement.attributes[ 'class' ];
2084 if ( classes && classes.value.indexOf( 'step' ) !== -1 ) {
2085 consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.next;
2086 return nextElement;
2087 }
2088
2089 if ( nextElement.firstElementChild ) { // First go into deep
2090 nextElement = nextElement.firstElementChild;
2091 } else {
2092
2093 // Go to next sibling or through parents until there is a next sibling
2094 while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
2095 nextElement = nextElement.parentNode;
2096 }
2097 nextElement = nextElement.nextElementSibling;
2098 }
2099 }
2100
2101 // No next element. Pick the first
2102 consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.restart;
2103 return document.querySelector( '.step' );
2104 };
2105
2106 // Sync the notes to the step
2107 var onStepLeave = function() {
2108 if ( consoleWindow ) {
2109
2110 // Set notes to next steps notes.
2111 var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
2112 if ( newNotes ) {
2113 newNotes = newNotes.innerHTML;
2114 } else {
2115 newNotes = lang.noNotes;
2116 }
2117 consoleWindow.document.getElementById( 'notes' ).innerHTML = newNotes;
2118
2119 // Set the views
2120 var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
2121 var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
2122 var preSrc = baseURL + '#' + nextStep().id;
2123 var slideView = consoleWindow.document.getElementById( 'slideView' );
2124
2125 // Setting them when they are already set causes glithes in Firefox, so check first:
2126 if ( slideView.src !== slideSrc ) {
2127 slideView.src = slideSrc;
2128 }
2129 var preView = consoleWindow.document.getElementById( 'preView' );
2130 if ( preView.src !== preSrc ) {
2131 preView.src = preSrc;
2132 }
2133
2134 consoleWindow.document.getElementById( 'status' ).innerHTML =
2135 '<span class="moving">' + lang.moving + '</span>';
2136 }
2137 };
2138
2139 // Sync the previews to the step
2140 var onStepEnter = function() {
2141 if ( consoleWindow ) {
2142
2143 // We do everything here again, because if you stopped the previos step to
2144 // early, the onstepleave trigger is not called for that step, so
2145 // we need this to sync things.
2146 var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
2147 if ( newNotes ) {
2148 newNotes = newNotes.innerHTML;
2149 } else {
2150 newNotes = lang.noNotes;
2151 }
2152 var notes = consoleWindow.document.getElementById( 'notes' );
2153 notes.innerHTML = newNotes;
2154 notes.scrollTop = 0;
2155
2156 // Set the views
2157 var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
2158 var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
2159 var preSrc = baseURL + '#' + nextStep().id;
2160 var slideView = consoleWindow.document.getElementById( 'slideView' );
2161
2162 // Setting them when they are already set causes glithes in Firefox, so check first:
2163 if ( slideView.src !== slideSrc ) {
2164 slideView.src = slideSrc;
2165 }
2166 var preView = consoleWindow.document.getElementById( 'preView' );
2167 if ( preView.src !== preSrc ) {
2168 preView.src = preSrc;
2169 }
2170
2171 consoleWindow.document.getElementById( 'status' ).innerHTML =
2172 '<span class="ready">' + lang.ready + '</span>';
2173 }
2174 };
2175
2176 // Sync substeps
2177 var onSubstep = function( event ) {
2178 if ( consoleWindow ) {
2179 if ( event.detail.reason === 'next' ) {
2180 onSubstepShow();
2181 }
2182 if ( event.detail.reason === 'prev' ) {
2183 onSubstepHide();
2184 }
2185 }
2186 };
2187
2188 var onSubstepShow = function() {
2189 var slideView = consoleWindow.document.getElementById( 'slideView' );
2190 triggerEventInView( slideView, 'impress:substep:show' );
2191 };
2192
2193 var onSubstepHide = function() {
2194 var slideView = consoleWindow.document.getElementById( 'slideView' );
2195 triggerEventInView( slideView, 'impress:substep:hide' );
2196 };
2197
2198 var triggerEventInView = function( frame, eventName, detail ) {
2199
2200 // Note: Unfortunately Chrome does not allow createEvent on file:// URLs, so this won't
2201 // work. This does work on Firefox, and should work if viewing the presentation on a
2202 // http:// URL on Chrome.
2203 var event = frame.contentDocument.createEvent( 'CustomEvent' );
2204 event.initCustomEvent( eventName, true, true, detail );
2205 frame.contentDocument.dispatchEvent( event );
2206 };
2207
2208 var spaceHandler = function() {
2209 var notes = consoleWindow.document.getElementById( 'notes' );
2210 if ( notes.scrollTopMax - notes.scrollTop > 20 ) {
2211 notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8;
2212 } else {
2213 window.impress().next();
2214 }
2215 };
2216
2217 var timerReset = function() {
2218 consoleWindow.timerStart = new Date();
2219 };
2220
2221 // Show a clock
2222 var clockTick = function() {
2223 var now = new Date();
2224 var hours = now.getHours();
2225 var minutes = now.getMinutes();
2226 var seconds = now.getSeconds();
2227 var ampm = '';
2228
2229 if ( lang.useAMPM ) {
2230 ampm = ( hours < 12 ) ? 'AM' : 'PM';
2231 hours = ( hours > 12 ) ? hours - 12 : hours;
2232 hours = ( hours === 0 ) ? 12 : hours;
2233 }
2234
2235 // Clock
2236 var clockStr = zeroPad( hours ) + ':' + zeroPad( minutes ) + ':' + zeroPad( seconds ) +
2237 ' ' + ampm;
2238 consoleWindow.document.getElementById( 'clock' ).firstChild.nodeValue = clockStr;
2239
2240 // Timer
2241 seconds = Math.floor( ( now - consoleWindow.timerStart ) / 1000 );
2242 minutes = Math.floor( seconds / 60 );
2243 seconds = Math.floor( seconds % 60 );
2244 consoleWindow.document.getElementById( 'timer' ).firstChild.nodeValue =
2245 zeroPad( minutes ) + 'm ' + zeroPad( seconds ) + 's';
2246
2247 if ( !consoleWindow.initialized ) {
2248
2249 // Nudge the slide windows after load, or they will scrolled wrong on Firefox.
2250 consoleWindow.document.getElementById( 'slideView' ).contentWindow.scrollTo( 0, 0 );
2251 consoleWindow.document.getElementById( 'preView' ).contentWindow.scrollTo( 0, 0 );
2252 consoleWindow.initialized = true;
2253 }
2254 };
2255
2256 var registerKeyEvent = function( keyCodes, handler, window ) {
2257 if ( window === undefined ) {
2258 window = consoleWindow;
2259 }
2260
2261 // Prevent default keydown action when one of supported key is pressed
2262 window.document.addEventListener( 'keydown', function( event ) {
2263 if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
2264 keyCodes.indexOf( event.keyCode ) !== -1 ) {
2265 event.preventDefault();
2266 }
2267 }, false );
2268
2269 // Trigger impress action on keyup
2270 window.document.addEventListener( 'keyup', function( event ) {
2271 if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
2272 keyCodes.indexOf( event.keyCode ) !== -1 ) {
2273 handler();
2274 event.preventDefault();
2275 }
2276 }, false );
2277 };
2278
2279 var consoleOnLoad = function() {
2280 var slideView = consoleWindow.document.getElementById( 'slideView' );
2281 var preView = consoleWindow.document.getElementById( 'preView' );
2282
2283 // Firefox:
2284 slideView.contentDocument.body.classList.add( 'impress-console' );
2285 preView.contentDocument.body.classList.add( 'impress-console' );
2286 if ( cssFileIframe !== undefined ) {
2287 slideView.contentDocument.head.insertAdjacentHTML(
2288 'beforeend',
2289 '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
2290 );
2291 preView.contentDocument.head.insertAdjacentHTML(
2292 'beforeend',
2293 '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
2294 );
2295 }
2296
2297 // Chrome:
2298 slideView.addEventListener( 'load', function() {
2299 slideView.contentDocument.body.classList.add( 'impress-console' );
2300 if ( cssFileIframe !== undefined ) {
2301 slideView.contentDocument.head.insertAdjacentHTML(
2302 'beforeend',
2303 '<link rel="stylesheet" type="text/css" href="' +
2304 cssFileIframe + '">'
2305 );
2306 }
2307 } );
2308 preView.addEventListener( 'load', function() {
2309 preView.contentDocument.body.classList.add( 'impress-console' );
2310 if ( cssFileIframe !== undefined ) {
2311 preView.contentDocument.head.insertAdjacentHTML(
2312 'beforeend',
2313 '<link rel="stylesheet" type="text/css" href="' +
2314 cssFileIframe + '">' );
2315 }
2316 } );
2317 };
2318
2319 var open = function() {
2320 if ( top.isconsoleWindow ) {
2321 return;
2322 }
2323
2324 if ( consoleWindow && !consoleWindow.closed ) {
2325 consoleWindow.focus();
2326 } else {
2327 consoleWindow = window.open( '', 'impressConsole' );
2328
2329 // If opening failes this may be because the browser prevents this from
2330 // not (or less) interactive JavaScript...
2331 if ( consoleWindow == null ) {
2332
2333 // ... so I add a button to klick.
2334 // workaround on firefox
2335 var message = document.createElement( 'div' );
2336 message.id = 'impress-console-button';
2337 message.style.position = 'fixed';
2338 message.style.left = 0;
2339 message.style.top = 0;
2340 message.style.right = 0;
2341 message.style.bottom = 0;
2342 message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
2343 var clickStr = 'var x = document.getElementById(\'impress-console-button\');' +
2344 'x.parentNode.removeChild(x);' +
2345 'var r = document.getElementById(\'' + rootId + '\');' +
2346 'impress(\'' + rootId +
2347 '\').lib.util.triggerEvent(r, \'impress:console:open\', {})';
2348 var styleStr = 'margin: 25vh 25vw;width:50vw;height:50vh;';
2349 message.innerHTML = '<button style="' + styleStr + '" ' +
2350 'onclick="' + clickStr + '">' +
2351 lang.clickToOpen +
2352 '</button>';
2353 document.body.appendChild( message );
2354 return;
2355 }
2356
2357 var cssLink = '';
2358 if ( cssFile !== undefined ) {
2359 cssLink = '<link rel="stylesheet" type="text/css" media="screen" href="' +
2360 cssFile + '">';
2361 }
2362
2363 // This sets the window location to the main window location, so css can be loaded:
2364 consoleWindow.document.open();
2365
2366 // Write the template:
2367 consoleWindow.document.write(
2368
2369 // CssStyleStr is lots of inline <style></style> defined at the end of this file
2370 consoleTemplate.replace( '{{cssStyle}}', cssStyleStr() )
2371 .replace( '{{cssLink}}', cssLink )
2372 .replace( /{{.*?}}/gi, function( x ) {
2373 return lang[ x.substring( 2, x.length - 2 ) ]; }
2374 )
2375 );
2376 consoleWindow.document.title = 'Speaker Console (' + document.title + ')';
2377 consoleWindow.impress = window.impress;
2378
2379 // We set this flag so we can detect it later, to prevent infinite popups.
2380 consoleWindow.isconsoleWindow = true;
2381
2382 // Set the onload function:
2383 consoleWindow.onload = consoleOnLoad;
2384
2385 // Add clock tick
2386 consoleWindow.timerStart = new Date();
2387 consoleWindow.timerReset = timerReset;
2388 consoleWindow.clockInterval = setInterval( allConsoles[ rootId ].clockTick, 1000 );
2389
2390 // Keyboard navigation handlers
2391 // 33: pg up, 37: left, 38: up
2392 registerKeyEvent( [ 33, 37, 38 ], window.impress().prev );
2393
2394 // 34: pg down, 39: right, 40: down
2395 registerKeyEvent( [ 34, 39, 40 ], window.impress().next );
2396
2397 // 32: space
2398 registerKeyEvent( [ 32 ], spaceHandler );
2399
2400 // 82: R
2401 registerKeyEvent( [ 82 ], timerReset );
2402
2403 // Cleanup
2404 consoleWindow.onbeforeunload = function() {
2405
2406 // I don't know why onunload doesn't work here.
2407 clearInterval( consoleWindow.clockInterval );
2408 };
2409
2410 // It will need a little nudge on Firefox, but only after loading:
2411 onStepEnter();
2412 consoleWindow.initialized = false;
2413 consoleWindow.document.close();
2414
2415 //Catch any window resize to pass size on
2416 window.onresize = resize;
2417 consoleWindow.onresize = resize;
2418
2419 return consoleWindow;
2420 }
2421 };
2422
2423 var resize = function() {
2424 var slideView = consoleWindow.document.getElementById( 'slideView' );
2425 var preView = consoleWindow.document.getElementById( 'preView' );
2426
2427 // Get ratio of presentation
2428 var ratio = window.innerHeight / window.innerWidth;
2429
2430 // Get size available for views
2431 var views = consoleWindow.document.getElementById( 'views' );
2432
2433 // SlideView may have a border or some padding:
2434 // asuming same border width on both direktions
2435 var delta = slideView.offsetWidth - slideView.clientWidth;
2436
2437 // Set views
2438 var slideViewWidth = ( views.clientWidth - delta );
2439 var slideViewHeight = Math.floor( slideViewWidth * ratio );
2440
2441 var preViewTop = slideViewHeight + preViewGap;
2442
2443 var preViewWidth = Math.floor( slideViewWidth * preViewDefaultFactor );
2444 var preViewHeight = Math.floor( slideViewHeight * preViewDefaultFactor );
2445
2446 // Shrink preview to fit into space available
2447 if ( views.clientHeight - delta < preViewTop + preViewHeight ) {
2448 preViewHeight = views.clientHeight - delta - preViewTop;
2449 preViewWidth = Math.floor( preViewHeight / ratio );
2450 }
2451
2452 // If preview is not high enough forget ratios!
2453 if ( preViewWidth <= Math.floor( slideViewWidth * preViewMinimumFactor ) ) {
2454 slideViewWidth = ( views.clientWidth - delta );
2455 slideViewHeight = Math.floor( ( views.clientHeight - delta - preViewGap ) /
2456 ( 1 + preViewMinimumFactor ) );
2457
2458 preViewTop = slideViewHeight + preViewGap;
2459
2460 preViewWidth = Math.floor( slideViewWidth * preViewMinimumFactor );
2461 preViewHeight = views.clientHeight - delta - preViewTop;
2462 }
2463
2464 // Set the calculated into styles
2465 slideView.style.width = slideViewWidth + 'px';
2466 slideView.style.height = slideViewHeight + 'px';
2467
2468 preView.style.top = preViewTop + 'px';
2469
2470 preView.style.width = preViewWidth + 'px';
2471 preView.style.height = preViewHeight + 'px';
2472 };
2473
2474 var _init = function( cssConsole, cssIframe ) {
2475 if ( cssConsole !== undefined ) {
2476 cssFile = cssConsole;
2477 }
2478
2479 // You can also specify the css in the presentation root div:
2480 // <div id="impress" data-console-css=..." data-console-css-iframe="...">
2481 else if ( root.dataset.consoleCss !== undefined ) {
2482 cssFile = root.dataset.consoleCss;
2483 }
2484
2485 if ( cssIframe !== undefined ) {
2486 cssFileIframe = cssIframe;
2487 } else if ( root.dataset.consoleCssIframe !== undefined ) {
2488 cssFileIframe = root.dataset.consoleCssIframe;
2489 }
2490
2491 // Register the event
2492 root.addEventListener( 'impress:stepleave', onStepLeave );
2493 root.addEventListener( 'impress:stepenter', onStepEnter );
2494 root.addEventListener( 'impress:substep:stepleaveaborted', onSubstep );
2495 root.addEventListener( 'impress:substep:show', onSubstepShow );
2496 root.addEventListener( 'impress:substep:hide', onSubstepHide );
2497
2498 //When the window closes, clean up after ourselves.
2499 window.onunload = function() {
2500 if ( consoleWindow && !consoleWindow.closed ) {
2501 consoleWindow.close();
2502 }
2503 };
2504
2505 //Open speaker console when they press 'p'
2506 registerKeyEvent( [ 80 ], open, window );
2507
2508 //Btw, you can also launch console automatically:
2509 //<div id="impress" data-console-autolaunch="true">
2510 if ( root.dataset.consoleAutolaunch === 'true' ) {
2511 window.open();
2512 }
2513 };
2514
2515 var init = function( cssConsole, cssIframe ) {
2516 if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&
2517 ( cssIframe === undefined || cssIframe === cssFileIframeOldDefault ) ) {
2518 window.console.log( 'impressConsole().init() is deprecated. ' +
2519 'impressConsole is now initialized automatically when you ' +
2520 'call impress().init().' );
2521 }
2522 _init( cssConsole, cssIframe );
2523 };
2524
2525 // New API for impress.js plugins is based on using events
2526 root.addEventListener( 'impress:console:open', function() {
2527 open();
2528 } );
2529
2530 /**
2531 * Register a key code to an event handler
2532 *
2533 * :param: event.detail.keyCodes List of key codes
2534 * :param: event.detail.handler A function registered as the event handler
2535 * :param: event.detail.window The console window to register the keycode in
2536 */
2537 root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {
2538 registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );
2539 } );
2540
2541 // Return the object
2542 allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,
2543 registerKeyEvent: registerKeyEvent, _init: _init };
2544 return allConsoles[ rootId ];
2545
2546 };
2547
2548 // This initializes impressConsole automatically when initializing impress itself
2549 document.addEventListener( 'impress:init', function( event ) {
2550
2551 // Note: impressConsole wants the id string, not the DOM element directly
2552 impressConsole( event.target.id )._init();
2553
2554 // Add 'P' to the help popup
2555 triggerEvent( document, 'impress:help:add',
2556 { command: 'P', text: 'Presenter console', row: 10 } );
2557 } );
2558
2559 // Returns a string to be used inline as a css <style> element in the console window.
2560 // Apologies for length, but hiding it here at the end to keep it away from rest of the code.
2561 var cssStyleStr = function() {
2562 return `<style>
2563 #impressconsole body {
2564 background-color: rgb(255, 255, 255);
2565 padding: 0;
2566 margin: 0;
2567 font-family: verdana, arial, sans-serif;
2568 font-size: 2vw;
2569 }
2570
2571 #impressconsole div#console {
2572 position: absolute;
2573 top: 0.5vw;
2574 left: 0.5vw;
2575 right: 0.5vw;
2576 bottom: 3vw;
2577 margin: 0;
2578 }
2579
2580 #impressconsole div#views, #impressconsole div#notes {
2581 position: absolute;
2582 top: 0;
2583 bottom: 0;
2584 }
2585
2586 #impressconsole div#views {
2587 left: 0;
2588 right: 50vw;
2589 overflow: hidden;
2590 }
2591
2592 #impressconsole div#blocker {
2593 position: absolute;
2594 right: 0;
2595 bottom: 0;
2596 }
2597
2598 #impressconsole div#notes {
2599 left: 50vw;
2600 right: 0;
2601 overflow-x: hidden;
2602 overflow-y: auto;
2603 padding: 0.3ex;
2604 background-color: rgb(255, 255, 255);
2605 border: solid 1px rgb(120, 120, 120);
2606 }
2607
2608 #impressconsole div#notes .noNotes {
2609 color: rgb(200, 200, 200);
2610 }
2611
2612 #impressconsole div#notes p {
2613 margin-top: 0;
2614 }
2615
2616 #impressconsole iframe {
2617 position: absolute;
2618 margin: 0;
2619 padding: 0;
2620 left: 0;
2621 border: solid 1px rgb(120, 120, 120);
2622 }
2623
2624 #impressconsole iframe#slideView {
2625 top: 0;
2626 width: 49vw;
2627 height: 49vh;
2628 }
2629
2630 #impressconsole iframe#preView {
2631 opacity: 0.7;
2632 top: 50vh;
2633 width: 30vw;
2634 height: 30vh;
2635 }
2636
2637 #impressconsole div#controls {
2638 margin: 0;
2639 position: absolute;
2640 bottom: 0.25vw;
2641 left: 0.5vw;
2642 right: 0.5vw;
2643 height: 2.5vw;
2644 background-color: rgb(255, 255, 255);
2645 background-color: rgba(255, 255, 255, 0.6);
2646 }
2647
2648 #impressconsole div#prev, div#next {
2649 }
2650
2651 #impressconsole div#prev a, #impressconsole div#next a {
2652 display: block;
2653 border: solid 1px rgb(70, 70, 70);
2654 border-radius: 0.5vw;
2655 font-size: 1.5vw;
2656 padding: 0.25vw;
2657 text-decoration: none;
2658 background-color: rgb(220, 220, 220);
2659 color: rgb(0, 0, 0);
2660 }
2661
2662 #impressconsole div#prev a:hover, #impressconsole div#next a:hover {
2663 background-color: rgb(245, 245, 245);
2664 }
2665
2666 #impressconsole div#prev {
2667 float: left;
2668 }
2669
2670 #impressconsole div#next {
2671 float: right;
2672 }
2673
2674 #impressconsole div#status {
2675 margin-left: 2em;
2676 margin-right: 2em;
2677 text-align: center;
2678 float: right;
2679 }
2680
2681 #impressconsole div#clock {
2682 margin-left: 2em;
2683 margin-right: 2em;
2684 text-align: center;
2685 float: left;
2686 }
2687
2688 #impressconsole div#timer {
2689 margin-left: 2em;
2690 margin-right: 2em;
2691 text-align: center;
2692 float: left;
2693 }
2694
2695 #impressconsole span.moving {
2696 color: rgb(255, 0, 0);
2697 }
2698
2699 #impressconsole span.ready {
2700 color: rgb(0, 128, 0);
2701 }
2702 </style>`;
2703 };
2704
2705} )( document, window );
2706
2707/**
2708 * Mobile devices support
2709 *
2710 * Allow presentation creators to hide all but 3 slides, to save resources, particularly on mobile
2711 * devices, using classes body.impress-mobile, .step.prev, .step.active and .step.next.
2712 *
2713 * Note: This plugin does not take into account possible redirections done with skip, goto etc
2714 * plugins. Basically it wouldn't work as intended in such cases, but the active step will at least
2715 * be correct.
2716 *
2717 * Adapted to a plugin from a submission by @Kzeni:
2718 * https://github.com/impress/impress.js/issues/333
2719 */
2720/* global document, navigator */
2721( function( document ) {
2722 "use strict";
2723
2724 var getNextStep = function( el ) {
2725 var steps = document.querySelectorAll( ".step" );
2726 for ( var i = 0; i < steps.length; i++ ) {
2727 if ( steps[ i ] === el ) {
2728 if ( i + 1 < steps.length ) {
2729 return steps[ i + 1 ];
2730 } else {
2731 return steps[ 0 ];
2732 }
2733 }
2734 }
2735 };
2736 var getPrevStep = function( el ) {
2737 var steps = document.querySelectorAll( ".step" );
2738 for ( var i = steps.length - 1; i >= 0; i-- ) {
2739 if ( steps[ i ] === el ) {
2740 if ( i - 1 >= 0 ) {
2741 return steps[ i - 1 ];
2742 } else {
2743 return steps[ steps.length - 1 ];
2744 }
2745 }
2746 }
2747 };
2748
2749 // Detect mobile browsers & add CSS class as appropriate.
2750 document.addEventListener( "impress:init", function( event ) {
2751 var body = document.body;
2752 if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
2753 navigator.userAgent
2754 ) ) {
2755 body.classList.add( "impress-mobile" );
2756 }
2757
2758 // Unset all this on teardown
2759 var api = event.detail.api;
2760 api.lib.gc.pushCallback( function() {
2761 document.body.classList.remove( "impress-mobile" );
2762 var prev = document.getElementsByClassName( "prev" )[ 0 ];
2763 var next = document.getElementsByClassName( "next" )[ 0 ];
2764 if ( typeof prev !== "undefined" ) {
2765 prev.classList.remove( "prev" );
2766 }
2767 if ( typeof next !== "undefined" ) {
2768 next.classList.remove( "next" );
2769 }
2770 } );
2771 } );
2772
2773 // Add prev and next classes to the siblings of the newly entered active step element
2774 // Remove prev and next classes from their current step elements
2775 // Note: As an exception we break namespacing rules, as these are useful general purpose
2776 // classes. (Naming rules would require us to use css classes mobile-next and mobile-prev,
2777 // based on plugin name.)
2778 document.addEventListener( "impress:stepenter", function( event ) {
2779 var oldprev = document.getElementsByClassName( "prev" )[ 0 ];
2780 var oldnext = document.getElementsByClassName( "next" )[ 0 ];
2781
2782 var prev = getPrevStep( event.target );
2783 prev.classList.add( "prev" );
2784 var next = getNextStep( event.target );
2785 next.classList.add( "next" );
2786
2787 if ( typeof oldprev !== "undefined" ) {
2788 oldprev.classList.remove( "prev" );
2789 }
2790 if ( typeof oldnext !== "undefined" ) {
2791 oldnext.classList.remove( "next" );
2792 }
2793 } );
2794} )( document );
2795
2796
2797/**
2798 * Mouse timeout plugin
2799 *
2800 * After 3 seconds of mouse inactivity, add the css class
2801 * `body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the
2802 * class.
2803 *
2804 * The use case for this plugin is to use CSS to hide elements from the screen
2805 * and only make them visible when the mouse is moved. Examples where this
2806 * might be used are: the toolbar from the toolbar plugin, and the mouse cursor
2807 * itself.
2808 *
2809 * Example CSS:
2810 *
2811 * body.impress-mouse-timeout {
2812 * cursor: none;
2813 * }
2814 * body.impress-mouse-timeout div#impress-toolbar {
2815 * display: none;
2816 * }
2817 *
2818 *
2819 * Copyright 2016 Henrik Ingo (@henrikingo)
2820 * Released under the MIT license.
2821 */
2822/* global window, document */
2823( function( document, window ) {
2824 "use strict";
2825 var timeout = 3;
2826 var timeoutHandle;
2827
2828 var hide = function() {
2829
2830 // Mouse is now inactive
2831 document.body.classList.add( "impress-mouse-timeout" );
2832 };
2833
2834 var show = function() {
2835 if ( timeoutHandle ) {
2836 window.clearTimeout( timeoutHandle );
2837 }
2838
2839 // Mouse is now active
2840 document.body.classList.remove( "impress-mouse-timeout" );
2841
2842 // Then set new timeout after which it is considered inactive again
2843 timeoutHandle = window.setTimeout( hide, timeout * 1000 );
2844 };
2845
2846 document.addEventListener( "impress:init", function( event ) {
2847 var api = event.detail.api;
2848 var gc = api.lib.gc;
2849 gc.addEventListener( document, "mousemove", show );
2850 gc.addEventListener( document, "click", show );
2851 gc.addEventListener( document, "touch", show );
2852
2853 // Set first timeout
2854 show();
2855
2856 // Unset all this on teardown
2857 gc.pushCallback( function() {
2858 window.clearTimeout( timeoutHandle );
2859 document.body.classList.remove( "impress-mouse-timeout" );
2860 } );
2861 }, false );
2862
2863} )( document, window );
2864
2865/**
2866 * Navigation events plugin
2867 *
2868 * As you can see this part is separate from the impress.js core code.
2869 * It's because these navigation actions only need what impress.js provides with
2870 * its simple API.
2871 *
2872 * This plugin is what we call an _init plugin_. It's a simple kind of
2873 * impress.js plugin. When loaded, it starts listening to the `impress:init`
2874 * event. That event listener initializes the plugin functionality - in this
2875 * case we listen to some keypress and mouse events. The only dependencies on
2876 * core impress.js functionality is the `impress:init` method, as well as using
2877 * the public api `next(), prev(),` etc when keys are pressed.
2878 *
2879 * Copyright 2011-2012 Bartek Szopka (@bartaz)
2880 * Released under the MIT license.
2881 * ------------------------------------------------
2882 * author: Bartek Szopka
2883 * version: 0.5.3
2884 * url: http://bartaz.github.com/impress.js/
2885 * source: http://github.com/bartaz/impress.js/
2886 *
2887 */
2888/* global document */
2889( function( document ) {
2890 "use strict";
2891
2892 // Wait for impress.js to be initialized
2893 document.addEventListener( "impress:init", function( event ) {
2894
2895 // Getting API from event data.
2896 // So you don't event need to know what is the id of the root element
2897 // or anything. `impress:init` event data gives you everything you
2898 // need to control the presentation that was just initialized.
2899 var api = event.detail.api;
2900 var gc = api.lib.gc;
2901 var util = api.lib.util;
2902
2903 // Supported keys are:
2904 // [space] - quite common in presentation software to move forward
2905 // [up] [right] / [down] [left] - again common and natural addition,
2906 // [pgdown] / [pgup] - often triggered by remote controllers,
2907 // [tab] - this one is quite controversial, but the reason it ended up on
2908 // this list is quite an interesting story... Remember that strange part
2909 // in the impress.js code where window is scrolled to 0,0 on every presentation
2910 // step, because sometimes browser scrolls viewport because of the focused element?
2911 // Well, the [tab] key by default navigates around focusable elements, so clicking
2912 // it very often caused scrolling to focused element and breaking impress.js
2913 // positioning. I didn't want to just prevent this default action, so I used [tab]
2914 // as another way to moving to next step... And yes, I know that for the sake of
2915 // consistency I should add [shift+tab] as opposite action...
2916 var isNavigationEvent = function( event ) {
2917
2918 // Don't trigger navigation for example when user returns to browser window with ALT+TAB
2919 if ( event.altKey || event.ctrlKey || event.metaKey ) {
2920 return false;
2921 }
2922
2923 // In the case of TAB, we force step navigation always, overriding the browser
2924 // navigation between input elements, buttons and links.
2925 if ( event.keyCode === 9 ) {
2926 return true;
2927 }
2928
2929 // With the sole exception of TAB, we also ignore keys pressed if shift is down.
2930 if ( event.shiftKey ) {
2931 return false;
2932 }
2933
2934 if ( ( event.keyCode >= 32 && event.keyCode <= 34 ) ||
2935 ( event.keyCode >= 37 && event.keyCode <= 40 ) ) {
2936 return true;
2937 }
2938 };
2939
2940 // KEYBOARD NAVIGATION HANDLERS
2941
2942 // Prevent default keydown action when one of supported key is pressed.
2943 gc.addEventListener( document, "keydown", function( event ) {
2944 if ( isNavigationEvent( event ) ) {
2945 event.preventDefault();
2946 }
2947 }, false );
2948
2949 // Trigger impress action (next or prev) on keyup.
2950 gc.addEventListener( document, "keyup", function( event ) {
2951 if ( isNavigationEvent( event ) ) {
2952 if ( event.shiftKey ) {
2953 switch ( event.keyCode ) {
2954 case 9: // Shift+tab
2955 api.prev();
2956 break;
2957 }
2958 } else {
2959 switch ( event.keyCode ) {
2960 case 33: // Pg up
2961 case 37: // Left
2962 case 38: // Up
2963 api.prev( event );
2964 break;
2965 case 9: // Tab
2966 case 32: // Space
2967 case 34: // Pg down
2968 case 39: // Right
2969 case 40: // Down
2970 api.next( event );
2971 break;
2972 }
2973 }
2974 event.preventDefault();
2975 }
2976 }, false );
2977
2978 // Delegated handler for clicking on the links to presentation steps
2979 gc.addEventListener( document, "click", function( event ) {
2980
2981 // Event delegation with "bubbling"
2982 // check if event target (or any of its parents is a link)
2983 var target = event.target;
2984 try {
2985 while ( ( target.tagName !== "A" ) &&
2986 ( target !== document.documentElement ) ) {
2987 target = target.parentNode;
2988 }
2989
2990 if ( target.tagName === "A" ) {
2991 var href = target.getAttribute( "href" );
2992
2993 // If it's a link to presentation step, target this step
2994 if ( href && href[ 0 ] === "#" ) {
2995 target = document.getElementById( href.slice( 1 ) );
2996 }
2997 }
2998
2999 if ( api.goto( target ) ) {
3000 event.stopImmediatePropagation();
3001 event.preventDefault();
3002 }
3003 }
3004 catch ( err ) {
3005
3006 // For example, when clicking on the button to launch speaker console, the button
3007 // is immediately deleted from the DOM. In this case target is a DOM element when
3008 // we get it, but turns out to be null if you try to actually do anything with it.
3009 if ( err instanceof TypeError &&
3010 err.message === "target is null" ) {
3011 return;
3012 }
3013 throw err;
3014 }
3015 }, false );
3016
3017 // Delegated handler for clicking on step elements
3018 gc.addEventListener( document, "click", function( event ) {
3019 var target = event.target;
3020 try {
3021
3022 // Find closest step element that is not active
3023 while ( !( target.classList.contains( "step" ) &&
3024 !target.classList.contains( "active" ) ) &&
3025 ( target !== document.documentElement ) ) {
3026 target = target.parentNode;
3027 }
3028
3029 if ( api.goto( target ) ) {
3030 event.preventDefault();
3031 }
3032 }
3033 catch ( err ) {
3034
3035 // For example, when clicking on the button to launch speaker console, the button
3036 // is immediately deleted from the DOM. In this case target is a DOM element when
3037 // we get it, but turns out to be null if you try to actually do anything with it.
3038 if ( err instanceof TypeError &&
3039 err.message === "target is null" ) {
3040 return;
3041 }
3042 throw err;
3043 }
3044 }, false );
3045
3046 // Add a line to the help popup
3047 util.triggerEvent( document, "impress:help:add", { command: "Left & Right",
3048 text: "Previous & Next step",
3049 row: 1 } );
3050
3051 }, false );
3052
3053} )( document );
3054
3055
3056/**
3057 * Navigation UI plugin
3058 *
3059 * This plugin provides UI elements "back", "forward" and a list to select
3060 * a specific slide number.
3061 *
3062 * The navigation controls are added to the toolbar plugin via DOM events. User must enable the
3063 * toolbar in a presentation to have them visible.
3064 *
3065 * Copyright 2016 Henrik Ingo (@henrikingo)
3066 * Released under the MIT license.
3067 */
3068
3069// This file contains so much HTML, that we will just respectfully disagree about js
3070/* jshint quotmark:single */
3071/* global document */
3072
3073( function( document ) {
3074 'use strict';
3075 var toolbar;
3076 var api;
3077 var root;
3078 var steps;
3079 var hideSteps = [];
3080 var prev;
3081 var select;
3082 var next;
3083
3084 var triggerEvent = function( el, eventName, detail ) {
3085 var event = document.createEvent( 'CustomEvent' );
3086 event.initCustomEvent( eventName, true, true, detail );
3087 el.dispatchEvent( event );
3088 };
3089
3090 var makeDomElement = function( html ) {
3091 var tempDiv = document.createElement( 'div' );
3092 tempDiv.innerHTML = html;
3093 return tempDiv.firstChild;
3094 };
3095
3096 var selectOptionsHtml = function() {
3097 var options = '';
3098 for ( var i = 0; i < steps.length; i++ ) {
3099
3100 // Omit steps that are listed as hidden from select widget
3101 if ( hideSteps.indexOf( steps[ i ] ) < 0 ) {
3102 options = options + '<option value="' + steps[ i ].id + '">' + // jshint ignore:line
3103 steps[ i ].id + '</option>' + '\n'; // jshint ignore:line
3104 }
3105 }
3106 return options;
3107 };
3108
3109 var addNavigationControls = function( event ) {
3110 api = event.detail.api;
3111 var gc = api.lib.gc;
3112 root = event.target;
3113 steps = root.querySelectorAll( '.step' );
3114
3115 var prevHtml = '<button id="impress-navigation-ui-prev" title="Previous" ' +
3116 'class="impress-navigation-ui"><</button>';
3117 var selectHtml = '<select id="impress-navigation-ui-select" title="Go to" ' +
3118 'class="impress-navigation-ui">' + '\n' +
3119 selectOptionsHtml() +
3120 '</select>';
3121 var nextHtml = '<button id="impress-navigation-ui-next" title="Next" ' +
3122 'class="impress-navigation-ui">></button>';
3123
3124 prev = makeDomElement( prevHtml );
3125 prev.addEventListener( 'click',
3126 function() {
3127 api.prev();
3128 } );
3129 select = makeDomElement( selectHtml );
3130 select.addEventListener( 'change',
3131 function( event ) {
3132 api.goto( event.target.value );
3133 } );
3134 gc.addEventListener( root, 'impress:steprefresh', function( event ) {
3135
3136 // As impress.js core now allows to dynamically edit the steps, including adding,
3137 // removing, and reordering steps, we need to requery and redraw the select list on
3138 // every stepenter event.
3139 steps = root.querySelectorAll( '.step' );
3140 select.innerHTML = '\n' + selectOptionsHtml();
3141
3142 // Make sure the list always shows the step we're actually on, even if it wasn't
3143 // selected from the list
3144 select.value = event.target.id;
3145 } );
3146 next = makeDomElement( nextHtml );
3147 next.addEventListener( 'click',
3148 function() {
3149 api.next();
3150 } );
3151
3152 triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev } );
3153 triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: select } );
3154 triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: next } );
3155
3156 };
3157
3158 // API for not listing given step in the select widget.
3159 // For example, if you set class="skip" on some element, you may not want it to show up in the
3160 // list either. Otoh we cannot assume that, or anything else, so steps that user wants omitted
3161 // must be specifically added with this API call.
3162 document.addEventListener( 'impress:navigation-ui:hideStep', function( event ) {
3163 hideSteps.push( event.target );
3164 if ( select ) {
3165 select.innerHTML = selectOptionsHtml();
3166 }
3167 }, false );
3168
3169 // Wait for impress.js to be initialized
3170 document.addEventListener( 'impress:init', function( event ) {
3171 toolbar = document.querySelector( '#impress-toolbar' );
3172 if ( toolbar ) {
3173 addNavigationControls( event );
3174 }
3175 }, false );
3176
3177} )( document );
3178
3179
3180/* global document */
3181( function( document ) {
3182 "use strict";
3183 var root;
3184 var stepids = [];
3185
3186 // Get stepids from the steps under impress root
3187 var getSteps = function() {
3188 stepids = [];
3189 var steps = root.querySelectorAll( ".step" );
3190 for ( var i = 0; i < steps.length; i++ )
3191 {
3192 stepids[ i + 1 ] = steps[ i ].id;
3193 }
3194 };
3195
3196 // Wait for impress.js to be initialized
3197 document.addEventListener( "impress:init", function( event ) {
3198 root = event.target;
3199 getSteps();
3200 var gc = event.detail.api.lib.gc;
3201 gc.pushCallback( function() {
3202 stepids = [];
3203 if ( progressbar ) {
3204 progressbar.style.width = "";
3205 }
3206 if ( progress ) {
3207 progress.innerHTML = "";
3208 }
3209 } );
3210 } );
3211
3212 var progressbar = document.querySelector( "div.impress-progressbar div" );
3213 var progress = document.querySelector( "div.impress-progress" );
3214
3215 if ( null !== progressbar || null !== progress ) {
3216 document.addEventListener( "impress:stepleave", function( event ) {
3217 updateProgressbar( event.detail.next.id );
3218 } );
3219
3220 document.addEventListener( "impress:steprefresh", function( event ) {
3221 getSteps();
3222 updateProgressbar( event.target.id );
3223 } );
3224
3225 }
3226
3227 function updateProgressbar( slideId ) {
3228 var slideNumber = stepids.indexOf( slideId );
3229 if ( null !== progressbar ) {
3230 var width = 100 / ( stepids.length - 1 ) * ( slideNumber );
3231 progressbar.style.width = width.toFixed( 2 ) + "%";
3232 }
3233 if ( null !== progress ) {
3234 progress.innerHTML = slideNumber + "/" + ( stepids.length - 1 );
3235 }
3236 }
3237} )( document );
3238
3239/**
3240 * Relative Positioning Plugin
3241 *
3242 * This plugin provides support for defining the coordinates of a step relative
3243 * to the previous step. This is often more convenient when creating presentations,
3244 * since as you add, remove or move steps, you may not need to edit the positions
3245 * as much as is the case with the absolute coordinates supported by impress.js
3246 * core.
3247 *
3248 * Example:
3249 *
3250 * <!-- Position step 1000 px to the right and 500 px up from the previous step. -->
3251 * <div class="step" data-rel-x="1000" data-rel-y="500">
3252 *
3253 * Following html attributes are supported for step elements:
3254 *
3255 * data-rel-x
3256 * data-rel-y
3257 * data-rel-z
3258 *
3259 * These values are also inherited from the previous step. This makes it easy to
3260 * create a boring presentation where each slide shifts for example 1000px down
3261 * from the previous.
3262 *
3263 * In addition to plain numbers, which are pixel values, it is also possible to
3264 * define relative positions as a multiple of screen height and width, using
3265 * a unit of "h" and "w", respectively, appended to the number.
3266 *
3267 * Example:
3268 *
3269 * <div class="step" data-rel-x="1.5w" data-rel-y="1.5h">
3270 *
3271 * This plugin is a *pre-init plugin*. It is called synchronously from impress.js
3272 * core at the beginning of `impress().init()`. This allows it to process its own
3273 * data attributes first, and possibly alter the data-x, data-y and data-z attributes
3274 * that will then be processed by `impress().init()`.
3275 *
3276 * (Another name for this kind of plugin might be called a *filter plugin*, but
3277 * *pre-init plugin* is more generic, as a plugin might do whatever it wants in
3278 * the pre-init stage.)
3279 *
3280 * Copyright 2016 Henrik Ingo (@henrikingo)
3281 * Released under the MIT license.
3282 */
3283
3284/* global document, window */
3285
3286( function( document, window ) {
3287 "use strict";
3288
3289 var startingState = {};
3290
3291 /**
3292 * Copied from core impress.js. We currently lack a library mechanism to
3293 * to share utility functions like this.
3294 */
3295 var toNumber = function( numeric, fallback ) {
3296 return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
3297 };
3298
3299 /**
3300 * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.
3301 *
3302 * Returns the computed value in pixels with w/h postfix removed.
3303 */
3304 var toNumberAdvanced = function( numeric, fallback ) {
3305 if ( typeof numeric !== "string" ) {
3306 return toNumber( numeric, fallback );
3307 }
3308 var ratio = numeric.match( /^([+-]*[\d\.]+)([wh])$/ );
3309 if ( ratio == null ) {
3310 return toNumber( numeric, fallback );
3311 } else {
3312 var value = parseFloat( ratio[ 1 ] );
3313 var multiplier = ratio[ 2 ] === "w" ? window.innerWidth : window.innerHeight;
3314 return value * multiplier;
3315 }
3316 };
3317
3318 var computeRelativePositions = function( el, prev ) {
3319 var data = el.dataset;
3320
3321 if ( !prev ) {
3322
3323 // For the first step, inherit these defaults
3324 prev = { x:0, y:0, z:0, relative: { x:0, y:0, z:0 } };
3325 }
3326
3327 var step = {
3328 x: toNumber( data.x, prev.x ),
3329 y: toNumber( data.y, prev.y ),
3330 z: toNumber( data.z, prev.z ),
3331 relative: {
3332 x: toNumberAdvanced( data.relX, prev.relative.x ),
3333 y: toNumberAdvanced( data.relY, prev.relative.y ),
3334 z: toNumberAdvanced( data.relZ, prev.relative.z )
3335 }
3336 };
3337
3338 // Relative position is ignored/zero if absolute is given.
3339 // Note that this also has the effect of resetting any inherited relative values.
3340 if ( data.x !== undefined ) {
3341 step.relative.x = 0;
3342 }
3343 if ( data.y !== undefined ) {
3344 step.relative.y = 0;
3345 }
3346 if ( data.z !== undefined ) {
3347 step.relative.z = 0;
3348 }
3349
3350 // Apply relative position to absolute position, if non-zero
3351 // Note that at this point, the relative values contain a number value of pixels.
3352 step.x = step.x + step.relative.x;
3353 step.y = step.y + step.relative.y;
3354 step.z = step.z + step.relative.z;
3355
3356 return step;
3357 };
3358
3359 var rel = function( root ) {
3360 var steps = root.querySelectorAll( ".step" );
3361 var prev;
3362 startingState[ root.id ] = [];
3363 for ( var i = 0; i < steps.length; i++ ) {
3364 var el = steps[ i ];
3365 startingState[ root.id ].push( {
3366 el: el,
3367 x: el.getAttribute( "data-x" ),
3368 y: el.getAttribute( "data-y" ),
3369 z: el.getAttribute( "data-z" )
3370 } );
3371 var step = computeRelativePositions( el, prev );
3372
3373 // Apply relative position (if non-zero)
3374 el.setAttribute( "data-x", step.x );
3375 el.setAttribute( "data-y", step.y );
3376 el.setAttribute( "data-z", step.z );
3377 prev = step;
3378 }
3379 };
3380
3381 // Register the plugin to be called in pre-init phase
3382 window.impress.addPreInitPlugin( rel );
3383
3384 // Register teardown callback to reset the data.x, .y, .z values.
3385 document.addEventListener( "impress:init", function( event ) {
3386 var root = event.target;
3387 event.detail.api.lib.gc.pushCallback( function() {
3388 var steps = startingState[ root.id ];
3389 var step;
3390 while ( step = steps.pop() ) {
3391 if ( step.x === null ) {
3392 step.el.removeAttribute( "data-x" );
3393 } else {
3394 step.el.setAttribute( "data-x", step.x );
3395 }
3396 if ( step.y === null ) {
3397 step.el.removeAttribute( "data-y" );
3398 } else {
3399 step.el.setAttribute( "data-y", step.y );
3400 }
3401 if ( step.z === null ) {
3402 step.el.removeAttribute( "data-z" );
3403 } else {
3404 step.el.setAttribute( "data-z", step.z );
3405 }
3406 }
3407 delete startingState[ root.id ];
3408 } );
3409 }, false );
3410} )( document, window );
3411
3412
3413/**
3414 * Resize plugin
3415 *
3416 * Rescale the presentation after a window resize.
3417 *
3418 * Copyright 2011-2012 Bartek Szopka (@bartaz)
3419 * Released under the MIT license.
3420 * ------------------------------------------------
3421 * author: Bartek Szopka
3422 * version: 0.5.3
3423 * url: http://bartaz.github.com/impress.js/
3424 * source: http://github.com/bartaz/impress.js/
3425 *
3426 */
3427
3428/* global document, window */
3429
3430( function( document, window ) {
3431 "use strict";
3432
3433 // Wait for impress.js to be initialized
3434 document.addEventListener( "impress:init", function( event ) {
3435 var api = event.detail.api;
3436
3437 // Rescale presentation when window is resized
3438 api.lib.gc.addEventListener( window, "resize", api.lib.util.throttle( function() {
3439
3440 // Force going to active step again, to trigger rescaling
3441 api.goto( document.querySelector( ".step.active" ), 500 );
3442 }, 250 ), false );
3443 }, false );
3444
3445} )( document, window );
3446
3447
3448/**
3449 * Skip Plugin
3450 *
3451 * Example:
3452 *
3453 * <!-- This slide is disabled in presentations, when moving with next()
3454 * and prev() commands, but you can still move directly to it, for
3455 * example with a url (anything using goto()). -->
3456 * <div class="step skip">
3457 *
3458 * Copyright 2016 Henrik Ingo (@henrikingo)
3459 * Released under the MIT license.
3460 */
3461
3462/* global document, window */
3463
3464( function( document, window ) {
3465 "use strict";
3466 var util;
3467
3468 document.addEventListener( "impress:init", function( event ) {
3469 util = event.detail.api.lib.util;
3470 }, false );
3471
3472 var getNextStep = function( el ) {
3473 var steps = document.querySelectorAll( ".step" );
3474 for ( var i = 0; i < steps.length; i++ ) {
3475 if ( steps[ i ] === el ) {
3476 if ( i + 1 < steps.length ) {
3477 return steps[ i + 1 ];
3478 } else {
3479 return steps[ 0 ];
3480 }
3481 }
3482 }
3483 };
3484 var getPrevStep = function( el ) {
3485 var steps = document.querySelectorAll( ".step" );
3486 for ( var i = steps.length - 1; i >= 0; i-- ) {
3487 if ( steps[ i ] === el ) {
3488 if ( i - 1 >= 0 ) {
3489 return steps[ i - 1 ];
3490 } else {
3491 return steps[ steps.length - 1 ];
3492 }
3493 }
3494 }
3495 };
3496
3497 var skip = function( event ) {
3498 if ( ( !event ) || ( !event.target ) ) {
3499 return;
3500 }
3501
3502 if ( event.detail.next.classList.contains( "skip" ) ) {
3503 if ( event.detail.reason === "next" ) {
3504
3505 // Go to the next next step instead
3506 event.detail.next = getNextStep( event.detail.next );
3507
3508 // Recursively call this plugin again, until there's a step not to skip
3509 skip( event );
3510 } else if ( event.detail.reason === "prev" ) {
3511
3512 // Go to the previous previous step instead
3513 event.detail.next = getPrevStep( event.detail.next );
3514 skip( event );
3515 }
3516
3517 // If the new next element has its own transitionDuration, we're responsible for setting
3518 // that on the event as well
3519 event.detail.transitionDuration = util.toNumber(
3520 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
3521 );
3522 }
3523 };
3524
3525 // Register the plugin to be called in pre-stepleave phase
3526 // The weight makes this plugin run early. This is a good thing, because this plugin calls
3527 // itself recursively.
3528 window.impress.addPreStepLeavePlugin( skip, 1 );
3529
3530} )( document, window );
3531
3532
3533/**
3534 * Stop Plugin
3535 *
3536 * Example:
3537 *
3538 * <!-- Stop at this slide.
3539 * (For example, when used on the last slide, this prevents the
3540 * presentation from wrapping back to the beginning.) -->
3541 * <div class="step stop">
3542 *
3543 * Copyright 2016 Henrik Ingo (@henrikingo)
3544 * Released under the MIT license.
3545 */
3546/* global document, window */
3547( function( document, window ) {
3548 "use strict";
3549
3550 var stop = function( event ) {
3551 if ( ( !event ) || ( !event.target ) ) {
3552 return;
3553 }
3554
3555 if ( event.target.classList.contains( "stop" ) ) {
3556 if ( event.detail.reason === "next" ) {
3557 return false;
3558 }
3559 }
3560 };
3561
3562 // Register the plugin to be called in pre-stepleave phase
3563 // The weight makes this plugin run fairly early.
3564 window.impress.addPreStepLeavePlugin( stop, 2 );
3565
3566} )( document, window );
3567
3568
3569/**
3570 * Substep Plugin
3571 *
3572 * Copyright 2017 Henrik Ingo (@henrikingo)
3573 * Released under the MIT license.
3574 */
3575
3576/* global document, window */
3577
3578( function( document, window ) {
3579 "use strict";
3580
3581 // Copied from core impress.js. Good candidate for moving to src/lib/util.js.
3582 var triggerEvent = function( el, eventName, detail ) {
3583 var event = document.createEvent( "CustomEvent" );
3584 event.initCustomEvent( eventName, true, true, detail );
3585 el.dispatchEvent( event );
3586 };
3587
3588 var activeStep = null;
3589 document.addEventListener( "impress:stepenter", function( event ) {
3590 activeStep = event.target;
3591 }, false );
3592
3593 var substep = function( event ) {
3594 if ( ( !event ) || ( !event.target ) ) {
3595 return;
3596 }
3597
3598 var step = event.target;
3599 var el; // Needed by jshint
3600 if ( event.detail.reason === "next" ) {
3601 el = showSubstepIfAny( step );
3602 if ( el ) {
3603
3604 // Send a message to others, that we aborted a stepleave event.
3605 // Autoplay will reload itself from this, as there won't be a stepenter event now.
3606 triggerEvent( step, "impress:substep:stepleaveaborted",
3607 { reason: "next", substep: el } );
3608
3609 // Returning false aborts the stepleave event
3610 return false;
3611 }
3612 }
3613 if ( event.detail.reason === "prev" ) {
3614 el = hideSubstepIfAny( step );
3615 if ( el ) {
3616 triggerEvent( step, "impress:substep:stepleaveaborted",
3617 { reason: "prev", substep: el } );
3618 return false;
3619 }
3620 }
3621 };
3622
3623 var showSubstepIfAny = function( step ) {
3624 var substeps = step.querySelectorAll( ".substep" );
3625 var visible = step.querySelectorAll( ".substep-visible" );
3626 if ( substeps.length > 0 ) {
3627 return showSubstep( substeps, visible );
3628 }
3629 };
3630
3631 var showSubstep = function( substeps, visible ) {
3632 if ( visible.length < substeps.length ) {
3633 var el = substeps[ visible.length ];
3634 el.classList.add( "substep-visible" );
3635 return el;
3636 }
3637 };
3638
3639 var hideSubstepIfAny = function( step ) {
3640 var substeps = step.querySelectorAll( ".substep" );
3641 var visible = step.querySelectorAll( ".substep-visible" );
3642 if ( substeps.length > 0 ) {
3643 return hideSubstep( visible );
3644 }
3645 };
3646
3647 var hideSubstep = function( visible ) {
3648 if ( visible.length > 0 ) {
3649 var el = visible[ visible.length - 1 ];
3650 el.classList.remove( "substep-visible" );
3651 return el;
3652 }
3653 };
3654
3655 // Register the plugin to be called in pre-stepleave phase.
3656 // The weight makes this plugin run before other preStepLeave plugins.
3657 window.impress.addPreStepLeavePlugin( substep, 1 );
3658
3659 // When entering a step, in particular when re-entering, make sure that all substeps are hidden
3660 // at first
3661 document.addEventListener( "impress:stepenter", function( event ) {
3662 var step = event.target;
3663 var visible = step.querySelectorAll( ".substep-visible" );
3664 for ( var i = 0; i < visible.length; i++ ) {
3665 visible[ i ].classList.remove( "substep-visible" );
3666 }
3667 }, false );
3668
3669 // API for others to reveal/hide next substep ////////////////////////////////////////////////
3670 document.addEventListener( "impress:substep:show", function() {
3671 showSubstepIfAny( activeStep );
3672 }, false );
3673
3674 document.addEventListener( "impress:substep:hide", function() {
3675 hideSubstepIfAny( activeStep );
3676 }, false );
3677
3678} )( document, window );
3679
3680
3681/**
3682 * Support for swipe and tap on touch devices
3683 *
3684 * This plugin implements navigation for plugin devices, via swiping left/right,
3685 * or tapping on the left/right edges of the screen.
3686 *
3687 *
3688 *
3689 * Copyright 2015: Andrew Dunai (@and3rson)
3690 * Modified to a plugin, 2016: Henrik Ingo (@henrikingo)
3691 *
3692 * MIT License
3693 */
3694/* global document, window */
3695( function( document, window ) {
3696 "use strict";
3697
3698 // Touch handler to detect swiping left and right based on window size.
3699 // If the difference in X change is bigger than 1/20 of the screen width,
3700 // we simply call an appropriate API function to complete the transition.
3701 var startX = 0;
3702 var lastX = 0;
3703 var lastDX = 0;
3704 var threshold = window.innerWidth / 20;
3705
3706 document.addEventListener( "touchstart", function( event ) {
3707 lastX = startX = event.touches[ 0 ].clientX;
3708 } );
3709
3710 document.addEventListener( "touchmove", function( event ) {
3711 var x = event.touches[ 0 ].clientX;
3712 var diff = x - startX;
3713
3714 // To be used in touchend
3715 lastDX = lastX - x;
3716 lastX = x;
3717
3718 window.impress().swipe( diff / window.innerWidth );
3719 } );
3720
3721 document.addEventListener( "touchend", function() {
3722 var totalDiff = lastX - startX;
3723 if ( Math.abs( totalDiff ) > window.innerWidth / 5 && ( totalDiff * lastDX ) <= 0 ) {
3724 if ( totalDiff > window.innerWidth / 5 && lastDX <= 0 ) {
3725 window.impress().prev();
3726 } else if ( totalDiff < -window.innerWidth / 5 && lastDX >= 0 ) {
3727 window.impress().next();
3728 }
3729 } else if ( Math.abs( lastDX ) > threshold ) {
3730 if ( lastDX < -threshold ) {
3731 window.impress().prev();
3732 } else if ( lastDX > threshold ) {
3733 window.impress().next();
3734 }
3735 } else {
3736
3737 // No movement - move (back) to the current slide
3738 window.impress().goto( document.querySelector( "#impress .step.active" ) );
3739 }
3740 } );
3741
3742 document.addEventListener( "touchcancel", function() {
3743
3744 // Move (back) to the current slide
3745 window.impress().goto( document.querySelector( "#impress .step.active" ) );
3746 } );
3747
3748} )( document, window );
3749
3750/**
3751 * Toolbar plugin
3752 *
3753 * This plugin provides a generic graphical toolbar. Other plugins that
3754 * want to expose a button or other widget, can add those to this toolbar.
3755 *
3756 * Using a single consolidated toolbar for all GUI widgets makes it easier
3757 * to position and style the toolbar rather than having to do that for lots
3758 * of different divs.
3759 *
3760 *
3761 * *** For presentation authors: *****************************************
3762 *
3763 * To add/activate the toolbar in your presentation, add this div:
3764 *
3765 * <div id="impress-toolbar"></div>
3766 *
3767 * Styling the toolbar is left to presentation author. Here's an example CSS:
3768 *
3769 * .impress-enabled div#impress-toolbar {
3770 * position: fixed;
3771 * right: 1px;
3772 * bottom: 1px;
3773 * opacity: 0.6;
3774 * }
3775 * .impress-enabled div#impress-toolbar > span {
3776 * margin-right: 10px;
3777 * }
3778 *
3779 * The [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide
3780 * the toolbar from sight, and only make it visible when mouse is moved.
3781 *
3782 * body.impress-mouse-timeout div#impress-toolbar {
3783 * display: none;
3784 * }
3785 *
3786 *
3787 * *** For plugin authors **********************************************
3788 *
3789 * To add a button to the toolbar, trigger the `impress:toolbar:appendChild`
3790 * or `impress:toolbar:insertBefore` events as appropriate. The detail object
3791 * should contain following parameters:
3792 *
3793 * { group : 1, // integer. Widgets with the same group are grouped inside
3794 * // the same <span> element.
3795 * html : "<button>Click</button>", // The html to add.
3796 * callback : "mycallback", // Toolbar plugin will trigger event
3797 * // `impress:toolbar:added:mycallback` when done.
3798 * before: element } // The reference element for an insertBefore() call.
3799 *
3800 * You should also listen to the `impress:toolbar:added:mycallback` event. At
3801 * this point you can find the new widget in the DOM, and for example add an
3802 * event listener to it.
3803 *
3804 * You are free to use any integer for the group. It's ok to leave gaps. It's
3805 * ok to co-locate with widgets for another plugin, if you think they belong
3806 * together.
3807 *
3808 * See navigation-ui for an example.
3809 *
3810 * Copyright 2016 Henrik Ingo (@henrikingo)
3811 * Released under the MIT license.
3812 */
3813
3814/* global document */
3815
3816( function( document ) {
3817 "use strict";
3818 var toolbar = document.getElementById( "impress-toolbar" );
3819 var groups = [];
3820
3821 /**
3822 * Get the span element that is a child of toolbar, identified by index.
3823 *
3824 * If span element doesn't exist yet, it is created.
3825 *
3826 * Note: Because of Run-to-completion, this is not a race condition.
3827 * https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop#Run-to-completion
3828 *
3829 * :param: index Method will return the element <span id="impress-toolbar-group-{index}">
3830 */
3831 var getGroupElement = function( index ) {
3832 var id = "impress-toolbar-group-" + index;
3833 if ( !groups[ index ] ) {
3834 groups[ index ] = document.createElement( "span" );
3835 groups[ index ].id = id;
3836 var nextIndex = getNextGroupIndex( index );
3837 if ( nextIndex === undefined ) {
3838 toolbar.appendChild( groups[ index ] );
3839 } else {
3840 toolbar.insertBefore( groups[ index ], groups[ nextIndex ] );
3841 }
3842 }
3843 return groups[ index ];
3844 };
3845
3846 /**
3847 * Get the span element from groups[] that is immediately after given index.
3848 *
3849 * This can be used to find the reference node for an insertBefore() call.
3850 * If no element exists at a larger index, returns undefined. (In this case,
3851 * you'd use appendChild() instead.)
3852 *
3853 * Note that index needn't itself exist in groups[].
3854 */
3855 var getNextGroupIndex = function( index ) {
3856 var i = index + 1;
3857 while ( !groups[ i ] && i < groups.length ) {
3858 i++;
3859 }
3860 if ( i < groups.length ) {
3861 return i;
3862 }
3863 };
3864
3865 // API
3866 // Other plugins can add and remove buttons by sending them as events.
3867 // In return, toolbar plugin will trigger events when button was added.
3868 if ( toolbar ) {
3869 /**
3870 * Append a widget inside toolbar span element identified by given group index.
3871 *
3872 * :param: e.detail.group integer specifying the span element where widget will be placed
3873 * :param: e.detail.element a dom element to add to the toolbar
3874 */
3875 toolbar.addEventListener( "impress:toolbar:appendChild", function( e ) {
3876 var group = getGroupElement( e.detail.group );
3877 group.appendChild( e.detail.element );
3878 } );
3879
3880 /**
3881 * Add a widget to toolbar using insertBefore() DOM method.
3882 *
3883 * :param: e.detail.before the reference dom element, before which new element is added
3884 * :param: e.detail.element a dom element to add to the toolbar
3885 */
3886 toolbar.addEventListener( "impress:toolbar:insertBefore", function( e ) {
3887 toolbar.insertBefore( e.detail.element, e.detail.before );
3888 } );
3889
3890 /**
3891 * Remove the widget in e.detail.remove.
3892 */
3893 toolbar.addEventListener( "impress:toolbar:removeWidget", function( e ) {
3894 toolbar.removeChild( e.detail.remove );
3895 } );
3896
3897 document.addEventListener( "impress:init", function( event ) {
3898 var api = event.detail.api;
3899 api.lib.gc.pushCallback( function() {
3900 toolbar.innerHTML = "";
3901 groups = [];
3902 } );
3903 } );
3904 } // If toolbar
3905
3906} )( document );