reveal-ck 0.1.2 → 0.1.3

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 (80) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +13 -0
  3. data/bin/reveal-ck +23 -8
  4. data/lib/reveal-ck.rb +6 -0
  5. data/lib/reveal-ck/build_task.rb +14 -0
  6. data/lib/reveal-ck/builder.rb +21 -0
  7. data/lib/reveal-ck/config.rb +43 -0
  8. data/lib/reveal-ck/file_slicer.rb +19 -17
  9. data/lib/reveal-ck/file_splicer.rb +30 -28
  10. data/lib/reveal-ck/file_string_replacer.rb +17 -0
  11. data/lib/reveal-ck/haml_processor.rb +17 -15
  12. data/lib/reveal-ck/presentation_builder.rb +69 -0
  13. data/lib/reveal-ck/slide_builder.rb +66 -0
  14. data/lib/reveal-ck/version.rb +1 -1
  15. data/rakelib/cucumber.rake +14 -1
  16. data/reveal.js/Gruntfile.js +132 -0
  17. data/reveal.js/LICENSE +1 -1
  18. data/reveal.js/README.md +362 -50
  19. data/reveal.js/css/print/paper.css +14 -14
  20. data/reveal.js/css/print/pdf.css +60 -30
  21. data/reveal.js/css/reveal.css +444 -149
  22. data/reveal.js/css/reveal.min.css +2 -2
  23. data/reveal.js/css/theme/README.md +8 -10
  24. data/reveal.js/css/theme/beige.css +24 -45
  25. data/reveal.js/css/theme/default.css +24 -45
  26. data/reveal.js/css/theme/moon.css +142 -0
  27. data/reveal.js/css/theme/night.css +24 -44
  28. data/reveal.js/css/theme/serif.css +27 -45
  29. data/reveal.js/css/theme/simple.css +25 -45
  30. data/reveal.js/css/theme/sky.css +26 -43
  31. data/reveal.js/css/theme/solarized.css +142 -0
  32. data/reveal.js/css/theme/source/beige.scss +2 -2
  33. data/reveal.js/css/theme/source/default.scss +2 -2
  34. data/reveal.js/css/theme/source/moon.scss +68 -0
  35. data/reveal.js/css/theme/source/night.scss +4 -4
  36. data/reveal.js/css/theme/source/serif.scss +8 -6
  37. data/reveal.js/css/theme/source/simple.scss +5 -5
  38. data/reveal.js/css/theme/source/sky.scss +9 -4
  39. data/reveal.js/css/theme/source/solarized.scss +74 -0
  40. data/reveal.js/css/theme/template/settings.scss +2 -2
  41. data/reveal.js/examples/assets/image1.png +0 -0
  42. data/reveal.js/examples/assets/image2.png +0 -0
  43. data/reveal.js/examples/barebones.html +42 -0
  44. data/reveal.js/examples/embedded-media.html +49 -0
  45. data/reveal.js/examples/slide-backgrounds.html +101 -0
  46. data/reveal.js/index.html +63 -56
  47. data/reveal.js/js/reveal.js +997 -216
  48. data/reveal.js/js/reveal.min.js +3 -3
  49. data/reveal.js/package.json +43 -29
  50. data/reveal.js/plugin/highlight/highlight.js +28 -6
  51. data/reveal.js/plugin/markdown/example.html +97 -0
  52. data/reveal.js/plugin/markdown/example.md +29 -0
  53. data/reveal.js/plugin/markdown/markdown.js +164 -11
  54. data/reveal.js/plugin/markdown/marked.js +37 -0
  55. data/reveal.js/plugin/multiplex/client.js +13 -0
  56. data/reveal.js/plugin/multiplex/index.js +56 -0
  57. data/reveal.js/plugin/multiplex/master.js +50 -0
  58. data/reveal.js/plugin/notes-server/index.js +1 -0
  59. data/reveal.js/plugin/notes-server/notes.html +8 -5
  60. data/reveal.js/plugin/notes/notes.html +133 -44
  61. data/reveal.js/plugin/notes/notes.js +3 -1
  62. data/reveal.js/plugin/print-pdf/print-pdf.js +8 -3
  63. data/reveal.js/plugin/remotes/remotes.js +13 -4
  64. data/reveal.js/plugin/search/search.js +196 -0
  65. data/reveal.js/plugin/zoom-js/zoom.js +26 -21
  66. data/spec/data/config/config.toml +6 -0
  67. data/spec/data/string_replacer/after_replace +4 -0
  68. data/spec/data/string_replacer/before_replace +4 -0
  69. data/spec/lib/reveal-ck/config_spec.rb +50 -0
  70. data/spec/lib/reveal-ck/file_slicer_spec.rb +29 -27
  71. data/spec/lib/reveal-ck/file_splicer_spec.rb +28 -26
  72. data/spec/lib/reveal-ck/file_string_replacer_spec.rb +32 -0
  73. data/spec/lib/reveal-ck/haml_processor_spec.rb +28 -26
  74. metadata +82 -27
  75. data/rakelib/presentation.rake +0 -41
  76. data/rakelib/reveal.rake +0 -15
  77. data/reveal.js/css/shaders/tile-flip.fs +0 -64
  78. data/reveal.js/css/shaders/tile-flip.vs +0 -141
  79. data/reveal.js/grunt.js +0 -84
  80. data/reveal.js/plugin/markdown/showdown.js +0 -62
@@ -3,7 +3,7 @@
3
3
  * http://lab.hakim.se/reveal-js
4
4
  * MIT licensed
5
5
  *
6
- * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
6
+ * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
7
7
  */
8
8
  var Reveal = (function(){
9
9
 
@@ -16,6 +16,19 @@ var Reveal = (function(){
16
16
 
17
17
  // Configurations defaults, can be overridden at initialization time
18
18
  config = {
19
+
20
+ // The "normal" size of the presentation, aspect ratio will be preserved
21
+ // when the presentation is scaled to fit different resolutions
22
+ width: 960,
23
+ height: 700,
24
+
25
+ // Factor of the display size that should remain empty around the content
26
+ margin: 0.1,
27
+
28
+ // Bounds for smallest/largest possible scale to apply to content
29
+ minScale: 0.2,
30
+ maxScale: 1.0,
31
+
19
32
  // Display controls in the bottom right corner
20
33
  controls: true,
21
34
 
@@ -31,7 +44,7 @@ var Reveal = (function(){
31
44
  // Enable the slide overview mode
32
45
  overview: true,
33
46
 
34
- // Vertical centering of slides
47
+ // Vertical centring of slides
35
48
  center: true,
36
49
 
37
50
  // Enables touch navigation on devices with touch input
@@ -43,6 +56,9 @@ var Reveal = (function(){
43
56
  // Change the presentation direction to be RTL
44
57
  rtl: false,
45
58
 
59
+ // Turns fragments on and off globally
60
+ fragments: true,
61
+
46
62
  // Number of milliseconds between automatically proceeding to the
47
63
  // next slide, disabled when set to 0, this value can be overwritten
48
64
  // by using a data-autoslide attribute on your slides
@@ -54,21 +70,29 @@ var Reveal = (function(){
54
70
  // Apply a 3D roll to links on hover
55
71
  rollingLinks: true,
56
72
 
73
+ // Opens links in an iframe preview overlay
74
+ previewLinks: false,
75
+
57
76
  // Theme (see /css/theme)
58
77
  theme: null,
59
78
 
60
79
  // Transition style
61
80
  transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
62
81
 
82
+ // Transition speed
83
+ transitionSpeed: 'default', // default/fast/slow
84
+
85
+ // Transition style for full page slide backgrounds
86
+ backgroundTransition: 'default', // default/linear
87
+
63
88
  // Script dependencies to load
64
89
  dependencies: []
65
90
  },
66
91
 
67
- // Stores if the next slide should be shown automatically
68
- // after n milliseconds
69
- autoSlide = config.autoSlide,
92
+ // The current auto-slide duration
93
+ autoSlide = 0,
70
94
 
71
- // The horizontal and verical index of the currently active slide
95
+ // The horizontal and vertical index of the currently active slide
72
96
  indexh = 0,
73
97
  indexv = 0,
74
98
 
@@ -81,6 +105,9 @@ var Reveal = (function(){
81
105
  // all current slides.
82
106
  state = [],
83
107
 
108
+ // The current scale of the presentation (see width/height config)
109
+ scale = 1,
110
+
84
111
  // Cached references to DOM elements
85
112
  dom = {},
86
113
 
@@ -99,7 +126,7 @@ var Reveal = (function(){
99
126
  'transform' in document.body.style,
100
127
 
101
128
  // Throttles mouse wheel navigation
102
- mouseWheelTimeout = 0,
129
+ lastMouseWheelStep = 0,
103
130
 
104
131
  // An interval used to automatically move on to the next slide
105
132
  autoSlideTimeout = 0,
@@ -113,6 +140,9 @@ var Reveal = (function(){
113
140
  // A delay used to deactivate the overview mode
114
141
  deactivateOverviewTimeout = 0,
115
142
 
143
+ // Flags if the interaction event listeners are bound
144
+ eventsAreBound = false,
145
+
116
146
  // Holds information about the currently ongoing touch input
117
147
  touch = {
118
148
  startX: 0,
@@ -128,7 +158,7 @@ var Reveal = (function(){
128
158
  */
129
159
  function initialize( options ) {
130
160
 
131
- if( ( !supports2DTransforms && !supports3DTransforms ) ) {
161
+ if( !supports2DTransforms && !supports3DTransforms ) {
132
162
  document.body.setAttribute( 'class', 'no-transforms' );
133
163
 
134
164
  // If the browser doesn't support core features we won't be
@@ -162,8 +192,15 @@ var Reveal = (function(){
162
192
  dom.wrapper = document.querySelector( '.reveal' );
163
193
  dom.slides = document.querySelector( '.reveal .slides' );
164
194
 
195
+ // Background element
196
+ if( !document.querySelector( '.reveal .backgrounds' ) ) {
197
+ dom.background = document.createElement( 'div' );
198
+ dom.background.classList.add( 'backgrounds' );
199
+ dom.wrapper.appendChild( dom.background );
200
+ }
201
+
165
202
  // Progress bar
166
- if( !dom.wrapper.querySelector( '.progress' ) && config.progress ) {
203
+ if( !dom.wrapper.querySelector( '.progress' ) ) {
167
204
  var progressElement = document.createElement( 'div' );
168
205
  progressElement.classList.add( 'progress' );
169
206
  progressElement.innerHTML = '<span></span>';
@@ -171,7 +208,7 @@ var Reveal = (function(){
171
208
  }
172
209
 
173
210
  // Arrow controls
174
- if( !dom.wrapper.querySelector( '.controls' ) && config.controls ) {
211
+ if( !dom.wrapper.querySelector( '.controls' ) ) {
175
212
  var controlsElement = document.createElement( 'aside' );
176
213
  controlsElement.classList.add( 'controls' );
177
214
  controlsElement.innerHTML = '<div class="navigate-left"></div>' +
@@ -181,11 +218,11 @@ var Reveal = (function(){
181
218
  dom.wrapper.appendChild( controlsElement );
182
219
  }
183
220
 
184
- // Presentation background element
221
+ // State background element [DEPRECATED]
185
222
  if( !dom.wrapper.querySelector( '.state-background' ) ) {
186
- var backgroundElement = document.createElement( 'div' );
187
- backgroundElement.classList.add( 'state-background' );
188
- dom.wrapper.appendChild( backgroundElement );
223
+ var stateBackgroundElement = document.createElement( 'div' );
224
+ stateBackgroundElement.classList.add( 'state-background' );
225
+ dom.wrapper.appendChild( stateBackgroundElement );
189
226
  }
190
227
 
191
228
  // Overlay graphic which is displayed during the paused mode
@@ -213,16 +250,96 @@ var Reveal = (function(){
213
250
 
214
251
  }
215
252
 
253
+ /**
254
+ * Creates the slide background elements and appends them
255
+ * to the background container. One element is created per
256
+ * slide no matter if the given slide has visible background.
257
+ */
258
+ function createBackgrounds() {
259
+
260
+ if( isPrintingPDF() ) {
261
+ document.body.classList.add( 'print-pdf' );
262
+ }
263
+
264
+ // Clear prior backgrounds
265
+ dom.background.innerHTML = '';
266
+ dom.background.classList.add( 'no-transition' );
267
+
268
+ // Helper method for creating a background element for the
269
+ // given slide
270
+ function _createBackground( slide, container ) {
271
+
272
+ var data = {
273
+ background: slide.getAttribute( 'data-background' ),
274
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
275
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
276
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
277
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
278
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
279
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
280
+ };
281
+
282
+ var element = document.createElement( 'div' );
283
+ element.className = 'slide-background';
284
+
285
+ if( data.background ) {
286
+ // Auto-wrap image urls in url(...)
287
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
288
+ element.style.backgroundImage = 'url('+ data.background +')';
289
+ }
290
+ else {
291
+ element.style.background = data.background;
292
+ }
293
+ }
294
+
295
+ // Additional and optional background properties
296
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
297
+ if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
298
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
299
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
300
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
301
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
302
+
303
+ container.appendChild( element );
304
+
305
+ return element;
306
+
307
+ }
308
+
309
+ // Iterate over all horizontal slides
310
+ toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
311
+
312
+ var backgroundStack;
313
+
314
+ if( isPrintingPDF() ) {
315
+ backgroundStack = _createBackground( slideh, slideh );
316
+ }
317
+ else {
318
+ backgroundStack = _createBackground( slideh, dom.background );
319
+ }
320
+
321
+ // Iterate over all vertical slides
322
+ toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
323
+
324
+ if( isPrintingPDF() ) {
325
+ _createBackground( slidev, slidev );
326
+ }
327
+ else {
328
+ _createBackground( slidev, backgroundStack );
329
+ }
330
+
331
+ } );
332
+
333
+ } );
334
+
335
+ }
336
+
216
337
  /**
217
338
  * Hides the address bar if we're on a mobile device.
218
339
  */
219
340
  function hideAddressBar() {
220
341
 
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
-
342
+ if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
226
343
  // Events that should trigger the address bar to hide
227
344
  window.addEventListener( 'load', removeAddressBar, false );
228
345
  window.addEventListener( 'orientationchange', removeAddressBar, false );
@@ -261,7 +378,7 @@ var Reveal = (function(){
261
378
  }
262
379
  }
263
380
 
264
- // Called once synchronous scritps finish loading
381
+ // Called once synchronous scripts finish loading
265
382
  function proceed() {
266
383
  if( scriptsAsync.length ) {
267
384
  // Load asynchronous scripts
@@ -292,22 +409,12 @@ var Reveal = (function(){
292
409
  // Make sure we've got all the DOM elements we need
293
410
  setupDOM();
294
411
 
295
- // Subscribe to input
296
- addEventListeners();
297
-
298
412
  // Updates the presentation to match the current configuration values
299
413
  configure();
300
414
 
301
- // Force an initial layout, will thereafter be invoked as the window
302
- // is resized
303
- layout();
304
-
305
415
  // Read the initial hash
306
416
  readURL();
307
417
 
308
- // Start auto-sliding if it's enabled
309
- cueAutoSlide();
310
-
311
418
  // Notify listeners that the presentation is ready but use a 1ms
312
419
  // timeout to ensure it's not fired synchronously after #initialize()
313
420
  setTimeout( function() {
@@ -321,42 +428,71 @@ var Reveal = (function(){
321
428
  }
322
429
 
323
430
  /**
324
- * Applies the configuration settings from the config object.
431
+ * Applies the configuration settings from the config
432
+ * object. May be called multiple times.
325
433
  */
326
- function configure() {
434
+ function configure( options ) {
327
435
 
328
- if( supports3DTransforms === false ) {
329
- config.transition = 'linear';
330
- }
436
+ dom.wrapper.classList.remove( config.transition );
331
437
 
332
- if( config.controls && dom.controls ) {
333
- dom.controls.style.display = 'block';
334
- }
438
+ // New config options may be passed when this method
439
+ // is invoked through the API after initialization
440
+ if( typeof options === 'object' ) extend( config, options );
335
441
 
336
- if( config.progress && dom.progress ) {
337
- dom.progress.style.display = 'block';
442
+ // Force linear transition based on browser capabilities
443
+ if( supports3DTransforms === false ) config.transition = 'linear';
444
+
445
+ dom.wrapper.classList.add( config.transition );
446
+
447
+ dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
448
+ dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
449
+
450
+ if( dom.controls ) {
451
+ dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
338
452
  }
339
453
 
340
- if( config.transition !== 'default' ) {
341
- dom.wrapper.classList.add( config.transition );
454
+ if( dom.progress ) {
455
+ dom.progress.style.display = ( config.progress && dom.progress ) ? 'block' : 'none';
342
456
  }
343
457
 
344
458
  if( config.rtl ) {
345
459
  dom.wrapper.classList.add( 'rtl' );
346
460
  }
461
+ else {
462
+ dom.wrapper.classList.remove( 'rtl' );
463
+ }
347
464
 
348
465
  if( config.center ) {
349
466
  dom.wrapper.classList.add( 'center' );
350
467
  }
468
+ else {
469
+ dom.wrapper.classList.remove( 'center' );
470
+ }
351
471
 
352
472
  if( config.mouseWheel ) {
353
473
  document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
354
474
  document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
355
475
  }
476
+ else {
477
+ document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
478
+ document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
479
+ }
356
480
 
357
- // 3D links
481
+ // Rolling 3D links
358
482
  if( config.rollingLinks ) {
359
- linkify();
483
+ enableRollingLinks();
484
+ }
485
+ else {
486
+ disableRollingLinks();
487
+ }
488
+
489
+ // Iframe link previews
490
+ if( config.previewLinks ) {
491
+ enablePreviewLinks();
492
+ }
493
+ else {
494
+ disablePreviewLinks();
495
+ enablePreviewLinks( '[data-preview-link]' );
360
496
  }
361
497
 
362
498
  // Load the theme in the config, if it's not already loaded
@@ -371,6 +507,8 @@ var Reveal = (function(){
371
507
  }
372
508
  }
373
509
 
510
+ sync();
511
+
374
512
  }
375
513
 
376
514
  /**
@@ -378,13 +516,22 @@ var Reveal = (function(){
378
516
  */
379
517
  function addEventListeners() {
380
518
 
519
+ eventsAreBound = true;
520
+
381
521
  window.addEventListener( 'hashchange', onWindowHashChange, false );
382
522
  window.addEventListener( 'resize', onWindowResize, false );
383
523
 
384
524
  if( config.touch ) {
385
- document.addEventListener( 'touchstart', onDocumentTouchStart, false );
386
- document.addEventListener( 'touchmove', onDocumentTouchMove, false );
387
- document.addEventListener( 'touchend', onDocumentTouchEnd, false );
525
+ dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
526
+ dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
527
+ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
528
+
529
+ // Support pointer-style touch interaction as well
530
+ if( window.navigator.msPointerEnabled ) {
531
+ dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
532
+ dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
533
+ dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
534
+ }
388
535
  }
389
536
 
390
537
  if( config.keyboard ) {
@@ -392,17 +539,18 @@ var Reveal = (function(){
392
539
  }
393
540
 
394
541
  if ( config.progress && dom.progress ) {
395
- dom.progress.addEventListener( 'click', preventAndForward( onProgressClick ), false );
542
+ dom.progress.addEventListener( 'click', onProgressClicked, false );
396
543
  }
397
544
 
398
545
  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 ); } );
546
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
547
+ dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
548
+ dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
549
+ dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
550
+ dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
551
+ dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
552
+ dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
553
+ } );
406
554
  }
407
555
 
408
556
  }
@@ -412,28 +560,35 @@ var Reveal = (function(){
412
560
  */
413
561
  function removeEventListeners() {
414
562
 
563
+ eventsAreBound = false;
564
+
415
565
  document.removeEventListener( 'keydown', onDocumentKeyDown, false );
416
566
  window.removeEventListener( 'hashchange', onWindowHashChange, false );
417
567
  window.removeEventListener( 'resize', onWindowResize, false );
418
568
 
419
- if( config.touch ) {
420
- document.removeEventListener( 'touchstart', onDocumentTouchStart, false );
421
- document.removeEventListener( 'touchmove', onDocumentTouchMove, false );
422
- document.removeEventListener( 'touchend', onDocumentTouchEnd, false );
569
+ dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
570
+ dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
571
+ dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
572
+
573
+ if( window.navigator.msPointerEnabled ) {
574
+ dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
575
+ dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
576
+ dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
423
577
  }
424
578
 
425
579
  if ( config.progress && dom.progress ) {
426
- dom.progress.removeEventListener( 'click', preventAndForward( onProgressClick ), false );
580
+ dom.progress.removeEventListener( 'click', onProgressClicked, false );
427
581
  }
428
582
 
429
583
  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 ); } );
584
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
585
+ dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
586
+ dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
587
+ dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
588
+ dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
589
+ dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
590
+ dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
591
+ } );
437
592
  }
438
593
 
439
594
  }
@@ -476,18 +631,46 @@ var Reveal = (function(){
476
631
  }
477
632
 
478
633
  /**
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
634
+ * Retrieves the height of the given element by looking
635
+ * at the position and height of its immediate children.
484
636
  */
485
- function preventAndForward( delegate ) {
637
+ function getAbsoluteHeight( element ) {
486
638
 
487
- return function( event ) {
488
- event.preventDefault();
489
- delegate.call( null, event );
490
- };
639
+ var height = 0;
640
+
641
+ if( element ) {
642
+ var absoluteChildren = 0;
643
+
644
+ toArray( element.childNodes ).forEach( function( child ) {
645
+
646
+ if( typeof child.offsetTop === 'number' && child.style ) {
647
+ // Count # of abs children
648
+ if( child.style.position === 'absolute' ) {
649
+ absoluteChildren += 1;
650
+ }
651
+
652
+ height = Math.max( height, child.offsetTop + child.offsetHeight );
653
+ }
654
+
655
+ } );
656
+
657
+ // If there are no absolute children, use offsetHeight
658
+ if( absoluteChildren === 0 ) {
659
+ height = element.offsetHeight;
660
+ }
661
+
662
+ }
663
+
664
+ return height;
665
+
666
+ }
667
+
668
+ /**
669
+ * Checks if this instance is being used to print a PDF.
670
+ */
671
+ function isPrintingPDF() {
672
+
673
+ return ( /print-pdf/gi ).test( window.location.search );
491
674
 
492
675
  }
493
676
 
@@ -497,9 +680,18 @@ var Reveal = (function(){
497
680
  */
498
681
  function removeAddressBar() {
499
682
 
683
+ if( window.orientation === 0 ) {
684
+ document.documentElement.style.overflow = 'scroll';
685
+ document.body.style.height = '120%';
686
+ }
687
+ else {
688
+ document.documentElement.style.overflow = '';
689
+ document.body.style.height = '100%';
690
+ }
691
+
500
692
  setTimeout( function() {
501
693
  window.scrollTo( 0, 1 );
502
- }, 0 );
694
+ }, 10 );
503
695
 
504
696
  }
505
697
 
@@ -519,42 +711,222 @@ var Reveal = (function(){
519
711
  /**
520
712
  * Wrap all links in 3D goodness.
521
713
  */
522
- function linkify() {
714
+ function enableRollingLinks() {
523
715
 
524
716
  if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
525
- var nodes = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
717
+ var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
526
718
 
527
- for( var i = 0, len = nodes.length; i < len; i++ ) {
528
- var node = nodes[i];
719
+ for( var i = 0, len = anchors.length; i < len; i++ ) {
720
+ var anchor = anchors[i];
529
721
 
530
- if( node.textContent && !node.querySelector( '*' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) {
722
+ if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {
531
723
  var span = document.createElement('span');
532
- span.setAttribute('data-title', node.text);
533
- span.innerHTML = node.innerHTML;
724
+ span.setAttribute('data-title', anchor.text);
725
+ span.innerHTML = anchor.innerHTML;
534
726
 
535
- node.classList.add( 'roll' );
536
- node.innerHTML = '';
537
- node.appendChild(span);
727
+ anchor.classList.add( 'roll' );
728
+ anchor.innerHTML = '';
729
+ anchor.appendChild(span);
538
730
  }
539
731
  }
540
732
  }
541
733
 
542
734
  }
543
735
 
736
+ /**
737
+ * Unwrap all 3D links.
738
+ */
739
+ function disableRollingLinks() {
740
+
741
+ var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
742
+
743
+ for( var i = 0, len = anchors.length; i < len; i++ ) {
744
+ var anchor = anchors[i];
745
+ var span = anchor.querySelector( 'span' );
746
+
747
+ if( span ) {
748
+ anchor.classList.remove( 'roll' );
749
+ anchor.innerHTML = span.innerHTML;
750
+ }
751
+ }
752
+
753
+ }
754
+
755
+ /**
756
+ * Bind preview frame links.
757
+ */
758
+ function enablePreviewLinks( selector ) {
759
+
760
+ var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
761
+
762
+ anchors.forEach( function( element ) {
763
+ if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
764
+ element.addEventListener( 'click', onPreviewLinkClicked, false );
765
+ }
766
+ } );
767
+
768
+ }
769
+
770
+ /**
771
+ * Unbind preview frame links.
772
+ */
773
+ function disablePreviewLinks() {
774
+
775
+ var anchors = toArray( document.querySelectorAll( 'a' ) );
776
+
777
+ anchors.forEach( function( element ) {
778
+ if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
779
+ element.removeEventListener( 'click', onPreviewLinkClicked, false );
780
+ }
781
+ } );
782
+
783
+ }
784
+
785
+ /**
786
+ * Opens a preview window for the target URL.
787
+ */
788
+ function openPreview( url ) {
789
+
790
+ closePreview();
791
+
792
+ dom.preview = document.createElement( 'div' );
793
+ dom.preview.classList.add( 'preview-link-overlay' );
794
+ dom.wrapper.appendChild( dom.preview );
795
+
796
+ dom.preview.innerHTML = [
797
+ '<header>',
798
+ '<a class="close" href="#"><span class="icon"></span></a>',
799
+ '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
800
+ '</header>',
801
+ '<div class="spinner"></div>',
802
+ '<div class="viewport">',
803
+ '<iframe src="'+ url +'"></iframe>',
804
+ '</div>'
805
+ ].join('');
806
+
807
+ dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
808
+ dom.preview.classList.add( 'loaded' );
809
+ }, false );
810
+
811
+ dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
812
+ closePreview();
813
+ event.preventDefault();
814
+ }, false );
815
+
816
+ dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
817
+ closePreview();
818
+ }, false );
819
+
820
+ setTimeout( function() {
821
+ dom.preview.classList.add( 'visible' );
822
+ }, 1 );
823
+
824
+ }
825
+
826
+ /**
827
+ * Closes the iframe preview window.
828
+ */
829
+ function closePreview() {
830
+
831
+ if( dom.preview ) {
832
+ dom.preview.setAttribute( 'src', '' );
833
+ dom.preview.parentNode.removeChild( dom.preview );
834
+ dom.preview = null;
835
+ }
836
+
837
+ }
838
+
839
+ /**
840
+ * Return a sorted fragments list, ordered by an increasing
841
+ * "data-fragment-index" attribute.
842
+ *
843
+ * Fragments will be revealed in the order that they are returned by
844
+ * this function, so you can use the index attributes to control the
845
+ * order of fragment appearance.
846
+ *
847
+ * To maintain a sensible default fragment order, fragments are presumed
848
+ * to be passed in document order. This function adds a "fragment-index"
849
+ * attribute to each node if such an attribute is not already present,
850
+ * and sets that attribute to an integer value which is the position of
851
+ * the fragment within the fragments list.
852
+ */
853
+ function sortFragments( fragments ) {
854
+
855
+ var a = toArray( fragments );
856
+
857
+ a.forEach( function( el, idx ) {
858
+ if( !el.hasAttribute( 'data-fragment-index' ) ) {
859
+ el.setAttribute( 'data-fragment-index', idx );
860
+ }
861
+ } );
862
+
863
+ a.sort( function( l, r ) {
864
+ return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
865
+ } );
866
+
867
+ return a;
868
+
869
+ }
870
+
544
871
  /**
545
872
  * Applies JavaScript-controlled layout rules to the
546
873
  * presentation.
547
874
  */
548
875
  function layout() {
549
876
 
550
- if( config.center ) {
877
+ if( dom.wrapper && !isPrintingPDF() ) {
878
+
879
+ // Available space to scale within
880
+ var availableWidth = dom.wrapper.offsetWidth,
881
+ availableHeight = dom.wrapper.offsetHeight;
882
+
883
+ // Reduce available space by margin
884
+ availableWidth -= ( availableHeight * config.margin );
885
+ availableHeight -= ( availableHeight * config.margin );
886
+
887
+ // Dimensions of the content
888
+ var slideWidth = config.width,
889
+ slideHeight = config.height;
890
+
891
+ // Slide width may be a percentage of available width
892
+ if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
893
+ slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
894
+ }
895
+
896
+ // Slide height may be a percentage of available height
897
+ if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
898
+ slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
899
+ }
900
+
901
+ dom.slides.style.width = slideWidth + 'px';
902
+ dom.slides.style.height = slideHeight + 'px';
903
+
904
+ // Determine scale of content to fit within available space
905
+ scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
906
+
907
+ // Respect max/min scale settings
908
+ scale = Math.max( scale, config.minScale );
909
+ scale = Math.min( scale, config.maxScale );
910
+
911
+ // Prefer applying scale via zoom since Chrome blurs scaled content
912
+ // with nested transforms
913
+ if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
914
+ dom.slides.style.zoom = scale;
915
+ }
916
+ // Apply scale transform as a fallback
917
+ else {
918
+ var transform = 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)';
919
+
920
+ dom.slides.style.WebkitTransform = transform;
921
+ dom.slides.style.MozTransform = transform;
922
+ dom.slides.style.msTransform = transform;
923
+ dom.slides.style.OTransform = transform;
924
+ dom.slides.style.transform = transform;
925
+ }
551
926
 
552
927
  // Select all slides, vertical and horizontal
553
928
  var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
554
929
 
555
- // Determine the minimum top offset for slides
556
- var minTop = -dom.wrapper.offsetHeight / 2;
557
-
558
930
  for( var i = 0, len = slides.length; i < len; i++ ) {
559
931
  var slide = slides[ i ];
560
932
 
@@ -563,16 +935,24 @@ var Reveal = (function(){
563
935
  continue;
564
936
  }
565
937
 
566
- // Vertical stacks are not centered since their section
567
- // children will be
568
- if( slide.classList.contains( 'stack' ) ) {
569
- slide.style.top = 0;
938
+ if( config.center ) {
939
+ // Vertical stacks are not centred since their section
940
+ // children will be
941
+ if( slide.classList.contains( 'stack' ) ) {
942
+ slide.style.top = 0;
943
+ }
944
+ else {
945
+ slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - 20, -slideHeight / 2 ) + 'px';
946
+ }
570
947
  }
571
948
  else {
572
- slide.style.top = Math.max( - ( slide.offsetHeight / 2 ) - 20, minTop ) + 'px';
949
+ slide.style.top = '';
573
950
  }
951
+
574
952
  }
575
953
 
954
+ updateProgress();
955
+
576
956
  }
577
957
 
578
958
  }
@@ -587,14 +967,14 @@ var Reveal = (function(){
587
967
  */
588
968
  function setPreviousVerticalIndex( stack, v ) {
589
969
 
590
- if( stack ) {
970
+ if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
591
971
  stack.setAttribute( 'data-previous-indexv', v || 0 );
592
972
  }
593
973
 
594
974
  }
595
975
 
596
976
  /**
597
- * Retrieves the vertical index which was stored using
977
+ * Retrieves the vertical index which was stored using
598
978
  * #setPreviousVerticalIndex() or 0 if no previous index
599
979
  * exists.
600
980
  *
@@ -602,8 +982,11 @@ var Reveal = (function(){
602
982
  */
603
983
  function getPreviousVerticalIndex( stack ) {
604
984
 
605
- if( stack && stack.classList.contains( 'stack' ) ) {
606
- return parseInt( stack.getAttribute( 'data-previous-indexv' ) || 0, 10 );
985
+ if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
986
+ // Prefer manually defined start-indexv
987
+ var attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';
988
+
989
+ return parseInt( stack.getAttribute( attributeName ) || 0, 10 );
607
990
  }
608
991
 
609
992
  return 0;
@@ -622,6 +1005,9 @@ var Reveal = (function(){
622
1005
  // Only proceed if enabled in config
623
1006
  if( config.overview ) {
624
1007
 
1008
+ // Don't auto-slide while in overview mode
1009
+ cancelAutoSlide();
1010
+
625
1011
  var wasActive = dom.wrapper.classList.contains( 'overview' );
626
1012
 
627
1013
  dom.wrapper.classList.add( 'overview' );
@@ -639,7 +1025,8 @@ var Reveal = (function(){
639
1025
 
640
1026
  for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
641
1027
  var hslide = horizontalSlides[i],
642
- htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * 105 ) + '%, 0%)';
1028
+ hoffset = config.rtl ? -105 : 105,
1029
+ htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)';
643
1030
 
644
1031
  hslide.setAttribute( 'data-index-h', i );
645
1032
  hslide.style.display = 'block';
@@ -741,6 +1128,8 @@ var Reveal = (function(){
741
1128
 
742
1129
  slide( indexh, indexv );
743
1130
 
1131
+ cueAutoSlide();
1132
+
744
1133
  // Notify observers of the overview hiding
745
1134
  dispatchEvent( 'overviewhidden', {
746
1135
  'indexh': indexh,
@@ -764,7 +1153,7 @@ var Reveal = (function(){
764
1153
  override ? activateOverview() : deactivateOverview();
765
1154
  }
766
1155
  else {
767
- isOverviewActive() ? deactivateOverview() : activateOverview();
1156
+ isOverview() ? deactivateOverview() : activateOverview();
768
1157
  }
769
1158
 
770
1159
  }
@@ -775,12 +1164,28 @@ var Reveal = (function(){
775
1164
  * @return {Boolean} true if the overview is active,
776
1165
  * false otherwise
777
1166
  */
778
- function isOverviewActive() {
1167
+ function isOverview() {
779
1168
 
780
1169
  return dom.wrapper.classList.contains( 'overview' );
781
1170
 
782
1171
  }
783
1172
 
1173
+ /**
1174
+ * Checks if the current or specified slide is vertical
1175
+ * (nested within another slide).
1176
+ *
1177
+ * @param {HTMLElement} slide [optional] The slide to check
1178
+ * orientation of
1179
+ */
1180
+ function isVerticalSlide( slide ) {
1181
+
1182
+ // Prefer slide argument, otherwise use current slide
1183
+ slide = slide ? slide : currentSlide;
1184
+
1185
+ return slide && !!slide.parentNode.nodeName.match( /section/i );
1186
+
1187
+ }
1188
+
784
1189
  /**
785
1190
  * Handling the fullscreen functionality via the fullscreen API
786
1191
  *
@@ -793,6 +1198,7 @@ var Reveal = (function(){
793
1198
 
794
1199
  // Check which implementation is available
795
1200
  var requestMethod = element.requestFullScreen ||
1201
+ element.webkitRequestFullscreen ||
796
1202
  element.webkitRequestFullScreen ||
797
1203
  element.mozRequestFullScreen ||
798
1204
  element.msRequestFullScreen;
@@ -809,8 +1215,15 @@ var Reveal = (function(){
809
1215
  */
810
1216
  function pause() {
811
1217
 
1218
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1219
+
1220
+ cancelAutoSlide();
812
1221
  dom.wrapper.classList.add( 'paused' );
813
1222
 
1223
+ if( wasPaused === false ) {
1224
+ dispatchEvent( 'paused' );
1225
+ }
1226
+
814
1227
  }
815
1228
 
816
1229
  /**
@@ -818,8 +1231,15 @@ var Reveal = (function(){
818
1231
  */
819
1232
  function resume() {
820
1233
 
1234
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
821
1235
  dom.wrapper.classList.remove( 'paused' );
822
1236
 
1237
+ cueAutoSlide();
1238
+
1239
+ if( wasPaused ) {
1240
+ dispatchEvent( 'resumed' );
1241
+ }
1242
+
823
1243
  }
824
1244
 
825
1245
  /**
@@ -854,8 +1274,9 @@ var Reveal = (function(){
854
1274
  * @param {int} v Vertical index of the target slide
855
1275
  * @param {int} f Optional index of a fragment within the
856
1276
  * target slide to activate
1277
+ * @param {int} o Optional origin for use in multimaster environments
857
1278
  */
858
- function slide( h, v, f ) {
1279
+ function slide( h, v, f, o ) {
859
1280
 
860
1281
  // Remember where we were at before
861
1282
  previousSlide = currentSlide;
@@ -913,7 +1334,7 @@ var Reveal = (function(){
913
1334
  }
914
1335
 
915
1336
  // If the overview is active, re-activate it to update positions
916
- if( isOverviewActive() ) {
1337
+ if( isOverview() ) {
917
1338
  activateOverview();
918
1339
  }
919
1340
 
@@ -932,7 +1353,7 @@ var Reveal = (function(){
932
1353
 
933
1354
  // Show fragment, if specified
934
1355
  if( typeof f !== 'undefined' ) {
935
- var fragments = currentSlide.querySelectorAll( '.fragment' );
1356
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
936
1357
 
937
1358
  toArray( fragments ).forEach( function( fragment, indexf ) {
938
1359
  if( indexf < f ) {
@@ -945,12 +1366,14 @@ var Reveal = (function(){
945
1366
  }
946
1367
 
947
1368
  // Dispatch an event if the slide changed
948
- if( indexh !== indexhBefore || indexv !== indexvBefore ) {
1369
+ var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
1370
+ if( slideChanged ) {
949
1371
  dispatchEvent( 'slidechanged', {
950
1372
  'indexh': indexh,
951
1373
  'indexv': indexv,
952
1374
  'previousSlide': previousSlide,
953
- 'currentSlide': currentSlide
1375
+ 'currentSlide': currentSlide,
1376
+ 'origin': o
954
1377
  } );
955
1378
  }
956
1379
  else {
@@ -980,8 +1403,44 @@ var Reveal = (function(){
980
1403
  }
981
1404
  }
982
1405
 
1406
+ // Handle embedded content
1407
+ if( slideChanged ) {
1408
+ stopEmbeddedContent( previousSlide );
1409
+ startEmbeddedContent( currentSlide );
1410
+ }
1411
+
1412
+ updateControls();
1413
+ updateProgress();
1414
+ updateBackground();
1415
+
1416
+ }
1417
+
1418
+ /**
1419
+ * Syncs the presentation with the current DOM. Useful
1420
+ * when new slides or control elements are added or when
1421
+ * the configuration has changed.
1422
+ */
1423
+ function sync() {
1424
+
1425
+ // Subscribe to input
1426
+ removeEventListeners();
1427
+ addEventListeners();
1428
+
1429
+ // Force a layout to make sure the current config is accounted for
1430
+ layout();
1431
+
1432
+ // Reflect the current autoSlide value
1433
+ autoSlide = config.autoSlide;
1434
+
1435
+ // Start auto-sliding if it's enabled
1436
+ cueAutoSlide();
1437
+
1438
+ // Re-create the slide backgrounds
1439
+ createBackgrounds();
1440
+
983
1441
  updateControls();
984
1442
  updateProgress();
1443
+ updateBackground();
985
1444
 
986
1445
  }
987
1446
 
@@ -1024,7 +1483,7 @@ var Reveal = (function(){
1024
1483
 
1025
1484
  // Optimization; hide all slides that are three or more steps
1026
1485
  // away from the present slide
1027
- if( isOverviewActive() === false ) {
1486
+ if( isOverview() === false ) {
1028
1487
  // The distance loops so that it measures 1 between the first
1029
1488
  // and last slides
1030
1489
  var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
@@ -1032,27 +1491,33 @@ var Reveal = (function(){
1032
1491
  element.style.display = distance > 3 ? 'none' : 'block';
1033
1492
  }
1034
1493
 
1035
- slides[i].classList.remove( 'past' );
1036
- slides[i].classList.remove( 'present' );
1037
- slides[i].classList.remove( 'future' );
1494
+ var reverse = config.rtl && !isVerticalSlide( element );
1495
+
1496
+ element.classList.remove( 'past' );
1497
+ element.classList.remove( 'present' );
1498
+ element.classList.remove( 'future' );
1499
+
1500
+ // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1501
+ element.setAttribute( 'hidden', '' );
1038
1502
 
1039
1503
  if( i < index ) {
1040
1504
  // Any element previous to index is given the 'past' class
1041
- slides[i].classList.add( 'past' );
1505
+ element.classList.add( reverse ? 'future' : 'past' );
1042
1506
  }
1043
1507
  else if( i > index ) {
1044
1508
  // Any element subsequent to index is given the 'future' class
1045
- slides[i].classList.add( 'future' );
1509
+ element.classList.add( reverse ? 'past' : 'future' );
1046
1510
  }
1047
1511
 
1048
1512
  // If this element contains vertical slides
1049
1513
  if( element.querySelector( 'section' ) ) {
1050
- slides[i].classList.add( 'stack' );
1514
+ element.classList.add( 'stack' );
1051
1515
  }
1052
1516
  }
1053
1517
 
1054
1518
  // Mark the current slide as present
1055
1519
  slides[index].classList.add( 'present' );
1520
+ slides[index].removeAttribute( 'hidden' );
1056
1521
 
1057
1522
  // If this slide has a state associated with it, add it
1058
1523
  // onto the current state of the deck
@@ -1066,7 +1531,7 @@ var Reveal = (function(){
1066
1531
  var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
1067
1532
  if( slideAutoSlide ) {
1068
1533
  autoSlide = parseInt( slideAutoSlide, 10 );
1069
- }
1534
+ }
1070
1535
  else {
1071
1536
  autoSlide = config.autoSlide;
1072
1537
  }
@@ -1139,6 +1604,7 @@ var Reveal = (function(){
1139
1604
  if ( config.controls && dom.controls ) {
1140
1605
 
1141
1606
  var routes = availableRoutes();
1607
+ var fragments = availableFragments();
1142
1608
 
1143
1609
  // Remove the 'enabled' class from all directions
1144
1610
  dom.controlsLeft.concat( dom.controlsRight )
@@ -1147,6 +1613,7 @@ var Reveal = (function(){
1147
1613
  .concat( dom.controlsPrev )
1148
1614
  .concat( dom.controlsNext ).forEach( function( node ) {
1149
1615
  node.classList.remove( 'enabled' );
1616
+ node.classList.remove( 'fragmented' );
1150
1617
  } );
1151
1618
 
1152
1619
  // Add the 'enabled' class to the available routes
@@ -1159,10 +1626,60 @@ var Reveal = (function(){
1159
1626
  if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1160
1627
  if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1161
1628
 
1629
+ // Highlight fragment directions
1630
+ if( currentSlide ) {
1631
+
1632
+ // Always apply fragment decorator to prev/next buttons
1633
+ if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1634
+ if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1635
+
1636
+ // Apply fragment decorators to directional buttons based on
1637
+ // what slide axis they are in
1638
+ if( isVerticalSlide( currentSlide ) ) {
1639
+ if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1640
+ if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1641
+ }
1642
+ else {
1643
+ if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1644
+ if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1645
+ }
1646
+ }
1647
+
1162
1648
  }
1163
1649
 
1164
1650
  }
1165
1651
 
1652
+ /**
1653
+ * Updates the background elements to reflect the current
1654
+ * slide.
1655
+ */
1656
+ function updateBackground() {
1657
+
1658
+ // Update the classes of all backgrounds to match the
1659
+ // states of their slides (past/present/future)
1660
+ toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1661
+
1662
+ // Reverse past/future classes when in RTL mode
1663
+ var horizontalPast = config.rtl ? 'future' : 'past',
1664
+ horizontalFuture = config.rtl ? 'past' : 'future';
1665
+
1666
+ backgroundh.className = 'slide-background ' + ( h < indexh ? horizontalPast : h > indexh ? horizontalFuture : 'present' );
1667
+
1668
+ toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1669
+
1670
+ backgroundv.className = 'slide-background ' + ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
1671
+
1672
+ } );
1673
+
1674
+ } );
1675
+
1676
+ // Allow the first background to apply without transition
1677
+ setTimeout( function() {
1678
+ dom.background.classList.remove( 'no-transition' );
1679
+ }, 1 );
1680
+
1681
+ }
1682
+
1166
1683
  /**
1167
1684
  * Determine what available routes there are for navigation.
1168
1685
  *
@@ -1173,13 +1690,93 @@ var Reveal = (function(){
1173
1690
  var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
1174
1691
  verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
1175
1692
 
1176
- return {
1177
- left: indexh > 0,
1178
- right: indexh < horizontalSlides.length - 1,
1693
+ var routes = {
1694
+ left: indexh > 0 || config.loop,
1695
+ right: indexh < horizontalSlides.length - 1 || config.loop,
1179
1696
  up: indexv > 0,
1180
1697
  down: indexv < verticalSlides.length - 1
1181
1698
  };
1182
1699
 
1700
+ // reverse horizontal controls for rtl
1701
+ if( config.rtl ) {
1702
+ var left = routes.left;
1703
+ routes.left = routes.right;
1704
+ routes.right = left;
1705
+ }
1706
+
1707
+ return routes;
1708
+
1709
+ }
1710
+
1711
+ /**
1712
+ * Returns an object describing the available fragment
1713
+ * directions.
1714
+ *
1715
+ * @return {Object} two boolean properties: prev/next
1716
+ */
1717
+ function availableFragments() {
1718
+
1719
+ if( currentSlide && config.fragments ) {
1720
+ var fragments = currentSlide.querySelectorAll( '.fragment' );
1721
+ var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
1722
+
1723
+ return {
1724
+ prev: fragments.length - hiddenFragments.length > 0,
1725
+ next: !!hiddenFragments.length
1726
+ };
1727
+ }
1728
+ else {
1729
+ return { prev: false, next: false };
1730
+ }
1731
+
1732
+ }
1733
+
1734
+ /**
1735
+ * Start playback of any embedded content inside of
1736
+ * the targeted slide.
1737
+ */
1738
+ function startEmbeddedContent( slide ) {
1739
+
1740
+ if( slide ) {
1741
+ // HTML5 media elements
1742
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
1743
+ if( el.hasAttribute( 'data-autoplay' ) ) {
1744
+ el.play();
1745
+ }
1746
+ } );
1747
+
1748
+ // YouTube embeds
1749
+ toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1750
+ if( el.hasAttribute( 'data-autoplay' ) ) {
1751
+ el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
1752
+ }
1753
+ });
1754
+ }
1755
+
1756
+ }
1757
+
1758
+ /**
1759
+ * Stop playback of any embedded content inside of
1760
+ * the targeted slide.
1761
+ */
1762
+ function stopEmbeddedContent( slide ) {
1763
+
1764
+ if( slide ) {
1765
+ // HTML5 media elements
1766
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
1767
+ if( !el.hasAttribute( 'data-ignore' ) ) {
1768
+ el.pause();
1769
+ }
1770
+ } );
1771
+
1772
+ // YouTube embeds
1773
+ toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1774
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
1775
+ el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
1776
+ }
1777
+ });
1778
+ }
1779
+
1183
1780
  }
1184
1781
 
1185
1782
  /**
@@ -1264,17 +1861,18 @@ var Reveal = (function(){
1264
1861
  * index will be for this slide rather than the currently
1265
1862
  * active one
1266
1863
  *
1267
- * @return {Object} { h: <int>, v: <int> }
1864
+ * @return {Object} { h: <int>, v: <int>, f: <int> }
1268
1865
  */
1269
1866
  function getIndices( slide ) {
1270
1867
 
1271
1868
  // By default, return the current indices
1272
1869
  var h = indexh,
1273
- v = indexv;
1870
+ v = indexv,
1871
+ f;
1274
1872
 
1275
1873
  // If a slide is specified, return the indices of that slide
1276
1874
  if( slide ) {
1277
- var isVertical = !!slide.parentNode.nodeName.match( /section/gi );
1875
+ var isVertical = isVerticalSlide( slide );
1278
1876
  var slideh = isVertical ? slide.parentNode : slide;
1279
1877
 
1280
1878
  // Select all horizontal slides
@@ -1289,7 +1887,14 @@ var Reveal = (function(){
1289
1887
  }
1290
1888
  }
1291
1889
 
1292
- return { h: h, v: v };
1890
+ if( !slide && currentSlide ) {
1891
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
1892
+ if( visibleFragments.length ) {
1893
+ f = visibleFragments.length;
1894
+ }
1895
+ }
1896
+
1897
+ return { h: h, v: v, f: f };
1293
1898
 
1294
1899
  }
1295
1900
 
@@ -1301,25 +1906,24 @@ var Reveal = (function(){
1301
1906
  */
1302
1907
  function nextFragment() {
1303
1908
 
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' );
1909
+ if( currentSlide && config.fragments ) {
1910
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );
1309
1911
 
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' );
1912
+ if( fragments.length ) {
1913
+ // Find the index of the next fragment
1914
+ var index = fragments[0].getAttribute( 'data-fragment-index' );
1915
+
1916
+ // Find all fragments with the same index
1917
+ fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
1918
+
1919
+ toArray( fragments ).forEach( function( element ) {
1920
+ element.classList.add( 'visible' );
1921
+ } );
1320
1922
 
1321
1923
  // Notify subscribers of the change
1322
- dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } );
1924
+ dispatchEvent( 'fragmentshown', { fragment: fragments[0], fragments: fragments } );
1925
+
1926
+ updateControls();
1323
1927
  return true;
1324
1928
  }
1325
1929
  }
@@ -1336,25 +1940,24 @@ var Reveal = (function(){
1336
1940
  */
1337
1941
  function previousFragment() {
1338
1942
 
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' );
1943
+ if( currentSlide && config.fragments ) {
1944
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );
1344
1945
 
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' );
1946
+ if( fragments.length ) {
1947
+ // Find the index of the previous fragment
1948
+ var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
1949
+
1950
+ // Find all fragments with the same index
1951
+ fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
1952
+
1953
+ toArray( fragments ).forEach( function( f ) {
1954
+ f.classList.remove( 'visible' );
1955
+ } );
1355
1956
 
1356
1957
  // Notify subscribers of the change
1357
- dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } );
1958
+ dispatchEvent( 'fragmenthidden', { fragment: fragments[0], fragments: fragments } );
1959
+
1960
+ updateControls();
1358
1961
  return true;
1359
1962
  }
1360
1963
  }
@@ -1371,16 +1974,31 @@ var Reveal = (function(){
1371
1974
  clearTimeout( autoSlideTimeout );
1372
1975
 
1373
1976
  // Cue the next auto-slide if enabled
1374
- if( autoSlide ) {
1977
+ if( autoSlide && !isPaused() && !isOverview() ) {
1375
1978
  autoSlideTimeout = setTimeout( navigateNext, autoSlide );
1376
1979
  }
1377
1980
 
1378
1981
  }
1379
1982
 
1983
+ /**
1984
+ * Cancels any ongoing request to auto-slide.
1985
+ */
1986
+ function cancelAutoSlide() {
1987
+
1988
+ clearTimeout( autoSlideTimeout );
1989
+
1990
+ }
1991
+
1380
1992
  function navigateLeft() {
1381
1993
 
1382
- // Prioritize hiding fragments
1383
- if( availableRoutes().left && isOverviewActive() || previousFragment() === false ) {
1994
+ // Reverse for RTL
1995
+ if( config.rtl ) {
1996
+ if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
1997
+ slide( indexh + 1 );
1998
+ }
1999
+ }
2000
+ // Normal navigation
2001
+ else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
1384
2002
  slide( indexh - 1 );
1385
2003
  }
1386
2004
 
@@ -1388,8 +2006,14 @@ var Reveal = (function(){
1388
2006
 
1389
2007
  function navigateRight() {
1390
2008
 
1391
- // Prioritize revealing fragments
1392
- if( availableRoutes().right && isOverviewActive() || nextFragment() === false ) {
2009
+ // Reverse for RTL
2010
+ if( config.rtl ) {
2011
+ if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
2012
+ slide( indexh - 1 );
2013
+ }
2014
+ }
2015
+ // Normal navigation
2016
+ else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
1393
2017
  slide( indexh + 1 );
1394
2018
  }
1395
2019
 
@@ -1398,7 +2022,7 @@ var Reveal = (function(){
1398
2022
  function navigateUp() {
1399
2023
 
1400
2024
  // Prioritize hiding fragments
1401
- if( availableRoutes().up && isOverviewActive() || previousFragment() === false ) {
2025
+ if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
1402
2026
  slide( indexh, indexv - 1 );
1403
2027
  }
1404
2028
 
@@ -1407,7 +2031,7 @@ var Reveal = (function(){
1407
2031
  function navigateDown() {
1408
2032
 
1409
2033
  // Prioritize revealing fragments
1410
- if( availableRoutes().down && isOverviewActive() || nextFragment() === false ) {
2034
+ if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
1411
2035
  slide( indexh, indexv + 1 );
1412
2036
  }
1413
2037
 
@@ -1431,9 +2055,9 @@ var Reveal = (function(){
1431
2055
  var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
1432
2056
 
1433
2057
  if( previousSlide ) {
1434
- indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
1435
- indexh --;
1436
- slide();
2058
+ var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
2059
+ var h = indexh - 1;
2060
+ slide( h, v );
1437
2061
  }
1438
2062
  }
1439
2063
  }
@@ -1476,37 +2100,77 @@ var Reveal = (function(){
1476
2100
 
1477
2101
  // Disregard the event if there's a focused element or a
1478
2102
  // 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;
2103
+ if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
2104
+
2105
+ // While paused only allow "unpausing" keyboard events (b and .)
2106
+ if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
2107
+ return false;
2108
+ }
2109
+
2110
+ var triggered = false;
2111
+
2112
+ // 1. User defined key bindings
2113
+ if( typeof config.keyboard === 'object' ) {
2114
+
2115
+ for( var key in config.keyboard ) {
2116
+
2117
+ // Check if this binding matches the pressed key
2118
+ if( parseInt( key, 10 ) === event.keyCode ) {
2119
+
2120
+ var value = config.keyboard[ key ];
2121
+
2122
+ // Calback function
2123
+ if( typeof value === 'function' ) {
2124
+ value.apply( null, [ event ] );
2125
+ }
2126
+ // String shortcuts to reveal.js API
2127
+ else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) {
2128
+ Reveal[ value ].call();
2129
+ }
2130
+
2131
+ triggered = true;
2132
+
2133
+ }
2134
+
2135
+ }
2136
+
2137
+ }
2138
+
2139
+ // 2. System defined key bindings
2140
+ if( triggered === false ) {
2141
+
2142
+ // Assume true and try to prove false
2143
+ triggered = true;
2144
+
2145
+ switch( event.keyCode ) {
2146
+ // p, page up
2147
+ case 80: case 33: navigatePrev(); break;
2148
+ // n, page down
2149
+ case 78: case 34: navigateNext(); break;
2150
+ // h, left
2151
+ case 72: case 37: navigateLeft(); break;
2152
+ // l, right
2153
+ case 76: case 39: navigateRight(); break;
2154
+ // k, up
2155
+ case 75: case 38: navigateUp(); break;
2156
+ // j, down
2157
+ case 74: case 40: navigateDown(); break;
2158
+ // home
2159
+ case 36: slide( 0 ); break;
2160
+ // end
2161
+ case 35: slide( Number.MAX_VALUE ); break;
2162
+ // space
2163
+ case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2164
+ // return
2165
+ case 13: isOverview() ? deactivateOverview() : triggered = false; break;
2166
+ // b, period, Logitech presenter tools "black screen" button
2167
+ case 66: case 190: case 191: togglePause(); break;
2168
+ // f
2169
+ case 70: enterFullscreen(); break;
2170
+ default:
2171
+ triggered = false;
2172
+ }
2173
+
1510
2174
  }
1511
2175
 
1512
2176
  // If the input resulted in a triggered action we should prevent
@@ -1527,10 +2191,10 @@ var Reveal = (function(){
1527
2191
  }
1528
2192
 
1529
2193
  /**
1530
- * Handler for the document level 'touchstart' event,
1531
- * enables support for swipe and pinch gestures.
2194
+ * Handler for the 'touchstart' event, enables support for
2195
+ * swipe and pinch gestures.
1532
2196
  */
1533
- function onDocumentTouchStart( event ) {
2197
+ function onTouchStart( event ) {
1534
2198
 
1535
2199
  touch.startX = event.touches[0].clientX;
1536
2200
  touch.startY = event.touches[0].clientY;
@@ -1551,9 +2215,9 @@ var Reveal = (function(){
1551
2215
  }
1552
2216
 
1553
2217
  /**
1554
- * Handler for the document level 'touchmove' event.
2218
+ * Handler for the 'touchmove' event.
1555
2219
  */
1556
- function onDocumentTouchMove( event ) {
2220
+ function onTouchMove( event ) {
1557
2221
 
1558
2222
  // Each touch should only trigger one action
1559
2223
  if( !touch.handled ) {
@@ -1625,23 +2289,60 @@ var Reveal = (function(){
1625
2289
  }
1626
2290
 
1627
2291
  /**
1628
- * Handler for the document level 'touchend' event.
2292
+ * Handler for the 'touchend' event.
1629
2293
  */
1630
- function onDocumentTouchEnd( event ) {
2294
+ function onTouchEnd( event ) {
1631
2295
 
1632
2296
  touch.handled = false;
1633
2297
 
1634
2298
  }
1635
2299
 
2300
+ /**
2301
+ * Convert pointer down to touch start.
2302
+ */
2303
+ function onPointerDown( event ) {
2304
+
2305
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2306
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2307
+ onTouchStart( event );
2308
+ }
2309
+
2310
+ }
2311
+
2312
+ /**
2313
+ * Convert pointer move to touch move.
2314
+ */
2315
+ function onPointerMove( event ) {
2316
+
2317
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2318
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2319
+ onTouchMove( event );
2320
+ }
2321
+
2322
+ }
2323
+
2324
+ /**
2325
+ * Convert pointer up to touch end.
2326
+ */
2327
+ function onPointerUp( event ) {
2328
+
2329
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2330
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2331
+ onTouchEnd( event );
2332
+ }
2333
+
2334
+ }
2335
+
1636
2336
  /**
1637
2337
  * Handles mouse wheel scrolling, throttled to avoid skipping
1638
2338
  * multiple slides.
1639
2339
  */
1640
2340
  function onDocumentMouseScroll( event ) {
1641
2341
 
1642
- clearTimeout( mouseWheelTimeout );
2342
+ if( Date.now() - lastMouseWheelStep > 600 ) {
2343
+
2344
+ lastMouseWheelStep = Date.now();
1643
2345
 
1644
- mouseWheelTimeout = setTimeout( function() {
1645
2346
  var delta = event.detail || -event.wheelDelta;
1646
2347
  if( delta > 0 ) {
1647
2348
  navigateNext();
@@ -1649,7 +2350,8 @@ var Reveal = (function(){
1649
2350
  else {
1650
2351
  navigatePrev();
1651
2352
  }
1652
- }, 100 );
2353
+
2354
+ }
1653
2355
 
1654
2356
  }
1655
2357
 
@@ -1659,7 +2361,9 @@ var Reveal = (function(){
1659
2361
  *
1660
2362
  * ( clickX / presentationWidth ) * numberOfSlides
1661
2363
  */
1662
- function onProgressClick( event ) {
2364
+ function onProgressClicked( event ) {
2365
+
2366
+ event.preventDefault();
1663
2367
 
1664
2368
  var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
1665
2369
  var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
@@ -1668,6 +2372,16 @@ var Reveal = (function(){
1668
2372
 
1669
2373
  }
1670
2374
 
2375
+ /**
2376
+ * Event handler for navigation control buttons.
2377
+ */
2378
+ function onNavigateLeftClicked( event ) { event.preventDefault(); navigateLeft(); }
2379
+ function onNavigateRightClicked( event ) { event.preventDefault(); navigateRight(); }
2380
+ function onNavigateUpClicked( event ) { event.preventDefault(); navigateUp(); }
2381
+ function onNavigateDownClicked( event ) { event.preventDefault(); navigateDown(); }
2382
+ function onNavigatePrevClicked( event ) { event.preventDefault(); navigatePrev(); }
2383
+ function onNavigateNextClicked( event ) { event.preventDefault(); navigateNext(); }
2384
+
1671
2385
  /**
1672
2386
  * Handler for the window level 'hashchange' event.
1673
2387
  */
@@ -1693,27 +2407,45 @@ var Reveal = (function(){
1693
2407
 
1694
2408
  // TODO There's a bug here where the event listeners are not
1695
2409
  // removed after deactivating the overview.
1696
- if( isOverviewActive() ) {
2410
+ if( eventsAreBound && isOverview() ) {
1697
2411
  event.preventDefault();
1698
2412
 
1699
- deactivateOverview();
1700
-
1701
2413
  var element = event.target;
1702
2414
 
1703
2415
  while( element && !element.nodeName.match( /section/gi ) ) {
1704
2416
  element = element.parentNode;
1705
2417
  }
1706
2418
 
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 );
2419
+ if( element && !element.classList.contains( 'disabled' ) ) {
2420
+
2421
+ deactivateOverview();
2422
+
2423
+ if( element.nodeName.match( /section/gi ) ) {
2424
+ var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
2425
+ v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
2426
+
2427
+ slide( h, v );
2428
+ }
1710
2429
 
1711
- slide( h, v );
1712
2430
  }
1713
2431
  }
1714
2432
 
1715
2433
  }
1716
2434
 
2435
+ /**
2436
+ * Handles clicks on links that are set to preview in the
2437
+ * iframe overlay.
2438
+ */
2439
+ function onPreviewLinkClicked( event ) {
2440
+
2441
+ var url = event.target.getAttribute( 'href' );
2442
+ if( url ) {
2443
+ openPreview( url );
2444
+ event.preventDefault();
2445
+ }
2446
+
2447
+ }
2448
+
1717
2449
 
1718
2450
  // --------------------------------------------------------------------//
1719
2451
  // ------------------------------- API --------------------------------//
@@ -1722,6 +2454,8 @@ var Reveal = (function(){
1722
2454
 
1723
2455
  return {
1724
2456
  initialize: initialize,
2457
+ configure: configure,
2458
+ sync: sync,
1725
2459
 
1726
2460
  // Navigation methods
1727
2461
  slide: slide,
@@ -1746,12 +2480,22 @@ var Reveal = (function(){
1746
2480
  // Forces an update in slide layout
1747
2481
  layout: layout,
1748
2482
 
2483
+ // Returns an object with the available routes as booleans (left/right/top/bottom)
2484
+ availableRoutes: availableRoutes,
2485
+
2486
+ // Returns an object with the available fragments as booleans (prev/next)
2487
+ availableFragments: availableFragments,
2488
+
1749
2489
  // Toggles the overview mode on/off
1750
2490
  toggleOverview: toggleOverview,
1751
2491
 
1752
2492
  // Toggles the "black screen" mode on/off
1753
2493
  togglePause: togglePause,
1754
2494
 
2495
+ // State checks
2496
+ isOverview: isOverview,
2497
+ isPaused: isPaused,
2498
+
1755
2499
  // Adds or removes all internal event listeners (such as keyboard)
1756
2500
  addEventListeners: addEventListeners,
1757
2501
  removeEventListeners: removeEventListeners,
@@ -1759,6 +2503,18 @@ var Reveal = (function(){
1759
2503
  // Returns the indices of the current, or specified, slide
1760
2504
  getIndices: getIndices,
1761
2505
 
2506
+ // Returns the slide at the specified index, y is optional
2507
+ getSlide: function( x, y ) {
2508
+ var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
2509
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
2510
+
2511
+ if( typeof y !== 'undefined' ) {
2512
+ return verticalSlides ? verticalSlides[ y ] : undefined;
2513
+ }
2514
+
2515
+ return horizontalSlide;
2516
+ },
2517
+
1762
2518
  // Returns the previous slide element, may be null
1763
2519
  getPreviousSlide: function() {
1764
2520
  return previousSlide;
@@ -1769,6 +2525,16 @@ var Reveal = (function(){
1769
2525
  return currentSlide;
1770
2526
  },
1771
2527
 
2528
+ // Returns the current scale of the presentation content
2529
+ getScale: function() {
2530
+ return scale;
2531
+ },
2532
+
2533
+ // Returns the current configuration object
2534
+ getConfig: function() {
2535
+ return config;
2536
+ },
2537
+
1772
2538
  // Helper method, retrieves query string as a key/value hash
1773
2539
  getQueryHash: function() {
1774
2540
  var query = {};
@@ -1780,6 +2546,21 @@ var Reveal = (function(){
1780
2546
  return query;
1781
2547
  },
1782
2548
 
2549
+ // Returns true if we're currently on the first slide
2550
+ isFirstSlide: function() {
2551
+ return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
2552
+ },
2553
+
2554
+ // Returns true if we're currently on the last slide
2555
+ isLastSlide: function() {
2556
+ if( currentSlide && currentSlide.classList.contains( '.stack' ) ) {
2557
+ return currentSlide.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
2558
+ }
2559
+ else {
2560
+ return document.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
2561
+ }
2562
+ },
2563
+
1783
2564
  // Forward event binding to the reveal DOM element
1784
2565
  addEventListener: function( type, listener, useCapture ) {
1785
2566
  if( 'addEventListener' in window ) {
@@ -1793,4 +2574,4 @@ var Reveal = (function(){
1793
2574
  }
1794
2575
  };
1795
2576
 
1796
- })();
2577
+ })();