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