briefing 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/.gitignore +4 -0
  2. data/.gitmodules +3 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +95 -0
  6. data/Rakefile +26 -0
  7. data/bin/briefing +28 -0
  8. data/briefing.gemspec +19 -0
  9. data/lib/briefing/app.rb +20 -0
  10. data/lib/briefing/public/css/print/paper.css +176 -0
  11. data/lib/briefing/public/css/print/pdf.css +160 -0
  12. data/lib/briefing/public/css/reveal.css +1281 -0
  13. data/lib/briefing/public/css/reveal.min.css +7 -0
  14. data/lib/briefing/public/css/shaders/tile-flip.fs +64 -0
  15. data/lib/briefing/public/css/shaders/tile-flip.vs +141 -0
  16. data/lib/briefing/public/css/theme/README.md +5 -0
  17. data/lib/briefing/public/css/theme/beige.css +163 -0
  18. data/lib/briefing/public/css/theme/default.css +163 -0
  19. data/lib/briefing/public/css/theme/night.css +150 -0
  20. data/lib/briefing/public/css/theme/serif.css +150 -0
  21. data/lib/briefing/public/css/theme/simple.css +152 -0
  22. data/lib/briefing/public/css/theme/sky.css +156 -0
  23. data/lib/briefing/public/css/theme/source/beige.scss +50 -0
  24. data/lib/briefing/public/css/theme/source/default.scss +42 -0
  25. data/lib/briefing/public/css/theme/source/night.scss +35 -0
  26. data/lib/briefing/public/css/theme/source/serif.scss +33 -0
  27. data/lib/briefing/public/css/theme/source/simple.scss +38 -0
  28. data/lib/briefing/public/css/theme/source/sky.scss +41 -0
  29. data/lib/briefing/public/css/theme/template/mixins.scss +29 -0
  30. data/lib/briefing/public/css/theme/template/settings.scss +33 -0
  31. data/lib/briefing/public/css/theme/template/theme.scss +163 -0
  32. data/lib/briefing/public/js/reveal.js +1634 -0
  33. data/lib/briefing/public/js/reveal.min.js +8 -0
  34. data/lib/briefing/public/lib/css/zenburn.css +115 -0
  35. data/lib/briefing/public/lib/font/league_gothic-webfont.eot +0 -0
  36. data/lib/briefing/public/lib/font/league_gothic-webfont.svg +230 -0
  37. data/lib/briefing/public/lib/font/league_gothic-webfont.ttf +0 -0
  38. data/lib/briefing/public/lib/font/league_gothic-webfont.woff +0 -0
  39. data/lib/briefing/public/lib/font/league_gothic_license +2 -0
  40. data/lib/briefing/public/lib/js/classList.js +2 -0
  41. data/lib/briefing/public/lib/js/head.min.js +8 -0
  42. data/lib/briefing/public/lib/js/html5shiv.js +7 -0
  43. data/lib/briefing/public/plugin/highlight/highlight.js +14 -0
  44. data/lib/briefing/public/plugin/markdown/markdown.js +37 -0
  45. data/lib/briefing/public/plugin/markdown/showdown.js +62 -0
  46. data/lib/briefing/public/plugin/notes/notes.html +143 -0
  47. data/lib/briefing/public/plugin/notes/notes.js +98 -0
  48. data/lib/briefing/public/plugin/notes-server/client.js +57 -0
  49. data/lib/briefing/public/plugin/notes-server/index.js +58 -0
  50. data/lib/briefing/public/plugin/notes-server/notes.html +139 -0
  51. data/lib/briefing/public/plugin/postmessage/example.html +39 -0
  52. data/lib/briefing/public/plugin/postmessage/postmessage.js +42 -0
  53. data/lib/briefing/public/plugin/remotes/remotes.js +19 -0
  54. data/lib/briefing/public/plugin/zoom-js/zoom.js +251 -0
  55. data/lib/briefing/slides.rb +37 -0
  56. data/lib/briefing/version.rb +3 -0
  57. data/lib/briefing/views/index.erb +79 -0
  58. data/lib/briefing.rb +5 -0
  59. data/vendor/.gitkeep +0 -0
  60. metadata +140 -0
@@ -0,0 +1,1634 @@
1
+ /*!
2
+ * reveal.js
3
+ * http://lab.hakim.se/reveal-js
4
+ * MIT licensed
5
+ *
6
+ * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
7
+ */
8
+ var Reveal = (function(){
9
+
10
+ 'use strict';
11
+
12
+ var SLIDES_SELECTOR = '.reveal .slides section',
13
+ HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
+ VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
+
16
+ // Configurations defaults, can be overridden at initialization time
17
+ config = {
18
+ // Display controls in the bottom right corner
19
+ controls: true,
20
+
21
+ // Display a presentation progress bar
22
+ progress: true,
23
+
24
+ // Push each slide change to the browser history
25
+ history: false,
26
+
27
+ // Enable keyboard shortcuts for navigation
28
+ keyboard: true,
29
+
30
+ // Enable the slide overview mode
31
+ overview: true,
32
+
33
+ // Vertical centering of slides
34
+ center: true,
35
+
36
+ // Loop the presentation
37
+ loop: false,
38
+
39
+ // Change the presentation direction to be RTL
40
+ rtl: false,
41
+
42
+ // Number of milliseconds between automatically proceeding to the
43
+ // next slide, disabled when set to 0, this value can be overwritten
44
+ // by using a data-autoslide attribute on your slides
45
+ autoSlide: 0,
46
+
47
+ // Enable slide navigation via mouse wheel
48
+ mouseWheel: false,
49
+
50
+ // Apply a 3D roll to links on hover
51
+ rollingLinks: true,
52
+
53
+ // Transition style (see /css/theme)
54
+ theme: null,
55
+
56
+ // Transition style
57
+ transition: 'default', // default/cube/page/concave/zoom/linear/none
58
+
59
+ // Script dependencies to load
60
+ dependencies: []
61
+ },
62
+
63
+ // Stores if the next slide should be shown automatically
64
+ // after n milliseconds
65
+ autoSlide = config.autoSlide,
66
+
67
+ // The horizontal and verical index of the currently active slide
68
+ indexh = 0,
69
+ indexv = 0,
70
+
71
+ // The previous and current slide HTML elements
72
+ previousSlide,
73
+ currentSlide,
74
+
75
+ // Slides may hold a data-state attribute which we pick up and apply
76
+ // as a class to the body. This list contains the combined state of
77
+ // all current slides.
78
+ state = [],
79
+
80
+ // Cached references to DOM elements
81
+ dom = {},
82
+
83
+ // Detect support for CSS 3D transforms
84
+ supports3DTransforms = 'WebkitPerspective' in document.body.style ||
85
+ 'MozPerspective' in document.body.style ||
86
+ 'msPerspective' in document.body.style ||
87
+ 'OPerspective' in document.body.style ||
88
+ 'perspective' in document.body.style,
89
+
90
+ supports2DTransforms = 'WebkitTransform' in document.body.style ||
91
+ 'MozTransform' in document.body.style ||
92
+ 'msTransform' in document.body.style ||
93
+ 'OTransform' in document.body.style ||
94
+ 'transform' in document.body.style,
95
+
96
+ // Throttles mouse wheel navigation
97
+ mouseWheelTimeout = 0,
98
+
99
+ // An interval used to automatically move on to the next slide
100
+ autoSlideTimeout = 0,
101
+
102
+ // Delays updates to the URL due to a Chrome thumbnailer bug
103
+ writeURLTimeout = 0,
104
+
105
+ // A delay used to ativate the overview mode
106
+ activateOverviewTimeout = 0,
107
+
108
+ // Holds information about the currently ongoing touch input
109
+ touch = {
110
+ startX: 0,
111
+ startY: 0,
112
+ startSpan: 0,
113
+ startCount: 0,
114
+ handled: false,
115
+ threshold: 80
116
+ };
117
+
118
+ /**
119
+ * Starts up the presentation if the client is capable.
120
+ */
121
+ function initialize( options ) {
122
+ if( ( !supports2DTransforms && !supports3DTransforms ) ) {
123
+ document.body.setAttribute( 'class', 'no-transforms' );
124
+
125
+ // If the browser doesn't support core features we won't be
126
+ // using JavaScript to control the presentation
127
+ return;
128
+ }
129
+
130
+ // Force a layout when the whole page, incl fonts, has loaded
131
+ window.addEventListener( 'load', layout, false );
132
+
133
+ // Copy options over to our config object
134
+ extend( config, options );
135
+
136
+ // Hide the address bar in mobile browsers
137
+ hideAddressBar();
138
+
139
+ // Loads the dependencies and continues to #start() once done
140
+ load();
141
+
142
+ }
143
+
144
+ /**
145
+ * Finds and stores references to DOM elements which are
146
+ * required by the presentation. If a required element is
147
+ * not found, it is created.
148
+ */
149
+ function setupDOM() {
150
+ // Cache references to key DOM elements
151
+ dom.theme = document.querySelector( '#theme' );
152
+ dom.wrapper = document.querySelector( '.reveal' );
153
+ dom.slides = document.querySelector( '.reveal .slides' );
154
+
155
+ // Progress bar
156
+ if( !dom.wrapper.querySelector( '.progress' ) && config.progress ) {
157
+ var progressElement = document.createElement( 'div' );
158
+ progressElement.classList.add( 'progress' );
159
+ progressElement.innerHTML = '<span></span>';
160
+ dom.wrapper.appendChild( progressElement );
161
+ }
162
+
163
+ // Arrow controls
164
+ if( !dom.wrapper.querySelector( '.controls' ) && config.controls ) {
165
+ var controlsElement = document.createElement( 'aside' );
166
+ controlsElement.classList.add( 'controls' );
167
+ controlsElement.innerHTML = '<div class="navigate-left"></div>' +
168
+ '<div class="navigate-right"></div>' +
169
+ '<div class="navigate-up"></div>' +
170
+ '<div class="navigate-down"></div>';
171
+ dom.wrapper.appendChild( controlsElement );
172
+ }
173
+
174
+ // Presentation background element
175
+ if( !dom.wrapper.querySelector( '.state-background' ) ) {
176
+ var backgroundElement = document.createElement( 'div' );
177
+ backgroundElement.classList.add( 'state-background' );
178
+ dom.wrapper.appendChild( backgroundElement );
179
+ }
180
+
181
+ // Overlay graphic which is displayed during the paused mode
182
+ if( !dom.wrapper.querySelector( '.pause-overlay' ) ) {
183
+ var pausedElement = document.createElement( 'div' );
184
+ pausedElement.classList.add( 'pause-overlay' );
185
+ dom.wrapper.appendChild( pausedElement );
186
+ }
187
+
188
+ // Cache references to elements
189
+ dom.progress = document.querySelector( '.reveal .progress' );
190
+ dom.progressbar = document.querySelector( '.reveal .progress span' );
191
+
192
+ if ( config.controls ) {
193
+ dom.controls = document.querySelector( '.reveal .controls' );
194
+
195
+ // There can be multiple instances of controls throughout the page
196
+ dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
197
+ dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
198
+ dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
199
+ dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
200
+ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
201
+ dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Hides the address bar if we're on a mobile device.
207
+ */
208
+ function hideAddressBar() {
209
+ if( navigator.userAgent.match( /(iphone|ipod)/i ) ) {
210
+ // Give the page some scrollable overflow
211
+ document.documentElement.style.overflow = 'scroll';
212
+ document.body.style.height = '120%';
213
+
214
+ // Events that should trigger the address bar to hide
215
+ window.addEventListener( 'load', removeAddressBar, false );
216
+ window.addEventListener( 'orientationchange', removeAddressBar, false );
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Loads the dependencies of reveal.js. Dependencies are
222
+ * defined via the configuration option 'dependencies'
223
+ * and will be loaded prior to starting/binding reveal.js.
224
+ * Some dependencies may have an 'async' flag, if so they
225
+ * will load after reveal.js has been started up.
226
+ */
227
+ function load() {
228
+ var scripts = [],
229
+ scriptsAsync = [];
230
+
231
+ for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
232
+ var s = config.dependencies[i];
233
+
234
+ // Load if there's no condition or the condition is truthy
235
+ if( !s.condition || s.condition() ) {
236
+ if( s.async ) {
237
+ scriptsAsync.push( s.src );
238
+ }
239
+ else {
240
+ scripts.push( s.src );
241
+ }
242
+
243
+ // Extension may contain callback functions
244
+ if( typeof s.callback === 'function' ) {
245
+ head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], s.callback );
246
+ }
247
+ }
248
+ }
249
+
250
+ // Called once synchronous scritps finish loading
251
+ function proceed() {
252
+ if( scriptsAsync.length ) {
253
+ // Load asynchronous scripts
254
+ head.js.apply( null, scriptsAsync );
255
+ }
256
+
257
+ start();
258
+ }
259
+
260
+ if( scripts.length ) {
261
+ head.ready( proceed );
262
+
263
+ // Load synchronous scripts
264
+ head.js.apply( null, scripts );
265
+ }
266
+ else {
267
+ proceed();
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Starts up reveal.js by binding input events and navigating
273
+ * to the current URL deeplink if there is one.
274
+ */
275
+ function start() {
276
+ // Make sure we've got all the DOM elements we need
277
+ setupDOM();
278
+
279
+ // Subscribe to input
280
+ addEventListeners();
281
+
282
+ // Updates the presentation to match the current configuration values
283
+ configure();
284
+
285
+ // Force an initial layout, will thereafter be invoked as the window
286
+ // is resized
287
+ layout();
288
+
289
+ // Read the initial hash
290
+ readURL();
291
+
292
+ // Start auto-sliding if it's enabled
293
+ cueAutoSlide();
294
+
295
+ // Notify listeners that the presentation is ready but use a 1ms
296
+ // timeout to ensure it's not fired synchronously after #initialize()
297
+ setTimeout( function() {
298
+ dispatchEvent( 'ready', {
299
+ 'indexh': indexh,
300
+ 'indexv': indexv,
301
+ 'currentSlide': currentSlide
302
+ } );
303
+ }, 1 );
304
+ }
305
+
306
+ /**
307
+ * Applies the configuration settings from the config object.
308
+ */
309
+ function configure() {
310
+ if( supports3DTransforms === false ) {
311
+ config.transition = 'linear';
312
+ }
313
+
314
+ if( config.controls && dom.controls ) {
315
+ dom.controls.style.display = 'block';
316
+ }
317
+
318
+ if( config.progress && dom.progress ) {
319
+ dom.progress.style.display = 'block';
320
+ }
321
+
322
+ if( config.transition !== 'default' ) {
323
+ dom.wrapper.classList.add( config.transition );
324
+ }
325
+
326
+ if( config.rtl ) {
327
+ dom.wrapper.classList.add( 'rtl' );
328
+ }
329
+
330
+ if( config.center ) {
331
+ dom.wrapper.classList.add( 'center' );
332
+ }
333
+
334
+ if( config.mouseWheel ) {
335
+ document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
336
+ document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
337
+ }
338
+
339
+ // 3D links
340
+ if( config.rollingLinks ) {
341
+ linkify();
342
+ }
343
+
344
+ // Load the theme in the config, if it's not already loaded
345
+ if( config.theme && dom.theme ) {
346
+ var themeURL = dom.theme.getAttribute( 'href' );
347
+ var themeFinder = /[^\/]*?(?=\.css)/;
348
+ var themeName = themeURL.match(themeFinder)[0];
349
+
350
+ if( config.theme !== themeName ) {
351
+ themeURL = themeURL.replace(themeFinder, config.theme);
352
+ dom.theme.setAttribute( 'href', themeURL );
353
+ }
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Binds all event listeners.
359
+ */
360
+ function addEventListeners() {
361
+ document.addEventListener( 'touchstart', onDocumentTouchStart, false );
362
+ document.addEventListener( 'touchmove', onDocumentTouchMove, false );
363
+ document.addEventListener( 'touchend', onDocumentTouchEnd, false );
364
+ window.addEventListener( 'hashchange', onWindowHashChange, false );
365
+ window.addEventListener( 'resize', onWindowResize, false );
366
+
367
+ if( config.keyboard ) {
368
+ document.addEventListener( 'keydown', onDocumentKeyDown, false );
369
+ }
370
+
371
+ if ( config.progress && dom.progress ) {
372
+ dom.progress.addEventListener( 'click', preventAndForward( onProgressClick ), false );
373
+ }
374
+
375
+ if ( config.controls && dom.controls ) {
376
+ var actionEvent = 'ontouchstart' in window ? 'touchstart' : 'click';
377
+ dom.controlsLeft.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateLeft ), false ); } );
378
+ dom.controlsRight.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateRight ), false ); } );
379
+ dom.controlsUp.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateUp ), false ); } );
380
+ dom.controlsDown.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateDown ), false ); } );
381
+ dom.controlsPrev.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigatePrev ), false ); } );
382
+ dom.controlsNext.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateNext ), false ); } );
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Unbinds all event listeners.
388
+ */
389
+ function removeEventListeners() {
390
+ document.removeEventListener( 'keydown', onDocumentKeyDown, false );
391
+ document.removeEventListener( 'touchstart', onDocumentTouchStart, false );
392
+ document.removeEventListener( 'touchmove', onDocumentTouchMove, false );
393
+ document.removeEventListener( 'touchend', onDocumentTouchEnd, false );
394
+ window.removeEventListener( 'hashchange', onWindowHashChange, false );
395
+ window.removeEventListener( 'resize', onWindowResize, false );
396
+
397
+ if ( config.progress && dom.progress ) {
398
+ dom.progress.removeEventListener( 'click', preventAndForward( onProgressClick ), false );
399
+ }
400
+
401
+ if ( config.controls && dom.controls ) {
402
+ var actionEvent = 'ontouchstart' in window ? 'touchstart' : 'click';
403
+ dom.controlsLeft.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateLeft ), false ); } );
404
+ dom.controlsRight.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateRight ), false ); } );
405
+ dom.controlsUp.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateUp ), false ); } );
406
+ dom.controlsDown.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateDown ), false ); } );
407
+ dom.controlsPrev.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigatePrev ), false ); } );
408
+ dom.controlsNext.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateNext ), false ); } );
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Extend object a with the properties of object b.
414
+ * If there's a conflict, object b takes precedence.
415
+ */
416
+ function extend( a, b ) {
417
+ for( var i in b ) {
418
+ a[ i ] = b[ i ];
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Converts the target object to an array.
424
+ */
425
+ function toArray( o ) {
426
+ return Array.prototype.slice.call( o );
427
+ }
428
+
429
+ function each( targets, method, args ) {
430
+ targets.forEach( function( el ) {
431
+ el[method].apply( el, args );
432
+ } );
433
+ }
434
+
435
+ /**
436
+ * Measures the distance in pixels between point a
437
+ * and point b.
438
+ *
439
+ * @param {Object} a point with x/y properties
440
+ * @param {Object} b point with x/y properties
441
+ */
442
+ function distanceBetween( a, b ) {
443
+ var dx = a.x - b.x,
444
+ dy = a.y - b.y;
445
+
446
+ return Math.sqrt( dx*dx + dy*dy );
447
+ }
448
+
449
+ /**
450
+ * Prevents an events defaults behavior calls the
451
+ * specified delegate.
452
+ *
453
+ * @param {Function} delegate The method to call
454
+ * after the wrapper has been executed
455
+ */
456
+ function preventAndForward( delegate ) {
457
+ return function( event ) {
458
+ event.preventDefault();
459
+ delegate.call( null, event );
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Causes the address bar to hide on mobile devices,
465
+ * more vertical space ftw.
466
+ */
467
+ function removeAddressBar() {
468
+ setTimeout( function() {
469
+ window.scrollTo( 0, 1 );
470
+ }, 0 );
471
+ }
472
+
473
+ /**
474
+ * Dispatches an event of the specified type from the
475
+ * reveal DOM element.
476
+ */
477
+ function dispatchEvent( type, properties ) {
478
+ var event = document.createEvent( "HTMLEvents", 1, 2 );
479
+ event.initEvent( type, true, true );
480
+ extend( event, properties );
481
+ dom.wrapper.dispatchEvent( event );
482
+ }
483
+
484
+ /**
485
+ * Wrap all links in 3D goodness.
486
+ */
487
+ function linkify() {
488
+ if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
489
+ var nodes = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
490
+
491
+ for( var i = 0, len = nodes.length; i < len; i++ ) {
492
+ var node = nodes[i];
493
+
494
+ if( node.textContent && !node.querySelector( 'img' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) {
495
+ node.classList.add( 'roll' );
496
+ node.innerHTML = '<span data-title="'+ node.text +'">' + node.innerHTML + '</span>';
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Applies JavaScript-controlled layout rules to the
504
+ * presentation.
505
+ */
506
+ function layout() {
507
+
508
+ if( config.center ) {
509
+
510
+ // Select all slides, vertical and horizontal
511
+ var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
512
+
513
+ // Determine the minimum top offset for slides
514
+ var minTop = -dom.wrapper.offsetHeight / 2;
515
+
516
+ for( var i = 0, len = slides.length; i < len; i++ ) {
517
+ var slide = slides[ i ];
518
+
519
+ // Don't bother update invisible slides
520
+ if( slide.style.display === 'none' ) {
521
+ continue;
522
+ }
523
+
524
+ // Vertical stacks are not centered since their section
525
+ // children will be
526
+ if( slide.classList.contains( 'stack' ) ) {
527
+ slide.style.top = 0;
528
+ }
529
+ else {
530
+ slide.style.top = Math.max( - ( slide.offsetHeight / 2 ) - 20, minTop ) + 'px';
531
+ }
532
+ }
533
+
534
+ }
535
+
536
+ }
537
+
538
+ /**
539
+ * Stores the vertical index of a stack so that the same
540
+ * vertical slide can be selected when navigating to and
541
+ * from the stack.
542
+ *
543
+ * @param {HTMLElement} stack The vertical stack element
544
+ * @param {int} v Index to memorize
545
+ */
546
+ function setPreviousVerticalIndex( stack, v ) {
547
+ if( stack ) {
548
+ stack.setAttribute( 'data-previous-indexv', v || 0 );
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Retrieves the vertical index which was stored using
554
+ * #setPreviousVerticalIndex() or 0 if no previous index
555
+ * exists.
556
+ *
557
+ * @param {HTMLElement} stack The vertical stack element
558
+ */
559
+ function getPreviousVerticalIndex( stack ) {
560
+ if( stack && stack.classList.contains( 'stack' ) ) {
561
+ return parseInt( stack.getAttribute( 'data-previous-indexv' ) || 0, 10 );
562
+ }
563
+
564
+ return 0;
565
+ }
566
+
567
+ /**
568
+ * Displays the overview of slides (quick nav) by
569
+ * scaling down and arranging all slide elements.
570
+ *
571
+ * Experimental feature, might be dropped if perf
572
+ * can't be improved.
573
+ */
574
+ function activateOverview() {
575
+
576
+ // Only proceed if enabled in config
577
+ if( config.overview ) {
578
+
579
+ dom.wrapper.classList.add( 'overview' );
580
+
581
+ clearTimeout( activateOverviewTimeout );
582
+
583
+ // Not the pretties solution, but need to let the overview
584
+ // class apply first so that slides are measured accurately
585
+ // before we can positon them
586
+ activateOverviewTimeout = setTimeout( function(){
587
+
588
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
589
+
590
+ for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
591
+ var hslide = horizontalSlides[i],
592
+ htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * 105 ) + '%, 0%)';
593
+
594
+ hslide.setAttribute( 'data-index-h', i );
595
+ hslide.style.display = 'block';
596
+ hslide.style.WebkitTransform = htransform;
597
+ hslide.style.MozTransform = htransform;
598
+ hslide.style.msTransform = htransform;
599
+ hslide.style.OTransform = htransform;
600
+ hslide.style.transform = htransform;
601
+
602
+ if( hslide.classList.contains( 'stack' ) ) {
603
+
604
+ var verticalSlides = hslide.querySelectorAll( 'section' );
605
+
606
+ for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
607
+ var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
608
+
609
+ var vslide = verticalSlides[j],
610
+ vtransform = 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)';
611
+
612
+ vslide.setAttribute( 'data-index-h', i );
613
+ vslide.setAttribute( 'data-index-v', j );
614
+ vslide.style.display = 'block';
615
+ vslide.style.WebkitTransform = vtransform;
616
+ vslide.style.MozTransform = vtransform;
617
+ vslide.style.msTransform = vtransform;
618
+ vslide.style.OTransform = vtransform;
619
+ vslide.style.transform = vtransform;
620
+
621
+ // Navigate to this slide on click
622
+ vslide.addEventListener( 'click', onOverviewSlideClicked, true );
623
+ }
624
+
625
+ }
626
+ else {
627
+
628
+ // Navigate to this slide on click
629
+ hslide.addEventListener( 'click', onOverviewSlideClicked, true );
630
+
631
+ }
632
+ }
633
+
634
+ layout();
635
+
636
+ }, 10 );
637
+
638
+ }
639
+
640
+ }
641
+
642
+ /**
643
+ * Exits the slide overview and enters the currently
644
+ * active slide.
645
+ */
646
+ function deactivateOverview() {
647
+
648
+ // Only proceed if enabled in config
649
+ if( config.overview ) {
650
+
651
+ clearTimeout( activateOverviewTimeout );
652
+
653
+ dom.wrapper.classList.remove( 'overview' );
654
+
655
+ // Select all slides
656
+ var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
657
+
658
+ for( var i = 0, len = slides.length; i < len; i++ ) {
659
+ var element = slides[i];
660
+
661
+ element.style.display = '';
662
+
663
+ // Resets all transforms to use the external styles
664
+ element.style.WebkitTransform = '';
665
+ element.style.MozTransform = '';
666
+ element.style.msTransform = '';
667
+ element.style.OTransform = '';
668
+ element.style.transform = '';
669
+
670
+ element.removeEventListener( 'click', onOverviewSlideClicked, true );
671
+ }
672
+
673
+ slide( indexh, indexv );
674
+
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Toggles the slide overview mode on and off.
680
+ *
681
+ * @param {Boolean} override Optional flag which overrides the
682
+ * toggle logic and forcibly sets the desired state. True means
683
+ * overview is open, false means it's closed.
684
+ */
685
+ function toggleOverview( override ) {
686
+ if( typeof override === 'boolean' ) {
687
+ override ? activateOverview() : deactivateOverview();
688
+ }
689
+ else {
690
+ isOverviewActive() ? deactivateOverview() : activateOverview();
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Checks if the overview is currently active.
696
+ *
697
+ * @return {Boolean} true if the overview is active,
698
+ * false otherwise
699
+ */
700
+ function isOverviewActive() {
701
+ return dom.wrapper.classList.contains( 'overview' );
702
+ }
703
+
704
+ /**
705
+ * Handling the fullscreen functionality via the fullscreen API
706
+ *
707
+ * @see http://fullscreen.spec.whatwg.org/
708
+ * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
709
+ */
710
+ function enterFullscreen() {
711
+ var element = document.body;
712
+
713
+ // Check which implementation is available
714
+ var requestMethod = element.requestFullScreen ||
715
+ element.webkitRequestFullScreen ||
716
+ element.mozRequestFullScreen ||
717
+ element.msRequestFullScreen;
718
+
719
+ if( requestMethod ) {
720
+ requestMethod.apply( element );
721
+ }
722
+ }
723
+
724
+ /**
725
+ * Enters the paused mode which fades everything on screen to
726
+ * black.
727
+ */
728
+ function pause() {
729
+ dom.wrapper.classList.add( 'paused' );
730
+ }
731
+
732
+ /**
733
+ * Exits from the paused mode.
734
+ */
735
+ function resume() {
736
+ dom.wrapper.classList.remove( 'paused' );
737
+ }
738
+
739
+ /**
740
+ * Toggles the paused mode on and off.
741
+ */
742
+ function togglePause() {
743
+ if( isPaused() ) {
744
+ resume();
745
+ }
746
+ else {
747
+ pause();
748
+ }
749
+ }
750
+
751
+ /**
752
+ * Checks if we are currently in the paused mode.
753
+ */
754
+ function isPaused() {
755
+ return dom.wrapper.classList.contains( 'paused' );
756
+ }
757
+
758
+ /**
759
+ * Steps from the current point in the presentation to the
760
+ * slide which matches the specified horizontal and vertical
761
+ * indices.
762
+ *
763
+ * @param {int} h Horizontal index of the target slide
764
+ * @param {int} v Vertical index of the target slide
765
+ * @param {int} f Optional index of a fragment within the
766
+ * target slide to activate
767
+ */
768
+ function slide( h, v, f ) {
769
+ // Remember where we were at before
770
+ previousSlide = currentSlide;
771
+
772
+ // Query all horizontal slides in the deck
773
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
774
+
775
+ // If no vertical index is specified and the upcoming slide is a
776
+ // stack, resume at its previous vertical index
777
+ if( v === undefined ) {
778
+ v = getPreviousVerticalIndex( horizontalSlides[ h ] );
779
+ }
780
+
781
+ // If we were on a vertical stack, remember what vertical index
782
+ // it was on so we can resume at the same position when returning
783
+ if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {
784
+ setPreviousVerticalIndex( previousSlide.parentNode, indexv );
785
+ }
786
+
787
+ // Remember the state before this slide
788
+ var stateBefore = state.concat();
789
+
790
+ // Reset the state array
791
+ state.length = 0;
792
+
793
+ var indexhBefore = indexh,
794
+ indexvBefore = indexv;
795
+
796
+ // Activate and transition to the new slide
797
+ indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
798
+ indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
799
+
800
+ layout();
801
+
802
+ // Apply the new state
803
+ stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
804
+ // Check if this state existed on the previous slide. If it
805
+ // did, we will avoid adding it repeatedly
806
+ for( var j = 0; j < stateBefore.length; j++ ) {
807
+ if( stateBefore[j] === state[i] ) {
808
+ stateBefore.splice( j, 1 );
809
+ continue stateLoop;
810
+ }
811
+ }
812
+
813
+ document.documentElement.classList.add( state[i] );
814
+
815
+ // Dispatch custom event matching the state's name
816
+ dispatchEvent( state[i] );
817
+ }
818
+
819
+ // Clean up the remaints of the previous state
820
+ while( stateBefore.length ) {
821
+ document.documentElement.classList.remove( stateBefore.pop() );
822
+ }
823
+
824
+ // If the overview is active, re-activate it to update positions
825
+ if( isOverviewActive() ) {
826
+ activateOverview();
827
+ }
828
+
829
+ // Update the URL hash after a delay since updating it mid-transition
830
+ // is likely to cause visual lag
831
+ writeURL( 1500 );
832
+
833
+ // Find the current horizontal slide and any possible vertical slides
834
+ // within it
835
+ var currentHorizontalSlide = horizontalSlides[ indexh ],
836
+ currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
837
+
838
+ // Store references to the previous and current slides
839
+ currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
840
+
841
+
842
+ // Show fragment, if specified
843
+ if( ( indexh !== indexhBefore || indexv !== indexvBefore ) && f ) {
844
+ var fragments = currentSlide.querySelectorAll( '.fragment' );
845
+
846
+ toArray( fragments ).forEach( function( fragment, indexf ) {
847
+ if( indexf < f ) {
848
+ fragment.classList.add( 'visible' );
849
+ }
850
+ else {
851
+ fragment.classList.remove( 'visible' );
852
+ }
853
+ } );
854
+ }
855
+
856
+ // Dispatch an event if the slide changed
857
+ if( indexh !== indexhBefore || indexv !== indexvBefore ) {
858
+ dispatchEvent( 'slidechanged', {
859
+ 'indexh': indexh,
860
+ 'indexv': indexv,
861
+ 'previousSlide': previousSlide,
862
+ 'currentSlide': currentSlide
863
+ } );
864
+ }
865
+ else {
866
+ // Ensure that the previous slide is never the same as the current
867
+ previousSlide = null;
868
+ }
869
+
870
+ // Solves an edge case where the previous slide maintains the
871
+ // 'present' class when navigating between adjacent vertical
872
+ // stacks
873
+ if( previousSlide ) {
874
+ previousSlide.classList.remove( 'present' );
875
+ }
876
+
877
+ updateControls();
878
+ updateProgress();
879
+ }
880
+
881
+ /**
882
+ * Updates one dimension of slides by showing the slide
883
+ * with the specified index.
884
+ *
885
+ * @param {String} selector A CSS selector that will fetch
886
+ * the group of slides we are working with
887
+ * @param {Number} index The index of the slide that should be
888
+ * shown
889
+ *
890
+ * @return {Number} The index of the slide that is now shown,
891
+ * might differ from the passed in index if it was out of
892
+ * bounds.
893
+ */
894
+ function updateSlides( selector, index ) {
895
+ // Select all slides and convert the NodeList result to
896
+ // an array
897
+ var slides = toArray( document.querySelectorAll( selector ) ),
898
+ slidesLength = slides.length;
899
+
900
+ if( slidesLength ) {
901
+
902
+ // Should the index loop?
903
+ if( config.loop ) {
904
+ index %= slidesLength;
905
+
906
+ if( index < 0 ) {
907
+ index = slidesLength + index;
908
+ }
909
+ }
910
+
911
+ // Enforce max and minimum index bounds
912
+ index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
913
+
914
+ for( var i = 0; i < slidesLength; i++ ) {
915
+ var element = slides[i];
916
+
917
+ // Optimization; hide all slides that are three or more steps
918
+ // away from the present slide
919
+ if( isOverviewActive() === false ) {
920
+ // The distance loops so that it measures 1 between the first
921
+ // and last slides
922
+ var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
923
+
924
+ element.style.display = distance > 3 ? 'none' : 'block';
925
+ }
926
+
927
+ slides[i].classList.remove( 'past' );
928
+ slides[i].classList.remove( 'present' );
929
+ slides[i].classList.remove( 'future' );
930
+
931
+ if( i < index ) {
932
+ // Any element previous to index is given the 'past' class
933
+ slides[i].classList.add( 'past' );
934
+ }
935
+ else if( i > index ) {
936
+ // Any element subsequent to index is given the 'future' class
937
+ slides[i].classList.add( 'future' );
938
+ }
939
+
940
+ // If this element contains vertical slides
941
+ if( element.querySelector( 'section' ) ) {
942
+ slides[i].classList.add( 'stack' );
943
+ }
944
+ }
945
+
946
+ // Mark the current slide as present
947
+ slides[index].classList.add( 'present' );
948
+
949
+ // If this slide has a state associated with it, add it
950
+ // onto the current state of the deck
951
+ var slideState = slides[index].getAttribute( 'data-state' );
952
+ if( slideState ) {
953
+ state = state.concat( slideState.split( ' ' ) );
954
+ }
955
+
956
+ // If this slide has a data-autoslide attribtue associated use this as
957
+ // autoSlide value otherwise use the global configured time
958
+ var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
959
+ if( slideAutoSlide ) {
960
+ autoSlide = parseInt( slideAutoSlide, 10 );
961
+ } else {
962
+ autoSlide = config.autoSlide;
963
+ }
964
+
965
+ }
966
+ else {
967
+ // Since there are no slides we can't be anywhere beyond the
968
+ // zeroth index
969
+ index = 0;
970
+ }
971
+
972
+ return index;
973
+
974
+ }
975
+
976
+ /**
977
+ * Updates the progress bar to reflect the current slide.
978
+ */
979
+ function updateProgress() {
980
+ // Update progress if enabled
981
+ if( config.progress && dom.progress ) {
982
+
983
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
984
+
985
+ // The number of past and total slides
986
+ var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
987
+ var pastCount = 0;
988
+
989
+ // Step through all slides and count the past ones
990
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
991
+
992
+ var horizontalSlide = horizontalSlides[i];
993
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
994
+
995
+ for( var j = 0; j < verticalSlides.length; j++ ) {
996
+
997
+ // Stop as soon as we arrive at the present
998
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
999
+ break mainLoop;
1000
+ }
1001
+
1002
+ pastCount++;
1003
+
1004
+ }
1005
+
1006
+ // Stop as soon as we arrive at the present
1007
+ if( horizontalSlide.classList.contains( 'present' ) ) {
1008
+ break;
1009
+ }
1010
+
1011
+ // Don't count the wrapping section for vertical slides
1012
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
1013
+ pastCount++;
1014
+ }
1015
+
1016
+ }
1017
+
1018
+ dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
1019
+
1020
+ }
1021
+ }
1022
+
1023
+ /**
1024
+ * Updates the state of all control/navigation arrows.
1025
+ */
1026
+ function updateControls() {
1027
+ if ( config.controls && dom.controls ) {
1028
+
1029
+ var routes = availableRoutes();
1030
+
1031
+ // Remove the 'enabled' class from all directions
1032
+ dom.controlsLeft.concat( dom.controlsRight )
1033
+ .concat( dom.controlsUp )
1034
+ .concat( dom.controlsDown )
1035
+ .concat( dom.controlsPrev )
1036
+ .concat( dom.controlsNext ).forEach( function( node ) {
1037
+ node.classList.remove( 'enabled' );
1038
+ } );
1039
+
1040
+ // Add the 'enabled' class to the available routes
1041
+ if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1042
+ if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1043
+ if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1044
+ if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1045
+
1046
+ // Prev/next buttons
1047
+ if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1048
+ if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1049
+
1050
+ }
1051
+ }
1052
+
1053
+ /**
1054
+ * Determine what available routes there are for navigation.
1055
+ *
1056
+ * @return {Object} containing four booleans: left/right/up/down
1057
+ */
1058
+ function availableRoutes() {
1059
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
1060
+ verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
1061
+
1062
+ return {
1063
+ left: indexh > 0,
1064
+ right: indexh < horizontalSlides.length - 1,
1065
+ up: indexv > 0,
1066
+ down: indexv < verticalSlides.length - 1
1067
+ };
1068
+ }
1069
+
1070
+ /**
1071
+ * Reads the current URL (hash) and navigates accordingly.
1072
+ */
1073
+ function readURL() {
1074
+ var hash = window.location.hash;
1075
+
1076
+ // Attempt to parse the hash as either an index or name
1077
+ var bits = hash.slice( 2 ).split( '/' ),
1078
+ name = hash.replace( /#|\//gi, '' );
1079
+
1080
+ // If the first bit is invalid and there is a name we can
1081
+ // assume that this is a named link
1082
+ if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
1083
+ // Find the slide with the specified name
1084
+ var element = document.querySelector( '#' + name );
1085
+
1086
+ if( element ) {
1087
+ // Find the position of the named slide and navigate to it
1088
+ var indices = Reveal.getIndices( element );
1089
+ slide( indices.h, indices.v );
1090
+ }
1091
+ // If the slide doesn't exist, navigate to the current slide
1092
+ else {
1093
+ slide( indexh, indexv );
1094
+ }
1095
+ }
1096
+ else {
1097
+ // Read the index components of the hash
1098
+ var h = parseInt( bits[0], 10 ) || 0,
1099
+ v = parseInt( bits[1], 10 ) || 0;
1100
+
1101
+ slide( h, v );
1102
+ }
1103
+ }
1104
+
1105
+ /**
1106
+ * Updates the page URL (hash) to reflect the current
1107
+ * state.
1108
+ *
1109
+ * @param {Number} delay The time in ms to wait before
1110
+ * writing the hash
1111
+ */
1112
+ function writeURL( delay ) {
1113
+ if( config.history ) {
1114
+
1115
+ // Make sure there's never more than one timeout running
1116
+ clearTimeout( writeURLTimeout );
1117
+
1118
+ // If a delay is specified, timeout this call
1119
+ if( typeof delay === 'number' ) {
1120
+ writeURLTimeout = setTimeout( writeURL, delay );
1121
+ }
1122
+ else {
1123
+ var url = '/';
1124
+
1125
+ // If the current slide has an ID, use that as a named link
1126
+ if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
1127
+ url = '/' + currentSlide.getAttribute( 'id' );
1128
+ }
1129
+ // Otherwise use the /h/v index
1130
+ else {
1131
+ if( indexh > 0 || indexv > 0 ) url += indexh;
1132
+ if( indexv > 0 ) url += '/' + indexv;
1133
+ }
1134
+
1135
+ window.location.hash = url;
1136
+ }
1137
+ }
1138
+ }
1139
+
1140
+ /**
1141
+ * Retrieves the h/v location of the current, or specified,
1142
+ * slide.
1143
+ *
1144
+ * @param {HTMLElement} slide If specified, the returned
1145
+ * index will be for this slide rather than the currently
1146
+ * active one
1147
+ *
1148
+ * @return {Object} { h: <int>, v: <int> }
1149
+ */
1150
+ function getIndices( slide ) {
1151
+ // By default, return the current indices
1152
+ var h = indexh,
1153
+ v = indexv;
1154
+
1155
+ // If a slide is specified, return the indices of that slide
1156
+ if( slide ) {
1157
+ var isVertical = !!slide.parentNode.nodeName.match( /section/gi );
1158
+ var slideh = isVertical ? slide.parentNode : slide;
1159
+
1160
+ // Select all horizontal slides
1161
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1162
+
1163
+ // Now that we know which the horizontal slide is, get its index
1164
+ h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
1165
+
1166
+ // If this is a vertical slide, grab the vertical index
1167
+ if( isVertical ) {
1168
+ v = Math.max( toArray( slide.parentNode.children ).indexOf( slide ), 0 );
1169
+ }
1170
+ }
1171
+
1172
+ return { h: h, v: v };
1173
+ }
1174
+
1175
+ /**
1176
+ * Navigate to the next slide fragment.
1177
+ *
1178
+ * @return {Boolean} true if there was a next fragment,
1179
+ * false otherwise
1180
+ */
1181
+ function nextFragment() {
1182
+ // Vertical slides:
1183
+ if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
1184
+ var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
1185
+ if( verticalFragments.length ) {
1186
+ verticalFragments[0].classList.add( 'visible' );
1187
+
1188
+ // Notify subscribers of the change
1189
+ dispatchEvent( 'fragmentshown', { fragment: verticalFragments[0] } );
1190
+ return true;
1191
+ }
1192
+ }
1193
+ // Horizontal slides:
1194
+ else {
1195
+ var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
1196
+ if( horizontalFragments.length ) {
1197
+ horizontalFragments[0].classList.add( 'visible' );
1198
+
1199
+ // Notify subscribers of the change
1200
+ dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } );
1201
+ return true;
1202
+ }
1203
+ }
1204
+
1205
+ return false;
1206
+ }
1207
+
1208
+ /**
1209
+ * Navigate to the previous slide fragment.
1210
+ *
1211
+ * @return {Boolean} true if there was a previous fragment,
1212
+ * false otherwise
1213
+ */
1214
+ function previousFragment() {
1215
+ // Vertical slides:
1216
+ if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
1217
+ var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' );
1218
+ if( verticalFragments.length ) {
1219
+ verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' );
1220
+
1221
+ // Notify subscribers of the change
1222
+ dispatchEvent( 'fragmenthidden', { fragment: verticalFragments[ verticalFragments.length - 1 ] } );
1223
+ return true;
1224
+ }
1225
+ }
1226
+ // Horizontal slides:
1227
+ else {
1228
+ var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' );
1229
+ if( horizontalFragments.length ) {
1230
+ horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' );
1231
+
1232
+ // Notify subscribers of the change
1233
+ dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } );
1234
+ return true;
1235
+ }
1236
+ }
1237
+
1238
+ return false;
1239
+ }
1240
+
1241
+ /**
1242
+ * Cues a new automated slide if enabled in the config.
1243
+ */
1244
+ function cueAutoSlide() {
1245
+ clearTimeout( autoSlideTimeout );
1246
+
1247
+ // Cue the next auto-slide if enabled
1248
+ if( autoSlide ) {
1249
+ autoSlideTimeout = setTimeout( navigateNext, autoSlide );
1250
+ }
1251
+ }
1252
+
1253
+ function navigateLeft() {
1254
+ // Prioritize hiding fragments
1255
+ if( availableRoutes().left && isOverviewActive() || previousFragment() === false ) {
1256
+ slide( indexh - 1 );
1257
+ }
1258
+ }
1259
+
1260
+ function navigateRight() {
1261
+ // Prioritize revealing fragments
1262
+ if( availableRoutes().right && isOverviewActive() || nextFragment() === false ) {
1263
+ slide( indexh + 1 );
1264
+ }
1265
+ }
1266
+
1267
+ function navigateUp() {
1268
+ // Prioritize hiding fragments
1269
+ if( availableRoutes().up && isOverviewActive() || previousFragment() === false ) {
1270
+ slide( indexh, indexv - 1 );
1271
+ }
1272
+ }
1273
+
1274
+ function navigateDown() {
1275
+ // Prioritize revealing fragments
1276
+ if( availableRoutes().down && isOverviewActive() || nextFragment() === false ) {
1277
+ slide( indexh, indexv + 1 );
1278
+ }
1279
+ }
1280
+
1281
+ /**
1282
+ * Navigates backwards, prioritized in the following order:
1283
+ * 1) Previous fragment
1284
+ * 2) Previous vertical slide
1285
+ * 3) Previous horizontal slide
1286
+ */
1287
+ function navigatePrev() {
1288
+ // Prioritize revealing fragments
1289
+ if( previousFragment() === false ) {
1290
+ if( availableRoutes().up ) {
1291
+ navigateUp();
1292
+ }
1293
+ else {
1294
+ // Fetch the previous horizontal slide, if there is one
1295
+ var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
1296
+
1297
+ if( previousSlide ) {
1298
+ indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
1299
+ indexh --;
1300
+ slide();
1301
+ }
1302
+ }
1303
+ }
1304
+ }
1305
+
1306
+ /**
1307
+ * Same as #navigatePrev() but navigates forwards.
1308
+ */
1309
+ function navigateNext() {
1310
+ // Prioritize revealing fragments
1311
+ if( nextFragment() === false ) {
1312
+ availableRoutes().down ? navigateDown() : navigateRight();
1313
+ }
1314
+
1315
+ // If auto-sliding is enabled we need to cue up
1316
+ // another timeout
1317
+ cueAutoSlide();
1318
+ }
1319
+
1320
+
1321
+ // --------------------------------------------------------------------//
1322
+ // ----------------------------- EVENTS -------------------------------//
1323
+ // --------------------------------------------------------------------//
1324
+
1325
+
1326
+ /**
1327
+ * Handler for the document level 'keydown' event.
1328
+ *
1329
+ * @param {Object} event
1330
+ */
1331
+ function onDocumentKeyDown( event ) {
1332
+ // Check if there's a focused element that could be using
1333
+ // the keyboard
1334
+ var activeElement = document.activeElement;
1335
+ var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
1336
+
1337
+ // Disregard the event if there's a focused element or a
1338
+ // keyboard modifier key is present
1339
+ if ( hasFocus || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
1340
+
1341
+ var triggered = true;
1342
+
1343
+ switch( event.keyCode ) {
1344
+ // p, page up
1345
+ case 80: case 33: navigatePrev(); break;
1346
+ // n, page down
1347
+ case 78: case 34: navigateNext(); break;
1348
+ // h, left
1349
+ case 72: case 37: navigateLeft(); break;
1350
+ // l, right
1351
+ case 76: case 39: navigateRight(); break;
1352
+ // k, up
1353
+ case 75: case 38: navigateUp(); break;
1354
+ // j, down
1355
+ case 74: case 40: navigateDown(); break;
1356
+ // home
1357
+ case 36: slide( 0 ); break;
1358
+ // end
1359
+ case 35: slide( Number.MAX_VALUE ); break;
1360
+ // space
1361
+ case 32: isOverviewActive() ? deactivateOverview() : navigateNext(); break;
1362
+ // return
1363
+ case 13: isOverviewActive() ? deactivateOverview() : triggered = false; break;
1364
+ // b, period
1365
+ case 66: case 190: togglePause(); break;
1366
+ // f
1367
+ case 70: enterFullscreen(); break;
1368
+ default:
1369
+ triggered = false;
1370
+ }
1371
+
1372
+ // If the input resulted in a triggered action we should prevent
1373
+ // the browsers default behavior
1374
+ if( triggered ) {
1375
+ event.preventDefault();
1376
+ }
1377
+ else if ( event.keyCode === 27 && supports3DTransforms ) {
1378
+ toggleOverview();
1379
+
1380
+ event.preventDefault();
1381
+ }
1382
+
1383
+ // If auto-sliding is enabled we need to cue up
1384
+ // another timeout
1385
+ cueAutoSlide();
1386
+
1387
+ }
1388
+
1389
+ /**
1390
+ * Handler for the document level 'touchstart' event,
1391
+ * enables support for swipe and pinch gestures.
1392
+ */
1393
+ function onDocumentTouchStart( event ) {
1394
+ touch.startX = event.touches[0].clientX;
1395
+ touch.startY = event.touches[0].clientY;
1396
+ touch.startCount = event.touches.length;
1397
+
1398
+ // If there's two touches we need to memorize the distance
1399
+ // between those two points to detect pinching
1400
+ if( event.touches.length === 2 && config.overview ) {
1401
+ touch.startSpan = distanceBetween( {
1402
+ x: event.touches[1].clientX,
1403
+ y: event.touches[1].clientY
1404
+ }, {
1405
+ x: touch.startX,
1406
+ y: touch.startY
1407
+ } );
1408
+ }
1409
+ }
1410
+
1411
+ /**
1412
+ * Handler for the document level 'touchmove' event.
1413
+ */
1414
+ function onDocumentTouchMove( event ) {
1415
+ // Each touch should only trigger one action
1416
+ if( !touch.handled ) {
1417
+ var currentX = event.touches[0].clientX;
1418
+ var currentY = event.touches[0].clientY;
1419
+
1420
+ // If the touch started off with two points and still has
1421
+ // two active touches; test for the pinch gesture
1422
+ if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
1423
+
1424
+ // The current distance in pixels between the two touch points
1425
+ var currentSpan = distanceBetween( {
1426
+ x: event.touches[1].clientX,
1427
+ y: event.touches[1].clientY
1428
+ }, {
1429
+ x: touch.startX,
1430
+ y: touch.startY
1431
+ } );
1432
+
1433
+ // If the span is larger than the desire amount we've got
1434
+ // ourselves a pinch
1435
+ if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
1436
+ touch.handled = true;
1437
+
1438
+ if( currentSpan < touch.startSpan ) {
1439
+ activateOverview();
1440
+ }
1441
+ else {
1442
+ deactivateOverview();
1443
+ }
1444
+ }
1445
+
1446
+ event.preventDefault();
1447
+
1448
+ }
1449
+ // There was only one touch point, look for a swipe
1450
+ else if( event.touches.length === 1 && touch.startCount !== 2 ) {
1451
+
1452
+ var deltaX = currentX - touch.startX,
1453
+ deltaY = currentY - touch.startY;
1454
+
1455
+ if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
1456
+ touch.handled = true;
1457
+ navigateLeft();
1458
+ }
1459
+ else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
1460
+ touch.handled = true;
1461
+ navigateRight();
1462
+ }
1463
+ else if( deltaY > touch.threshold ) {
1464
+ touch.handled = true;
1465
+ navigateUp();
1466
+ }
1467
+ else if( deltaY < -touch.threshold ) {
1468
+ touch.handled = true;
1469
+ navigateDown();
1470
+ }
1471
+
1472
+ event.preventDefault();
1473
+
1474
+ }
1475
+ }
1476
+ // There's a bug with swiping on some Android devices unless
1477
+ // the default action is always prevented
1478
+ else if( navigator.userAgent.match( /android/gi ) ) {
1479
+ event.preventDefault();
1480
+ }
1481
+ }
1482
+
1483
+ /**
1484
+ * Handler for the document level 'touchend' event.
1485
+ */
1486
+ function onDocumentTouchEnd( event ) {
1487
+ touch.handled = false;
1488
+ }
1489
+
1490
+ /**
1491
+ * Handles mouse wheel scrolling, throttled to avoid skipping
1492
+ * multiple slides.
1493
+ */
1494
+ function onDocumentMouseScroll( event ){
1495
+ clearTimeout( mouseWheelTimeout );
1496
+
1497
+ mouseWheelTimeout = setTimeout( function() {
1498
+ var delta = event.detail || -event.wheelDelta;
1499
+ if( delta > 0 ) {
1500
+ navigateNext();
1501
+ }
1502
+ else {
1503
+ navigatePrev();
1504
+ }
1505
+ }, 100 );
1506
+ }
1507
+
1508
+ /**
1509
+ * Clicking on the progress bar results in a navigation to the
1510
+ * closest approximate horizontal slide using this equation:
1511
+ *
1512
+ * ( clickX / presentationWidth ) * numberOfSlides
1513
+ */
1514
+ function onProgressClick( event ) {
1515
+ var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
1516
+ var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
1517
+
1518
+ slide( slideIndex );
1519
+ }
1520
+
1521
+ /**
1522
+ * Handler for the window level 'hashchange' event.
1523
+ */
1524
+ function onWindowHashChange( event ) {
1525
+ readURL();
1526
+ }
1527
+
1528
+ /**
1529
+ * Handler for the window level 'resize' event.
1530
+ */
1531
+ function onWindowResize( event ) {
1532
+ layout();
1533
+ }
1534
+
1535
+ /**
1536
+ * Invoked when a slide is and we're in the overview.
1537
+ */
1538
+ function onOverviewSlideClicked( event ) {
1539
+ // TODO There's a bug here where the event listeners are not
1540
+ // removed after deactivating the overview.
1541
+ if( isOverviewActive() ) {
1542
+ event.preventDefault();
1543
+
1544
+ deactivateOverview();
1545
+
1546
+ var element = event.target;
1547
+
1548
+ while( element && !element.nodeName.match( /section/gi ) ) {
1549
+ element = element.parentNode;
1550
+ }
1551
+
1552
+ if( element.nodeName.match( /section/gi ) ) {
1553
+ var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
1554
+ v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
1555
+
1556
+ slide( h, v );
1557
+ }
1558
+ }
1559
+ }
1560
+
1561
+
1562
+ // --------------------------------------------------------------------//
1563
+ // ------------------------------- API --------------------------------//
1564
+ // --------------------------------------------------------------------//
1565
+
1566
+
1567
+ return {
1568
+ initialize: initialize,
1569
+
1570
+ // Navigation methods
1571
+ slide: slide,
1572
+ left: navigateLeft,
1573
+ right: navigateRight,
1574
+ up: navigateUp,
1575
+ down: navigateDown,
1576
+ prev: navigatePrev,
1577
+ next: navigateNext,
1578
+ prevFragment: previousFragment,
1579
+ nextFragment: nextFragment,
1580
+
1581
+ // Deprecated aliases
1582
+ navigateTo: slide,
1583
+ navigateLeft: navigateLeft,
1584
+ navigateRight: navigateRight,
1585
+ navigateUp: navigateUp,
1586
+ navigateDown: navigateDown,
1587
+ navigatePrev: navigatePrev,
1588
+ navigateNext: navigateNext,
1589
+
1590
+ // Toggles the overview mode on/off
1591
+ toggleOverview: toggleOverview,
1592
+
1593
+ // Adds or removes all internal event listeners (such as keyboard)
1594
+ addEventListeners: addEventListeners,
1595
+ removeEventListeners: removeEventListeners,
1596
+
1597
+ // Returns the indices of the current, or specified, slide
1598
+ getIndices: getIndices,
1599
+
1600
+ // Returns the previous slide element, may be null
1601
+ getPreviousSlide: function() {
1602
+ return previousSlide;
1603
+ },
1604
+
1605
+ // Returns the current slide element
1606
+ getCurrentSlide: function() {
1607
+ return currentSlide;
1608
+ },
1609
+
1610
+ // Helper method, retrieves query string as a key/value hash
1611
+ getQueryHash: function() {
1612
+ var query = {};
1613
+
1614
+ location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
1615
+ query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
1616
+ } );
1617
+
1618
+ return query;
1619
+ },
1620
+
1621
+ // Forward event binding to the reveal DOM element
1622
+ addEventListener: function( type, listener, useCapture ) {
1623
+ if( 'addEventListener' in window ) {
1624
+ ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
1625
+ }
1626
+ },
1627
+ removeEventListener: function( type, listener, useCapture ) {
1628
+ if( 'addEventListener' in window ) {
1629
+ ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
1630
+ }
1631
+ }
1632
+ };
1633
+
1634
+ })();