reveal-ck 0.1.0

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