reveal-ck 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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
+ })();