slide_hero 0.0.8 → 0.0.9

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/README.md +1 -1
  4. data/lib/slide_hero/version.rb +1 -1
  5. data/lib/slide_hero/views/layout.html.erb +2 -2
  6. data/vendor/reveal.js/.gitignore +3 -1
  7. data/vendor/reveal.js/.travis.yml +1 -1
  8. data/vendor/reveal.js/CONTRIBUTING.md +19 -0
  9. data/vendor/reveal.js/Gruntfile.js +58 -21
  10. data/vendor/reveal.js/LICENSE +1 -1
  11. data/vendor/reveal.js/README.md +154 -74
  12. data/vendor/reveal.js/css/print/paper.css +193 -167
  13. data/vendor/reveal.js/css/print/pdf.css +20 -53
  14. data/vendor/reveal.js/css/reveal.css +912 -1651
  15. data/vendor/reveal.js/css/reveal.scss +1316 -0
  16. data/vendor/reveal.js/css/theme/README.md +1 -1
  17. data/vendor/reveal.js/css/theme/beige.css +177 -60
  18. data/vendor/reveal.js/css/theme/black.css +261 -0
  19. data/vendor/reveal.js/css/theme/blood.css +191 -75
  20. data/vendor/reveal.js/css/theme/league.css +267 -0
  21. data/vendor/reveal.js/css/theme/moon.css +168 -51
  22. data/vendor/reveal.js/css/theme/night.css +165 -42
  23. data/vendor/reveal.js/css/theme/serif.css +181 -58
  24. data/vendor/reveal.js/css/theme/simple.css +173 -50
  25. data/vendor/reveal.js/css/theme/sky.css +170 -47
  26. data/vendor/reveal.js/css/theme/solarized.css +168 -51
  27. data/vendor/reveal.js/css/theme/source/beige.scss +1 -12
  28. data/vendor/reveal.js/css/theme/source/black.scss +49 -0
  29. data/vendor/reveal.js/css/theme/source/blood.scss +4 -4
  30. data/vendor/reveal.js/css/theme/source/{default.scss → league.scss} +5 -13
  31. data/vendor/reveal.js/css/theme/source/moon.scss +1 -12
  32. data/vendor/reveal.js/css/theme/source/serif.scss +1 -1
  33. data/vendor/reveal.js/css/theme/source/sky.scss +1 -1
  34. data/vendor/reveal.js/css/theme/source/solarized.scss +1 -12
  35. data/vendor/reveal.js/css/theme/source/white.scss +49 -0
  36. data/vendor/reveal.js/css/theme/template/settings.scss +13 -4
  37. data/vendor/reveal.js/css/theme/template/theme.scss +182 -13
  38. data/vendor/reveal.js/css/theme/white.css +261 -0
  39. data/vendor/reveal.js/index.html +198 -178
  40. data/vendor/reveal.js/js/reveal.js +1286 -392
  41. data/vendor/reveal.js/lib/css/zenburn.css +74 -71
  42. data/vendor/reveal.js/lib/font/{league_gothic_license → league-gothic/LICENSE} +0 -0
  43. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.css +10 -0
  44. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.eot +0 -0
  45. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.ttf +0 -0
  46. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.woff +0 -0
  47. data/vendor/reveal.js/lib/font/source-sans-pro/LICENSE +45 -0
  48. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.eot +0 -0
  49. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.ttf +0 -0
  50. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.woff +0 -0
  51. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.eot +0 -0
  52. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.ttf +0 -0
  53. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.woff +0 -0
  54. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.eot +0 -0
  55. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.ttf +0 -0
  56. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.woff +0 -0
  57. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot +0 -0
  58. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf +0 -0
  59. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff +0 -0
  60. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro.css +39 -0
  61. data/vendor/reveal.js/package.json +9 -7
  62. data/vendor/reveal.js/plugin/highlight/highlight.js +2 -4
  63. data/vendor/reveal.js/plugin/leap/leap.js +4 -2
  64. data/vendor/reveal.js/plugin/markdown/example.html +2 -2
  65. data/vendor/reveal.js/plugin/markdown/markdown.js +8 -7
  66. data/vendor/reveal.js/plugin/notes/notes.html +321 -182
  67. data/vendor/reveal.js/plugin/notes/notes.js +89 -45
  68. data/vendor/reveal.js/plugin/notes-server/client.js +49 -46
  69. data/vendor/reveal.js/plugin/notes-server/index.js +28 -21
  70. data/vendor/reveal.js/plugin/notes-server/notes.html +351 -97
  71. data/vendor/reveal.js/plugin/print-pdf/print-pdf.js +24 -20
  72. data/vendor/reveal.js/plugin/zoom-js/zoom.js +77 -57
  73. data/vendor/reveal.js/test/examples/barebones.html +2 -2
  74. data/vendor/reveal.js/test/examples/embedded-media.html +2 -2
  75. data/vendor/reveal.js/test/examples/math.html +2 -2
  76. data/vendor/reveal.js/test/examples/slide-backgrounds.html +29 -7
  77. data/vendor/reveal.js/test/test-markdown-element-attributes.html +6 -6
  78. data/vendor/reveal.js/test/test-markdown-slide-attributes.html +7 -7
  79. data/vendor/reveal.js/test/test-markdown.html +4 -4
  80. data/vendor/reveal.js/test/test-pdf.html +83 -0
  81. data/vendor/reveal.js/test/test-pdf.js +15 -0
  82. data/vendor/reveal.js/test/test.html +5 -4
  83. data/vendor/reveal.js/test/test.js +143 -9
  84. metadata +31 -13
  85. data/vendor/reveal.js/css/reveal.min.css +0 -7
  86. data/vendor/reveal.js/css/theme/default.css +0 -148
  87. data/vendor/reveal.js/js/reveal.min.js +0 -9
  88. data/vendor/reveal.js/lib/font/league_gothic-webfont.eot +0 -0
  89. data/vendor/reveal.js/lib/font/league_gothic-webfont.svg +0 -230
  90. data/vendor/reveal.js/lib/font/league_gothic-webfont.ttf +0 -0
  91. data/vendor/reveal.js/lib/font/league_gothic-webfont.woff +0 -0
  92. data/vendor/reveal.js/plugin/postmessage/example.html +0 -39
  93. data/vendor/reveal.js/plugin/postmessage/postmessage.js +0 -42
@@ -3,16 +3,32 @@
3
3
  * http://lab.hakim.se/reveal-js
4
4
  * MIT licensed
5
5
  *
6
- * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
6
+ * Copyright (C) 2015 Hakim El Hattab, http://hakim.se
7
7
  */
8
- var Reveal = (function(){
8
+ (function( root, factory ) {
9
+ if( typeof define === 'function' && define.amd ) {
10
+ // AMD. Register as an anonymous module.
11
+ define( function() {
12
+ root.Reveal = factory();
13
+ return root.Reveal;
14
+ } );
15
+ } else if( typeof exports === 'object' ) {
16
+ // Node. Does not work with strict CommonJS.
17
+ module.exports = factory();
18
+ } else {
19
+ // Browser globals.
20
+ root.Reveal = factory();
21
+ }
22
+ }( this, function() {
9
23
 
10
24
  'use strict';
11
25
 
12
- var SLIDES_SELECTOR = '.reveal .slides section',
13
- HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
- VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
- HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
26
+ var Reveal;
27
+
28
+ var SLIDES_SELECTOR = '.slides section',
29
+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
30
+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
31
+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
16
32
 
17
33
  // Configurations defaults, can be overridden at initialization time
18
34
  config = {
@@ -27,7 +43,7 @@ var Reveal = (function(){
27
43
 
28
44
  // Bounds for smallest/largest possible scale to apply to content
29
45
  minScale: 0.2,
30
- maxScale: 1.0,
46
+ maxScale: 1.5,
31
47
 
32
48
  // Display controls in the bottom right corner
33
49
  controls: true,
@@ -44,6 +60,9 @@ var Reveal = (function(){
44
60
  // Enable keyboard shortcuts for navigation
45
61
  keyboard: true,
46
62
 
63
+ // Optional function that blocks keyboard events when retuning false
64
+ keyboardCondition: null,
65
+
47
66
  // Enable the slide overview mode
48
67
  overview: true,
49
68
 
@@ -66,6 +85,13 @@ var Reveal = (function(){
66
85
  // i.e. contained within a limited portion of the screen
67
86
  embedded: false,
68
87
 
88
+ // Flags if we should show a help overlay when the questionmark
89
+ // key is pressed
90
+ help: true,
91
+
92
+ // Flags if it should be possible to pause the presentation (blackout)
93
+ pause: true,
94
+
69
95
  // Number of milliseconds between automatically proceeding to the
70
96
  // next slide, disabled when set to 0, this value can be overwritten
71
97
  // by using a data-autoslide attribute on your slides
@@ -86,20 +112,23 @@ var Reveal = (function(){
86
112
  // Opens links in an iframe preview overlay
87
113
  previewLinks: false,
88
114
 
89
- // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
- focusBodyOnPageVisiblityChange: true,
115
+ // Exposes the reveal.js API through window.postMessage
116
+ postMessage: true,
91
117
 
92
- // Theme (see /css/theme)
93
- theme: null,
118
+ // Dispatches all reveal.js events to the parent window through postMessage
119
+ postMessageEvents: false,
120
+
121
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
122
+ focusBodyOnPageVisibilityChange: true,
94
123
 
95
124
  // Transition style
96
- transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
125
+ transition: 'slide', // none/fade/slide/convex/concave/zoom
97
126
 
98
127
  // Transition speed
99
128
  transitionSpeed: 'default', // default/fast/slow
100
129
 
101
130
  // Transition style for full page slide backgrounds
102
- backgroundTransition: 'default', // default/linear/none
131
+ backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
103
132
 
104
133
  // Parallax background image
105
134
  parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
@@ -151,12 +180,6 @@ var Reveal = (function(){
151
180
  // Delays updates to the URL due to a Chrome thumbnailer bug
152
181
  writeURLTimeout = 0,
153
182
 
154
- // A delay used to activate the overview mode
155
- activateOverviewTimeout = 0,
156
-
157
- // A delay used to deactivate the overview mode
158
- deactivateOverviewTimeout = 0,
159
-
160
183
  // Flags if the interaction event listeners are bound
161
184
  eventsAreBound = false,
162
185
 
@@ -177,6 +200,21 @@ var Reveal = (function(){
177
200
  startCount: 0,
178
201
  captured: false,
179
202
  threshold: 40
203
+ },
204
+
205
+ // Holds information about the keyboard shortcuts
206
+ keyboardShortcuts = {
207
+ 'N , SPACE': 'Next slide',
208
+ 'P': 'Previous slide',
209
+ '← , H': 'Navigate left',
210
+ '→ , L': 'Navigate right',
211
+ '↑ , K': 'Navigate up',
212
+ '↓ , J': 'Navigate down',
213
+ 'Home': 'First slide',
214
+ 'End': 'Last slide',
215
+ 'B , .': 'Pause',
216
+ 'F': 'Fullscreen',
217
+ 'ESC, O': 'Slide overview'
180
218
  };
181
219
 
182
220
  /**
@@ -189,11 +227,26 @@ var Reveal = (function(){
189
227
  if( !features.transforms2d && !features.transforms3d ) {
190
228
  document.body.setAttribute( 'class', 'no-transforms' );
191
229
 
230
+ // Since JS won't be running any further, we need to load all
231
+ // images that were intended to lazy load now
232
+ var images = document.getElementsByTagName( 'img' );
233
+ for( var i = 0, len = images.length; i < len; i++ ) {
234
+ var image = images[i];
235
+ if( image.getAttribute( 'data-src' ) ) {
236
+ image.setAttribute( 'src', image.getAttribute( 'data-src' ) );
237
+ image.removeAttribute( 'data-src' );
238
+ }
239
+ }
240
+
192
241
  // If the browser doesn't support core features we won't be
193
242
  // using JavaScript to control the presentation
194
243
  return;
195
244
  }
196
245
 
246
+ // Cache references to key DOM elements
247
+ dom.wrapper = document.querySelector( '.reveal' );
248
+ dom.slides = document.querySelector( '.reveal .slides' );
249
+
197
250
  // Force a layout when the whole page, incl fonts, has loaded
198
251
  window.addEventListener( 'load', layout, false );
199
252
 
@@ -238,10 +291,11 @@ var Reveal = (function(){
238
291
 
239
292
  features.canvas = !!document.createElement( 'canvas' ).getContext;
240
293
 
241
- isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
294
+ features.touch = !!( 'ontouchstart' in window );
242
295
 
243
- }
296
+ isMobileDevice = navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi );
244
297
 
298
+ }
245
299
 
246
300
  /**
247
301
  * Loads the dependencies of reveal.js. Dependencies are
@@ -316,6 +370,9 @@ var Reveal = (function(){
316
370
  // Make sure we've got all the DOM elements we need
317
371
  setupDOM();
318
372
 
373
+ // Listen to messages posted to this window
374
+ setupPostMessage();
375
+
319
376
  // Resets all vertical slides so that only the first is visible
320
377
  resetVerticalSlides();
321
378
 
@@ -343,6 +400,20 @@ var Reveal = (function(){
343
400
  } );
344
401
  }, 1 );
345
402
 
403
+ // Special setup and config is required when printing to PDF
404
+ if( isPrintingPDF() ) {
405
+ removeEventListeners();
406
+
407
+ // The document needs to have loaded for the PDF layout
408
+ // measurements to be accurate
409
+ if( document.readyState === 'complete' ) {
410
+ setupPDF();
411
+ }
412
+ else {
413
+ window.addEventListener( 'load', setupPDF );
414
+ }
415
+ }
416
+
346
417
  }
347
418
 
348
419
  /**
@@ -352,11 +423,6 @@ var Reveal = (function(){
352
423
  */
353
424
  function setupDOM() {
354
425
 
355
- // Cache references to key DOM elements
356
- dom.theme = document.querySelector( '#theme' );
357
- dom.wrapper = document.querySelector( '.reveal' );
358
- dom.slides = document.querySelector( '.reveal .slides' );
359
-
360
426
  // Prevent transitions while we're loading
361
427
  dom.slides.classList.add( 'no-transition' );
362
428
 
@@ -377,14 +443,14 @@ var Reveal = (function(){
377
443
  // Slide number
378
444
  dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
445
 
380
- // State background element [DEPRECATED]
381
- createSingletonNode( dom.wrapper, 'div', 'state-background', null );
382
-
383
446
  // Overlay graphic which is displayed during the paused mode
384
447
  createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
385
448
 
386
449
  // Cache references to elements
387
450
  dom.controls = document.querySelector( '.reveal .controls' );
451
+ dom.theme = document.querySelector( '#theme' );
452
+
453
+ dom.wrapper.setAttribute( 'role', 'application' );
388
454
 
389
455
  // There can be multiple instances of controls throughout the page
390
456
  dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
@@ -394,6 +460,100 @@ var Reveal = (function(){
394
460
  dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
461
  dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
396
462
 
463
+ dom.statusDiv = createStatusDiv();
464
+ }
465
+
466
+ /**
467
+ * Creates a hidden div with role aria-live to announce the
468
+ * current slide content. Hide the div off-screen to make it
469
+ * available only to Assistive Technologies.
470
+ */
471
+ function createStatusDiv() {
472
+
473
+ var statusDiv = document.getElementById( 'aria-status-div' );
474
+ if( !statusDiv ) {
475
+ statusDiv = document.createElement( 'div' );
476
+ statusDiv.style.position = 'absolute';
477
+ statusDiv.style.height = '1px';
478
+ statusDiv.style.width = '1px';
479
+ statusDiv.style.overflow ='hidden';
480
+ statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
481
+ statusDiv.setAttribute( 'id', 'aria-status-div' );
482
+ statusDiv.setAttribute( 'aria-live', 'polite' );
483
+ statusDiv.setAttribute( 'aria-atomic','true' );
484
+ dom.wrapper.appendChild( statusDiv );
485
+ }
486
+ return statusDiv;
487
+
488
+ }
489
+
490
+ /**
491
+ * Configures the presentation for printing to a static
492
+ * PDF.
493
+ */
494
+ function setupPDF() {
495
+
496
+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
497
+
498
+ // Dimensions of the PDF pages
499
+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
500
+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
501
+
502
+ // Dimensions of slides within the pages
503
+ var slideWidth = slideSize.width,
504
+ slideHeight = slideSize.height;
505
+
506
+ // Let the browser know what page size we want to print
507
+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' );
508
+
509
+ // Limit the size of certain elements to the dimensions of the slide
510
+ injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
511
+
512
+ document.body.classList.add( 'print-pdf' );
513
+ document.body.style.width = pageWidth + 'px';
514
+ document.body.style.height = pageHeight + 'px';
515
+
516
+ // Slide and slide background layout
517
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
518
+
519
+ // Vertical stacks are not centred since their section
520
+ // children will be
521
+ if( slide.classList.contains( 'stack' ) === false ) {
522
+ // Center the slide inside of the page, giving the slide some margin
523
+ var left = ( pageWidth - slideWidth ) / 2,
524
+ top = ( pageHeight - slideHeight ) / 2;
525
+
526
+ var contentHeight = getAbsoluteHeight( slide );
527
+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
528
+
529
+ // Center slides vertically
530
+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
531
+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
532
+ }
533
+
534
+ // Position the slide inside of the page
535
+ slide.style.left = left + 'px';
536
+ slide.style.top = top + 'px';
537
+ slide.style.width = slideWidth + 'px';
538
+
539
+ // TODO Backgrounds need to be multiplied when the slide
540
+ // stretches over multiple pages
541
+ var background = slide.querySelector( '.slide-background' );
542
+ if( background ) {
543
+ background.style.width = pageWidth + 'px';
544
+ background.style.height = ( pageHeight * numberOfPages ) + 'px';
545
+ background.style.top = -top + 'px';
546
+ background.style.left = -left + 'px';
547
+ }
548
+ }
549
+
550
+ } );
551
+
552
+ // Show all fragments
553
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
554
+ fragment.classList.add( 'visible' );
555
+ } );
556
+
397
557
  }
398
558
 
399
559
  /**
@@ -403,15 +563,26 @@ var Reveal = (function(){
403
563
  */
404
564
  function createSingletonNode( container, tagname, classname, innerHTML ) {
405
565
 
406
- var node = container.querySelector( '.' + classname );
407
- if( !node ) {
408
- node = document.createElement( tagname );
409
- node.classList.add( classname );
410
- if( innerHTML !== null ) {
411
- node.innerHTML = innerHTML;
566
+ // Find all nodes matching the description
567
+ var nodes = container.querySelectorAll( '.' + classname );
568
+
569
+ // Check all matches to find one which is a direct child of
570
+ // the specified container
571
+ for( var i = 0; i < nodes.length; i++ ) {
572
+ var testNode = nodes[i];
573
+ if( testNode.parentNode === container ) {
574
+ return testNode;
412
575
  }
413
- container.appendChild( node );
414
576
  }
577
+
578
+ // If no node was found, create it now
579
+ var node = document.createElement( tagname );
580
+ node.classList.add( classname );
581
+ if( typeof innerHTML === 'string' ) {
582
+ node.innerHTML = innerHTML;
583
+ }
584
+ container.appendChild( node );
585
+
415
586
  return node;
416
587
 
417
588
  }
@@ -423,81 +594,36 @@ var Reveal = (function(){
423
594
  */
424
595
  function createBackgrounds() {
425
596
 
426
- if( isPrintingPDF() ) {
427
- document.body.classList.add( 'print-pdf' );
428
- }
597
+ var printMode = isPrintingPDF();
429
598
 
430
599
  // Clear prior backgrounds
431
600
  dom.background.innerHTML = '';
432
601
  dom.background.classList.add( 'no-transition' );
433
602
 
434
- // Helper method for creating a background element for the
435
- // given slide
436
- function _createBackground( slide, container ) {
437
-
438
- var data = {
439
- background: slide.getAttribute( 'data-background' ),
440
- backgroundSize: slide.getAttribute( 'data-background-size' ),
441
- backgroundImage: slide.getAttribute( 'data-background-image' ),
442
- backgroundColor: slide.getAttribute( 'data-background-color' ),
443
- backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
444
- backgroundPosition: slide.getAttribute( 'data-background-position' ),
445
- backgroundTransition: slide.getAttribute( 'data-background-transition' )
446
- };
447
-
448
- var element = document.createElement( 'div' );
449
- element.className = 'slide-background';
450
-
451
- if( data.background ) {
452
- // Auto-wrap image urls in url(...)
453
- if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
454
- element.style.backgroundImage = 'url('+ data.background +')';
455
- }
456
- else {
457
- element.style.background = data.background;
458
- }
459
- }
460
-
461
- if( data.background || data.backgroundColor || data.backgroundImage ) {
462
- element.setAttribute( 'data-background-hash', data.background + data.backgroundSize + data.backgroundImage + data.backgroundColor + data.backgroundRepeat + data.backgroundPosition + data.backgroundTransition );
463
- }
464
-
465
- // Additional and optional background properties
466
- if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
467
- if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
468
- if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
469
- if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
470
- if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
471
- if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
472
-
473
- container.appendChild( element );
474
-
475
- return element;
476
-
477
- }
478
-
479
603
  // Iterate over all horizontal slides
480
- toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
604
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
481
605
 
482
606
  var backgroundStack;
483
607
 
484
- if( isPrintingPDF() ) {
485
- backgroundStack = _createBackground( slideh, slideh );
608
+ if( printMode ) {
609
+ backgroundStack = createBackground( slideh, slideh );
486
610
  }
487
611
  else {
488
- backgroundStack = _createBackground( slideh, dom.background );
612
+ backgroundStack = createBackground( slideh, dom.background );
489
613
  }
490
614
 
491
615
  // Iterate over all vertical slides
492
616
  toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
493
617
 
494
- if( isPrintingPDF() ) {
495
- _createBackground( slidev, slidev );
618
+ if( printMode ) {
619
+ createBackground( slidev, slidev );
496
620
  }
497
621
  else {
498
- _createBackground( slidev, backgroundStack );
622
+ createBackground( slidev, backgroundStack );
499
623
  }
500
624
 
625
+ backgroundStack.classList.add( 'stack' );
626
+
501
627
  } );
502
628
 
503
629
  } );
@@ -526,13 +652,131 @@ var Reveal = (function(){
526
652
 
527
653
  }
528
654
 
655
+ /**
656
+ * Creates a background for the given slide.
657
+ *
658
+ * @param {HTMLElement} slide
659
+ * @param {HTMLElement} container The element that the background
660
+ * should be appended to
661
+ */
662
+ function createBackground( slide, container ) {
663
+
664
+ var data = {
665
+ background: slide.getAttribute( 'data-background' ),
666
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
667
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
668
+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
669
+ backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
670
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
671
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
672
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
673
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
674
+ };
675
+
676
+ var element = document.createElement( 'div' );
677
+
678
+ // Carry over custom classes from the slide to the background
679
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
680
+
681
+ if( data.background ) {
682
+ // Auto-wrap image urls in url(...)
683
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
684
+ slide.setAttribute( 'data-background-image', data.background );
685
+ }
686
+ else {
687
+ element.style.background = data.background;
688
+ }
689
+ }
690
+
691
+ // Create a hash for this combination of background settings.
692
+ // This is used to determine when two slide backgrounds are
693
+ // the same.
694
+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
695
+ element.setAttribute( 'data-background-hash', data.background +
696
+ data.backgroundSize +
697
+ data.backgroundImage +
698
+ data.backgroundVideo +
699
+ data.backgroundIframe +
700
+ data.backgroundColor +
701
+ data.backgroundRepeat +
702
+ data.backgroundPosition +
703
+ data.backgroundTransition );
704
+ }
705
+
706
+ // Additional and optional background properties
707
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
708
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
709
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
710
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
711
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
712
+
713
+ container.appendChild( element );
714
+
715
+ // If backgrounds are being recreated, clear old classes
716
+ slide.classList.remove( 'has-dark-background' );
717
+ slide.classList.remove( 'has-light-background' );
718
+
719
+ // If this slide has a background color, add a class that
720
+ // signals if it is light or dark. If the slide has no background
721
+ // color, no class will be set
722
+ var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
723
+ if( computedBackgroundColor ) {
724
+ var rgb = colorToRgb( computedBackgroundColor );
725
+
726
+ // Ignore fully transparent backgrounds. Some browsers return
727
+ // rgba(0,0,0,0) when reading the computed background color of
728
+ // an element with no background
729
+ if( rgb && rgb.a !== 0 ) {
730
+ if( colorBrightness( computedBackgroundColor ) < 128 ) {
731
+ slide.classList.add( 'has-dark-background' );
732
+ }
733
+ else {
734
+ slide.classList.add( 'has-light-background' );
735
+ }
736
+ }
737
+ }
738
+
739
+ return element;
740
+
741
+ }
742
+
743
+ /**
744
+ * Registers a listener to postMessage events, this makes it
745
+ * possible to call all reveal.js API methods from another
746
+ * window. For example:
747
+ *
748
+ * revealWindow.postMessage( JSON.stringify({
749
+ * method: 'slide',
750
+ * args: [ 2 ]
751
+ * }), '*' );
752
+ */
753
+ function setupPostMessage() {
754
+
755
+ if( config.postMessage ) {
756
+ window.addEventListener( 'message', function ( event ) {
757
+ var data = event.data;
758
+
759
+ // Make sure we're dealing with JSON
760
+ if( data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
761
+ data = JSON.parse( data );
762
+
763
+ // Check if the requested method can be found
764
+ if( data.method && typeof Reveal[data.method] === 'function' ) {
765
+ Reveal[data.method].apply( Reveal, data.args );
766
+ }
767
+ }
768
+ }, false );
769
+ }
770
+
771
+ }
772
+
529
773
  /**
530
774
  * Applies the configuration settings from the config
531
775
  * object. May be called multiple times.
532
776
  */
533
777
  function configure( options ) {
534
778
 
535
- var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
779
+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
536
780
 
537
781
  dom.wrapper.classList.remove( config.transition );
538
782
 
@@ -565,6 +809,11 @@ var Reveal = (function(){
565
809
  dom.wrapper.classList.remove( 'center' );
566
810
  }
567
811
 
812
+ // Exit the paused mode if it was configured off
813
+ if( config.pause === false ) {
814
+ resume();
815
+ }
816
+
568
817
  if( config.mouseWheel ) {
569
818
  document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
570
819
  document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
@@ -591,7 +840,13 @@ var Reveal = (function(){
591
840
  enablePreviewLinks( '[data-preview-link]' );
592
841
  }
593
842
 
594
- // Auto-slide playback controls
843
+ // Remove existing auto-slide controls
844
+ if( autoSlidePlayer ) {
845
+ autoSlidePlayer.destroy();
846
+ autoSlidePlayer = null;
847
+ }
848
+
849
+ // Generate auto-slide controls if needed
595
850
  if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
851
  autoSlidePlayer = new Playback( dom.wrapper, function() {
597
852
  return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
@@ -600,21 +855,13 @@ var Reveal = (function(){
600
855
  autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
856
  autoSlidePaused = false;
602
857
  }
603
- else if( autoSlidePlayer ) {
604
- autoSlidePlayer.destroy();
605
- autoSlidePlayer = null;
606
- }
607
858
 
608
- // Load the theme in the config, if it's not already loaded
609
- if( config.theme && dom.theme ) {
610
- var themeURL = dom.theme.getAttribute( 'href' );
611
- var themeFinder = /[^\/]*?(?=\.css)/;
612
- var themeName = themeURL.match(themeFinder)[0];
613
-
614
- if( config.theme !== themeName ) {
615
- themeURL = themeURL.replace(themeFinder, config.theme);
616
- dom.theme.setAttribute( 'href', themeURL );
617
- }
859
+ // When fragments are turned off they should be visible
860
+ if( config.fragments === false ) {
861
+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
862
+ element.classList.add( 'visible' );
863
+ element.classList.remove( 'current-fragment' );
864
+ } );
618
865
  }
619
866
 
620
867
  sync();
@@ -637,7 +884,14 @@ var Reveal = (function(){
637
884
  dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
638
885
 
639
886
  // Support pointer-style touch interaction as well
640
- if( window.navigator.msPointerEnabled ) {
887
+ if( window.navigator.pointerEnabled ) {
888
+ // IE 11 uses un-prefixed version of pointer events
889
+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
890
+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
891
+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
892
+ }
893
+ else if( window.navigator.msPointerEnabled ) {
894
+ // IE 10 uses prefixed version of pointer events
641
895
  dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
642
896
  dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
643
897
  dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
@@ -646,13 +900,14 @@ var Reveal = (function(){
646
900
 
647
901
  if( config.keyboard ) {
648
902
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
903
+ document.addEventListener( 'keypress', onDocumentKeyPress, false );
649
904
  }
650
905
 
651
906
  if( config.progress && dom.progress ) {
652
907
  dom.progress.addEventListener( 'click', onProgressClicked, false );
653
908
  }
654
909
 
655
- if( config.focusBodyOnPageVisiblityChange ) {
910
+ if( config.focusBodyOnPageVisibilityChange ) {
656
911
  var visibilityChange;
657
912
 
658
913
  if( 'hidden' in document ) {
@@ -670,7 +925,17 @@ var Reveal = (function(){
670
925
  }
671
926
  }
672
927
 
673
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
928
+ // Listen to both touch and click events, in case the device
929
+ // supports both
930
+ var pointerEvents = [ 'touchstart', 'click' ];
931
+
932
+ // Only support touch for Android, fixes double navigations in
933
+ // stock browser
934
+ if( navigator.userAgent.match( /android/gi ) ) {
935
+ pointerEvents = [ 'touchstart' ];
936
+ }
937
+
938
+ pointerEvents.forEach( function( eventName ) {
674
939
  dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
940
  dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
941
  dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
@@ -689,6 +954,7 @@ var Reveal = (function(){
689
954
  eventsAreBound = false;
690
955
 
691
956
  document.removeEventListener( 'keydown', onDocumentKeyDown, false );
957
+ document.removeEventListener( 'keypress', onDocumentKeyPress, false );
692
958
  window.removeEventListener( 'hashchange', onWindowHashChange, false );
693
959
  window.removeEventListener( 'resize', onWindowResize, false );
694
960
 
@@ -696,7 +962,14 @@ var Reveal = (function(){
696
962
  dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
697
963
  dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
698
964
 
699
- if( window.navigator.msPointerEnabled ) {
965
+ // IE11
966
+ if( window.navigator.pointerEnabled ) {
967
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
968
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
969
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
970
+ }
971
+ // IE10
972
+ else if( window.navigator.msPointerEnabled ) {
700
973
  dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
701
974
  dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
702
975
  dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
@@ -738,6 +1011,22 @@ var Reveal = (function(){
738
1011
 
739
1012
  }
740
1013
 
1014
+ /**
1015
+ * Utility for deserializing a value.
1016
+ */
1017
+ function deserialize( value ) {
1018
+
1019
+ if( typeof value === 'string' ) {
1020
+ if( value === 'null' ) return null;
1021
+ else if( value === 'true' ) return true;
1022
+ else if( value === 'false' ) return false;
1023
+ else if( value.match( /^\d+$/ ) ) return parseFloat( value );
1024
+ }
1025
+
1026
+ return value;
1027
+
1028
+ }
1029
+
741
1030
  /**
742
1031
  * Measures the distance in pixels between point a
743
1032
  * and point b.
@@ -767,6 +1056,94 @@ var Reveal = (function(){
767
1056
 
768
1057
  }
769
1058
 
1059
+ /**
1060
+ * Injects the given CSS styles into the DOM.
1061
+ */
1062
+ function injectStyleSheet( value ) {
1063
+
1064
+ var tag = document.createElement( 'style' );
1065
+ tag.type = 'text/css';
1066
+ if( tag.styleSheet ) {
1067
+ tag.styleSheet.cssText = value;
1068
+ }
1069
+ else {
1070
+ tag.appendChild( document.createTextNode( value ) );
1071
+ }
1072
+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
1073
+
1074
+ }
1075
+
1076
+ /**
1077
+ * Measures the distance in pixels between point a and point b.
1078
+ *
1079
+ * @param {String} color The string representation of a color,
1080
+ * the following formats are supported:
1081
+ * - #000
1082
+ * - #000000
1083
+ * - rgb(0,0,0)
1084
+ */
1085
+ function colorToRgb( color ) {
1086
+
1087
+ var hex3 = color.match( /^#([0-9a-f]{3})$/i );
1088
+ if( hex3 && hex3[1] ) {
1089
+ hex3 = hex3[1];
1090
+ return {
1091
+ r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
1092
+ g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
1093
+ b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
1094
+ };
1095
+ }
1096
+
1097
+ var hex6 = color.match( /^#([0-9a-f]{6})$/i );
1098
+ if( hex6 && hex6[1] ) {
1099
+ hex6 = hex6[1];
1100
+ return {
1101
+ r: parseInt( hex6.substr( 0, 2 ), 16 ),
1102
+ g: parseInt( hex6.substr( 2, 2 ), 16 ),
1103
+ b: parseInt( hex6.substr( 4, 2 ), 16 )
1104
+ };
1105
+ }
1106
+
1107
+ var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
1108
+ if( rgb ) {
1109
+ return {
1110
+ r: parseInt( rgb[1], 10 ),
1111
+ g: parseInt( rgb[2], 10 ),
1112
+ b: parseInt( rgb[3], 10 )
1113
+ };
1114
+ }
1115
+
1116
+ var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
1117
+ if( rgba ) {
1118
+ return {
1119
+ r: parseInt( rgba[1], 10 ),
1120
+ g: parseInt( rgba[2], 10 ),
1121
+ b: parseInt( rgba[3], 10 ),
1122
+ a: parseFloat( rgba[4] )
1123
+ };
1124
+ }
1125
+
1126
+ return null;
1127
+
1128
+ }
1129
+
1130
+ /**
1131
+ * Calculates brightness on a scale of 0-255.
1132
+ *
1133
+ * @param color See colorStringToRgb for supported formats.
1134
+ */
1135
+ function colorBrightness( color ) {
1136
+
1137
+ if( typeof color === 'string' ) color = colorToRgb( color );
1138
+
1139
+ if( color ) {
1140
+ return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
1141
+ }
1142
+
1143
+ return null;
1144
+
1145
+ }
1146
+
770
1147
  /**
771
1148
  * Retrieves the height of the given element by looking
772
1149
  * at the position and height of its immediate children.
@@ -782,7 +1159,7 @@ var Reveal = (function(){
782
1159
 
783
1160
  if( typeof child.offsetTop === 'number' && child.style ) {
784
1161
  // Count # of abs children
785
- if( child.style.position === 'absolute' ) {
1162
+ if( window.getComputedStyle( child ).position === 'absolute' ) {
786
1163
  absoluteChildren += 1;
787
1164
  }
788
1165
 
@@ -804,40 +1181,26 @@ var Reveal = (function(){
804
1181
 
805
1182
  /**
806
1183
  * Returns the remaining height within the parent of the
807
- * target element after subtracting the height of all
808
- * siblings.
1184
+ * target element.
809
1185
  *
810
- * remaining height = [parent height] - [ siblings height]
1186
+ * remaining height = [ configured parent height ] - [ current parent height ]
811
1187
  */
812
1188
  function getRemainingHeight( element, height ) {
813
1189
 
814
1190
  height = height || 0;
815
1191
 
816
1192
  if( element ) {
817
- var parent = element.parentNode;
818
- var siblings = parent.childNodes;
819
-
820
- // Subtract the height of each sibling
821
- toArray( siblings ).forEach( function( sibling ) {
822
-
823
- if( typeof sibling.offsetHeight === 'number' && sibling !== element ) {
824
-
825
- var styles = window.getComputedStyle( sibling ),
826
- marginTop = parseInt( styles.marginTop, 10 ),
827
- marginBottom = parseInt( styles.marginBottom, 10 );
828
-
829
- height -= sibling.offsetHeight + marginTop + marginBottom;
830
-
831
- }
832
-
833
- } );
1193
+ var newHeight, oldHeight = element.style.height;
834
1194
 
835
- var elementStyles = window.getComputedStyle( element );
1195
+ // Change the .stretch element height to 0 in order find the height of all
1196
+ // the other elements
1197
+ element.style.height = '0px';
1198
+ newHeight = height - element.parentNode.offsetHeight;
836
1199
 
837
- // Subtract the margins of the target element
838
- height -= parseInt( elementStyles.marginTop, 10 ) +
839
- parseInt( elementStyles.marginBottom, 10 );
1200
+ // Restore the old height, just in case
1201
+ element.style.height = oldHeight + 'px';
840
1202
 
1203
+ return newHeight;
841
1204
  }
842
1205
 
843
1206
  return height;
@@ -882,13 +1245,19 @@ var Reveal = (function(){
882
1245
  * Dispatches an event of the specified type from the
883
1246
  * reveal DOM element.
884
1247
  */
885
- function dispatchEvent( type, properties ) {
1248
+ function dispatchEvent( type, args ) {
886
1249
 
887
- var event = document.createEvent( "HTMLEvents", 1, 2 );
1250
+ var event = document.createEvent( 'HTMLEvents', 1, 2 );
888
1251
  event.initEvent( type, true, true );
889
- extend( event, properties );
1252
+ extend( event, args );
890
1253
  dom.wrapper.dispatchEvent( event );
891
1254
 
1255
+ // If we're in an iframe, post each reveal.js event to the
1256
+ // parent window. Used by the notes plugin
1257
+ if( config.postMessageEvents && window.parent !== window.self ) {
1258
+ window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
1259
+ }
1260
+
892
1261
  }
893
1262
 
894
1263
  /**
@@ -897,7 +1266,7 @@ var Reveal = (function(){
897
1266
  function enableRollingLinks() {
898
1267
 
899
1268
  if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
900
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
1269
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
901
1270
 
902
1271
  for( var i = 0, len = anchors.length; i < len; i++ ) {
903
1272
  var anchor = anchors[i];
@@ -921,7 +1290,7 @@ var Reveal = (function(){
921
1290
  */
922
1291
  function disableRollingLinks() {
923
1292
 
924
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
1293
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
925
1294
 
926
1295
  for( var i = 0, len = anchors.length; i < len; i++ ) {
927
1296
  var anchor = anchors[i];
@@ -968,15 +1337,16 @@ var Reveal = (function(){
968
1337
  /**
969
1338
  * Opens a preview window for the target URL.
970
1339
  */
971
- function openPreview( url ) {
1340
+ function showPreview( url ) {
972
1341
 
973
- closePreview();
1342
+ closeOverlay();
974
1343
 
975
- dom.preview = document.createElement( 'div' );
976
- dom.preview.classList.add( 'preview-link-overlay' );
977
- dom.wrapper.appendChild( dom.preview );
1344
+ dom.overlay = document.createElement( 'div' );
1345
+ dom.overlay.classList.add( 'overlay' );
1346
+ dom.overlay.classList.add( 'overlay-preview' );
1347
+ dom.wrapper.appendChild( dom.overlay );
978
1348
 
979
- dom.preview.innerHTML = [
1349
+ dom.overlay.innerHTML = [
980
1350
  '<header>',
981
1351
  '<a class="close" href="#"><span class="icon"></span></a>',
982
1352
  '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
@@ -987,34 +1357,78 @@ var Reveal = (function(){
987
1357
  '</div>'
988
1358
  ].join('');
989
1359
 
990
- dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
991
- dom.preview.classList.add( 'loaded' );
1360
+ dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
1361
+ dom.overlay.classList.add( 'loaded' );
992
1362
  }, false );
993
1363
 
994
- dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
995
- closePreview();
1364
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1365
+ closeOverlay();
996
1366
  event.preventDefault();
997
1367
  }, false );
998
1368
 
999
- dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1000
- closePreview();
1369
+ dom.overlay.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1370
+ closeOverlay();
1001
1371
  }, false );
1002
1372
 
1003
1373
  setTimeout( function() {
1004
- dom.preview.classList.add( 'visible' );
1374
+ dom.overlay.classList.add( 'visible' );
1005
1375
  }, 1 );
1006
1376
 
1007
1377
  }
1008
1378
 
1009
1379
  /**
1010
- * Closes the iframe preview window.
1380
+ * Opens a overlay window with help material.
1011
1381
  */
1012
- function closePreview() {
1382
+ function showHelp() {
1383
+
1384
+ if( config.help ) {
1385
+
1386
+ closeOverlay();
1387
+
1388
+ dom.overlay = document.createElement( 'div' );
1389
+ dom.overlay.classList.add( 'overlay' );
1390
+ dom.overlay.classList.add( 'overlay-help' );
1391
+ dom.wrapper.appendChild( dom.overlay );
1392
+
1393
+ var html = '<p class="title">Keyboard Shortcuts</p><br/>';
1394
+
1395
+ html += '<table><th>KEY</th><th>ACTION</th>';
1396
+ for( var key in keyboardShortcuts ) {
1397
+ html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
1398
+ }
1399
+
1400
+ html += '</table>';
1401
+
1402
+ dom.overlay.innerHTML = [
1403
+ '<header>',
1404
+ '<a class="close" href="#"><span class="icon"></span></a>',
1405
+ '</header>',
1406
+ '<div class="viewport">',
1407
+ '<div class="viewport-inner">'+ html +'</div>',
1408
+ '</div>'
1409
+ ].join('');
1410
+
1411
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1412
+ closeOverlay();
1413
+ event.preventDefault();
1414
+ }, false );
1415
+
1416
+ setTimeout( function() {
1417
+ dom.overlay.classList.add( 'visible' );
1418
+ }, 1 );
1013
1419
 
1014
- if( dom.preview ) {
1015
- dom.preview.setAttribute( 'src', '' );
1016
- dom.preview.parentNode.removeChild( dom.preview );
1017
- dom.preview = null;
1420
+ }
1421
+
1422
+ }
1423
+
1424
+ /**
1425
+ * Closes any currently open overlay.
1426
+ */
1427
+ function closeOverlay() {
1428
+
1429
+ if( dom.overlay ) {
1430
+ dom.overlay.parentNode.removeChild( dom.overlay );
1431
+ dom.overlay = null;
1018
1432
  }
1019
1433
 
1020
1434
  }
@@ -1027,54 +1441,49 @@ var Reveal = (function(){
1027
1441
 
1028
1442
  if( dom.wrapper && !isPrintingPDF() ) {
1029
1443
 
1030
- // Available space to scale within
1031
- var availableWidth = dom.wrapper.offsetWidth,
1032
- availableHeight = dom.wrapper.offsetHeight;
1033
-
1034
- // Reduce available space by margin
1035
- availableWidth -= ( availableHeight * config.margin );
1036
- availableHeight -= ( availableHeight * config.margin );
1444
+ var size = getComputedSlideSize();
1037
1445
 
1038
- // Dimensions of the content
1039
- var slideWidth = config.width,
1040
- slideHeight = config.height,
1041
- slidePadding = 20; // TODO Dig this out of DOM
1446
+ var slidePadding = 20; // TODO Dig this out of DOM
1042
1447
 
1043
1448
  // Layout the contents of the slides
1044
1449
  layoutSlideContents( config.width, config.height, slidePadding );
1045
1450
 
1046
- // Slide width may be a percentage of available width
1047
- if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
1048
- slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
1049
- }
1050
-
1051
- // Slide height may be a percentage of available height
1052
- if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
1053
- slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
1054
- }
1055
-
1056
- dom.slides.style.width = slideWidth + 'px';
1057
- dom.slides.style.height = slideHeight + 'px';
1451
+ dom.slides.style.width = size.width + 'px';
1452
+ dom.slides.style.height = size.height + 'px';
1058
1453
 
1059
1454
  // Determine scale of content to fit within available space
1060
- scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
1455
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
1061
1456
 
1062
1457
  // Respect max/min scale settings
1063
1458
  scale = Math.max( scale, config.minScale );
1064
1459
  scale = Math.min( scale, config.maxScale );
1065
1460
 
1066
- // Prefer applying scale via zoom since Chrome blurs scaled content
1067
- // with nested transforms
1068
- if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
1069
- dom.slides.style.zoom = scale;
1461
+ // Don't apply any scaling styles if scale is 1
1462
+ if( scale === 1 ) {
1463
+ dom.slides.style.zoom = '';
1464
+ dom.slides.style.left = '';
1465
+ dom.slides.style.top = '';
1466
+ dom.slides.style.bottom = '';
1467
+ dom.slides.style.right = '';
1468
+ transformElement( dom.slides, '' );
1070
1469
  }
1071
- // Apply scale transform as a fallback
1072
1470
  else {
1073
- transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
1471
+ // Prefer zooming in desktop Chrome so that content remains crisp
1472
+ if( !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
1473
+ dom.slides.style.zoom = scale;
1474
+ }
1475
+ // Apply scale transform as a fallback
1476
+ else {
1477
+ dom.slides.style.left = '50%';
1478
+ dom.slides.style.top = '50%';
1479
+ dom.slides.style.bottom = 'auto';
1480
+ dom.slides.style.right = 'auto';
1481
+ transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +')' );
1482
+ }
1074
1483
  }
1075
1484
 
1076
1485
  // Select all slides, vertical and horizontal
1077
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1486
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
1078
1487
 
1079
1488
  for( var i = 0, len = slides.length; i < len; i++ ) {
1080
1489
  var slide = slides[ i ];
@@ -1091,7 +1500,7 @@ var Reveal = (function(){
1091
1500
  slide.style.top = 0;
1092
1501
  }
1093
1502
  else {
1094
- slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
1503
+ slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
1095
1504
  }
1096
1505
  }
1097
1506
  else {
@@ -1117,7 +1526,7 @@ var Reveal = (function(){
1117
1526
  toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1118
1527
 
1119
1528
  // Determine how much vertical space we can use
1120
- var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1529
+ var remainingHeight = getRemainingHeight( element, height );
1121
1530
 
1122
1531
  // Consider the aspect ratio of media elements
1123
1532
  if( /(img|video)/gi.test( element.nodeName ) ) {
@@ -1139,6 +1548,41 @@ var Reveal = (function(){
1139
1548
 
1140
1549
  }
1141
1550
 
1551
+ /**
1552
+ * Calculates the computed pixel size of our slides. These
1553
+ * values are based on the width and height configuration
1554
+ * options.
1555
+ */
1556
+ function getComputedSlideSize( presentationWidth, presentationHeight ) {
1557
+
1558
+ var size = {
1559
+ // Slide size
1560
+ width: config.width,
1561
+ height: config.height,
1562
+
1563
+ // Presentation size
1564
+ presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
1565
+ presentationHeight: presentationHeight || dom.wrapper.offsetHeight
1566
+ };
1567
+
1568
+ // Reduce available space by margin
1569
+ size.presentationWidth -= ( size.presentationHeight * config.margin );
1570
+ size.presentationHeight -= ( size.presentationHeight * config.margin );
1571
+
1572
+ // Slide width may be a percentage of available width
1573
+ if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
1574
+ size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
1575
+ }
1576
+
1577
+ // Slide height may be a percentage of available height
1578
+ if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
1579
+ size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
1580
+ }
1581
+
1582
+ return size;
1583
+
1584
+ }
1585
+
1142
1586
  /**
1143
1587
  * Stores the vertical index of a stack so that the same
1144
1588
  * vertical slide can be selected when navigating to and
@@ -1198,67 +1642,57 @@ var Reveal = (function(){
1198
1642
  dom.wrapper.classList.add( 'overview' );
1199
1643
  dom.wrapper.classList.remove( 'overview-deactivating' );
1200
1644
 
1201
- clearTimeout( activateOverviewTimeout );
1202
- clearTimeout( deactivateOverviewTimeout );
1203
-
1204
- // Not the pretties solution, but need to let the overview
1205
- // class apply first so that slides are measured accurately
1206
- // before we can position them
1207
- activateOverviewTimeout = setTimeout( function() {
1208
-
1209
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1645
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1210
1646
 
1211
- for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1212
- var hslide = horizontalSlides[i],
1213
- hoffset = config.rtl ? -105 : 105;
1647
+ for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1648
+ var hslide = horizontalSlides[i],
1649
+ hoffset = config.rtl ? -105 : 105;
1214
1650
 
1215
- hslide.setAttribute( 'data-index-h', i );
1651
+ hslide.setAttribute( 'data-index-h', i );
1216
1652
 
1217
- // Apply CSS transform
1218
- transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1653
+ // Apply CSS transform
1654
+ transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1219
1655
 
1220
- if( hslide.classList.contains( 'stack' ) ) {
1656
+ if( hslide.classList.contains( 'stack' ) ) {
1221
1657
 
1222
- var verticalSlides = hslide.querySelectorAll( 'section' );
1658
+ var verticalSlides = hslide.querySelectorAll( 'section' );
1223
1659
 
1224
- for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1225
- var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1660
+ for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1661
+ var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1226
1662
 
1227
- var vslide = verticalSlides[j];
1663
+ var vslide = verticalSlides[j];
1228
1664
 
1229
- vslide.setAttribute( 'data-index-h', i );
1230
- vslide.setAttribute( 'data-index-v', j );
1665
+ vslide.setAttribute( 'data-index-h', i );
1666
+ vslide.setAttribute( 'data-index-v', j );
1231
1667
 
1232
- // Apply CSS transform
1233
- transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1234
-
1235
- // Navigate to this slide on click
1236
- vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1237
- }
1238
-
1239
- }
1240
- else {
1668
+ // Apply CSS transform
1669
+ transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1241
1670
 
1242
1671
  // Navigate to this slide on click
1243
- hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1244
-
1672
+ vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1245
1673
  }
1246
- }
1247
1674
 
1248
- updateSlidesVisibility();
1675
+ }
1676
+ else {
1249
1677
 
1250
- layout();
1678
+ // Navigate to this slide on click
1679
+ hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1251
1680
 
1252
- if( !wasActive ) {
1253
- // Notify observers of the overview showing
1254
- dispatchEvent( 'overviewshown', {
1255
- 'indexh': indexh,
1256
- 'indexv': indexv,
1257
- 'currentSlide': currentSlide
1258
- } );
1259
1681
  }
1682
+ }
1683
+
1684
+ updateSlidesVisibility();
1260
1685
 
1261
- }, 10 );
1686
+ layout();
1687
+
1688
+ if( !wasActive ) {
1689
+ // Notify observers of the overview showing
1690
+ dispatchEvent( 'overviewshown', {
1691
+ 'indexh': indexh,
1692
+ 'indexv': indexv,
1693
+ 'currentSlide': currentSlide
1694
+ } );
1695
+ }
1262
1696
 
1263
1697
  }
1264
1698
 
@@ -1273,9 +1707,6 @@ var Reveal = (function(){
1273
1707
  // Only proceed if enabled in config
1274
1708
  if( config.overview ) {
1275
1709
 
1276
- clearTimeout( activateOverviewTimeout );
1277
- clearTimeout( deactivateOverviewTimeout );
1278
-
1279
1710
  dom.wrapper.classList.remove( 'overview' );
1280
1711
 
1281
1712
  // Temporarily add a class so that transitions can do different things
@@ -1283,12 +1714,12 @@ var Reveal = (function(){
1283
1714
  // moving from slide to slide
1284
1715
  dom.wrapper.classList.add( 'overview-deactivating' );
1285
1716
 
1286
- deactivateOverviewTimeout = setTimeout( function () {
1717
+ setTimeout( function () {
1287
1718
  dom.wrapper.classList.remove( 'overview-deactivating' );
1288
1719
  }, 1 );
1289
1720
 
1290
1721
  // Select all slides
1291
- toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1722
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1292
1723
  // Resets all transforms to use the external styles
1293
1724
  transformElement( slide, '' );
1294
1725
 
@@ -1370,7 +1801,7 @@ var Reveal = (function(){
1370
1801
  element.webkitRequestFullscreen ||
1371
1802
  element.webkitRequestFullScreen ||
1372
1803
  element.mozRequestFullScreen ||
1373
- element.msRequestFullScreen;
1804
+ element.msRequestFullscreen;
1374
1805
 
1375
1806
  if( requestMethod ) {
1376
1807
  requestMethod.apply( element );
@@ -1384,13 +1815,15 @@ var Reveal = (function(){
1384
1815
  */
1385
1816
  function pause() {
1386
1817
 
1387
- var wasPaused = dom.wrapper.classList.contains( 'paused' );
1818
+ if( config.pause ) {
1819
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1388
1820
 
1389
- cancelAutoSlide();
1390
- dom.wrapper.classList.add( 'paused' );
1821
+ cancelAutoSlide();
1822
+ dom.wrapper.classList.add( 'paused' );
1391
1823
 
1392
- if( wasPaused === false ) {
1393
- dispatchEvent( 'paused' );
1824
+ if( wasPaused === false ) {
1825
+ dispatchEvent( 'paused' );
1826
+ }
1394
1827
  }
1395
1828
 
1396
1829
  }
@@ -1414,13 +1847,13 @@ var Reveal = (function(){
1414
1847
  /**
1415
1848
  * Toggles the paused mode on and off.
1416
1849
  */
1417
- function togglePause() {
1850
+ function togglePause( override ) {
1418
1851
 
1419
- if( isPaused() ) {
1420
- resume();
1852
+ if( typeof override === 'boolean' ) {
1853
+ override ? pause() : resume();
1421
1854
  }
1422
1855
  else {
1423
- pause();
1856
+ isPaused() ? resume() : pause();
1424
1857
  }
1425
1858
 
1426
1859
  }
@@ -1434,6 +1867,34 @@ var Reveal = (function(){
1434
1867
 
1435
1868
  }
1436
1869
 
1870
+ /**
1871
+ * Toggles the auto slide mode on and off.
1872
+ *
1873
+ * @param {Boolean} override Optional flag which sets the desired state.
1874
+ * True means autoplay starts, false means it stops.
1875
+ */
1876
+
1877
+ function toggleAutoSlide( override ) {
1878
+
1879
+ if( typeof override === 'boolean' ) {
1880
+ override ? resumeAutoSlide() : pauseAutoSlide();
1881
+ }
1882
+
1883
+ else {
1884
+ autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
1885
+ }
1886
+
1887
+ }
1888
+
1889
+ /**
1890
+ * Checks if the auto slide mode is currently on.
1891
+ */
1892
+ function isAutoSliding() {
1893
+
1894
+ return !!( autoSlide && !autoSlidePaused );
1895
+
1896
+ }
1897
+
1437
1898
  /**
1438
1899
  * Steps from the current point in the presentation to the
1439
1900
  * slide which matches the specified horizontal and vertical
@@ -1451,7 +1912,7 @@ var Reveal = (function(){
1451
1912
  previousSlide = currentSlide;
1452
1913
 
1453
1914
  // Query all horizontal slides in the deck
1454
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1915
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1455
1916
 
1456
1917
  // If no vertical index is specified and the upcoming slide is a
1457
1918
  // stack, resume at its previous vertical index
@@ -1544,13 +2005,14 @@ var Reveal = (function(){
1544
2005
  // stacks
1545
2006
  if( previousSlide ) {
1546
2007
  previousSlide.classList.remove( 'present' );
2008
+ previousSlide.setAttribute( 'aria-hidden', 'true' );
1547
2009
 
1548
2010
  // Reset all slides upon navigate to home
1549
2011
  // Issue: #285
1550
- if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
2012
+ if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
1551
2013
  // Launch async task
1552
2014
  setTimeout( function () {
1553
- var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
2015
+ var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
1554
2016
  for( i in slides ) {
1555
2017
  if( slides[i] ) {
1556
2018
  // Reset stack
@@ -1562,11 +2024,14 @@ var Reveal = (function(){
1562
2024
  }
1563
2025
 
1564
2026
  // Handle embedded content
1565
- if( slideChanged ) {
2027
+ if( slideChanged || !previousSlide ) {
1566
2028
  stopEmbeddedContent( previousSlide );
1567
2029
  startEmbeddedContent( currentSlide );
1568
2030
  }
1569
2031
 
2032
+ // Announce the current slide contents, for screen readers
2033
+ dom.statusDiv.textContent = currentSlide.textContent;
2034
+
1570
2035
  updateControls();
1571
2036
  updateProgress();
1572
2037
  updateBackground();
@@ -1603,12 +2068,18 @@ var Reveal = (function(){
1603
2068
  // Re-create the slide backgrounds
1604
2069
  createBackgrounds();
1605
2070
 
2071
+ // Write the current hash to the URL
2072
+ writeURL();
2073
+
1606
2074
  sortAllFragments();
1607
2075
 
1608
2076
  updateControls();
1609
2077
  updateProgress();
1610
2078
  updateBackground( true );
1611
2079
  updateSlideNumber();
2080
+ updateSlidesVisibility();
2081
+
2082
+ formatEmbeddedContent();
1612
2083
 
1613
2084
  }
1614
2085
 
@@ -1618,7 +2089,7 @@ var Reveal = (function(){
1618
2089
  */
1619
2090
  function resetVerticalSlides() {
1620
2091
 
1621
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2092
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
2093
  horizontalSlides.forEach( function( horizontalSlide ) {
1623
2094
 
1624
2095
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1628,6 +2099,7 @@ var Reveal = (function(){
1628
2099
  verticalSlide.classList.remove( 'present' );
1629
2100
  verticalSlide.classList.remove( 'past' );
1630
2101
  verticalSlide.classList.add( 'future' );
2102
+ verticalSlide.setAttribute( 'aria-hidden', 'true' );
1631
2103
  }
1632
2104
 
1633
2105
  } );
@@ -1642,7 +2114,7 @@ var Reveal = (function(){
1642
2114
  */
1643
2115
  function sortAllFragments() {
1644
2116
 
1645
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2117
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
2118
  horizontalSlides.forEach( function( horizontalSlide ) {
1647
2119
 
1648
2120
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1675,9 +2147,11 @@ var Reveal = (function(){
1675
2147
 
1676
2148
  // Select all slides and convert the NodeList result to
1677
2149
  // an array
1678
- var slides = toArray( document.querySelectorAll( selector ) ),
2150
+ var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),
1679
2151
  slidesLength = slides.length;
1680
2152
 
2153
+ var printMode = isPrintingPDF();
2154
+
1681
2155
  if( slidesLength ) {
1682
2156
 
1683
2157
  // Should the index loop?
@@ -1703,43 +2177,55 @@ var Reveal = (function(){
1703
2177
 
1704
2178
  // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1705
2179
  element.setAttribute( 'hidden', '' );
2180
+ element.setAttribute( 'aria-hidden', 'true' );
2181
+
2182
+ // If this element contains vertical slides
2183
+ if( element.querySelector( 'section' ) ) {
2184
+ element.classList.add( 'stack' );
2185
+ }
2186
+
2187
+ // If we're printing static slides, all slides are "present"
2188
+ if( printMode ) {
2189
+ element.classList.add( 'present' );
2190
+ continue;
2191
+ }
1706
2192
 
1707
2193
  if( i < index ) {
1708
2194
  // Any element previous to index is given the 'past' class
1709
2195
  element.classList.add( reverse ? 'future' : 'past' );
1710
2196
 
1711
- var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
2197
+ if( config.fragments ) {
2198
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
2199
 
1713
- // Show all fragments on prior slides
1714
- while( pastFragments.length ) {
1715
- var pastFragment = pastFragments.pop();
1716
- pastFragment.classList.add( 'visible' );
1717
- pastFragment.classList.remove( 'current-fragment' );
2200
+ // Show all fragments on prior slides
2201
+ while( pastFragments.length ) {
2202
+ var pastFragment = pastFragments.pop();
2203
+ pastFragment.classList.add( 'visible' );
2204
+ pastFragment.classList.remove( 'current-fragment' );
2205
+ }
1718
2206
  }
1719
2207
  }
1720
2208
  else if( i > index ) {
1721
2209
  // Any element subsequent to index is given the 'future' class
1722
2210
  element.classList.add( reverse ? 'past' : 'future' );
1723
2211
 
1724
- var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
2212
+ if( config.fragments ) {
2213
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1725
2214
 
1726
- // No fragments in future slides should be visible ahead of time
1727
- while( futureFragments.length ) {
1728
- var futureFragment = futureFragments.pop();
1729
- futureFragment.classList.remove( 'visible' );
1730
- futureFragment.classList.remove( 'current-fragment' );
2215
+ // No fragments in future slides should be visible ahead of time
2216
+ while( futureFragments.length ) {
2217
+ var futureFragment = futureFragments.pop();
2218
+ futureFragment.classList.remove( 'visible' );
2219
+ futureFragment.classList.remove( 'current-fragment' );
2220
+ }
1731
2221
  }
1732
2222
  }
1733
-
1734
- // If this element contains vertical slides
1735
- if( element.querySelector( 'section' ) ) {
1736
- element.classList.add( 'stack' );
1737
- }
1738
2223
  }
1739
2224
 
1740
2225
  // Mark the current slide as present
1741
2226
  slides[index].classList.add( 'present' );
1742
2227
  slides[index].removeAttribute( 'hidden' );
2228
+ slides[index].removeAttribute( 'aria-hidden' );
1743
2229
 
1744
2230
  // If this slide has a state associated with it, add it
1745
2231
  // onto the current state of the deck
@@ -1767,12 +2253,12 @@ var Reveal = (function(){
1767
2253
 
1768
2254
  // Select all slides and convert the NodeList result to
1769
2255
  // an array
1770
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
2256
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1771
2257
  horizontalSlidesLength = horizontalSlides.length,
1772
2258
  distanceX,
1773
2259
  distanceY;
1774
2260
 
1775
- if( horizontalSlidesLength ) {
2261
+ if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
1776
2262
 
1777
2263
  // The number of steps away from the present slide that will
1778
2264
  // be visible
@@ -1780,7 +2266,12 @@ var Reveal = (function(){
1780
2266
 
1781
2267
  // Limit view distance on weaker devices
1782
2268
  if( isMobileDevice ) {
1783
- viewDistance = isOverview() ? 6 : 1;
2269
+ viewDistance = isOverview() ? 6 : 2;
2270
+ }
2271
+
2272
+ // Limit view distance on weaker devices
2273
+ if( isPrintingPDF() ) {
2274
+ viewDistance = Number.MAX_VALUE;
1784
2275
  }
1785
2276
 
1786
2277
  for( var x = 0; x < horizontalSlidesLength; x++ ) {
@@ -1790,10 +2281,15 @@ var Reveal = (function(){
1790
2281
  verticalSlidesLength = verticalSlides.length;
1791
2282
 
1792
2283
  // Loops so that it measures 1 between the first and last slides
1793
- distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2284
+ distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
1794
2285
 
1795
2286
  // Show the horizontal slide if it's within the view distance
1796
- horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
2287
+ if( distanceX < viewDistance ) {
2288
+ showSlide( horizontalSlide );
2289
+ }
2290
+ else {
2291
+ hideSlide( horizontalSlide );
2292
+ }
1797
2293
 
1798
2294
  if( verticalSlidesLength ) {
1799
2295
 
@@ -1802,9 +2298,14 @@ var Reveal = (function(){
1802
2298
  for( var y = 0; y < verticalSlidesLength; y++ ) {
1803
2299
  var verticalSlide = verticalSlides[y];
1804
2300
 
1805
- distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
2301
+ distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
1806
2302
 
1807
- verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
2303
+ if( distanceX + distanceY < viewDistance ) {
2304
+ showSlide( verticalSlide );
2305
+ }
2306
+ else {
2307
+ hideSlide( verticalSlide );
2308
+ }
1808
2309
  }
1809
2310
 
1810
2311
  }
@@ -1820,44 +2321,9 @@ var Reveal = (function(){
1820
2321
  function updateProgress() {
1821
2322
 
1822
2323
  // Update progress if enabled
1823
- if( config.progress && dom.progress ) {
1824
-
1825
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1826
-
1827
- // The number of past and total slides
1828
- var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
1829
- var pastCount = 0;
1830
-
1831
- // Step through all slides and count the past ones
1832
- mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
1833
-
1834
- var horizontalSlide = horizontalSlides[i];
1835
- var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
2324
+ if( config.progress && dom.progressbar ) {
1836
2325
 
1837
- for( var j = 0; j < verticalSlides.length; j++ ) {
1838
-
1839
- // Stop as soon as we arrive at the present
1840
- if( verticalSlides[j].classList.contains( 'present' ) ) {
1841
- break mainLoop;
1842
- }
1843
-
1844
- pastCount++;
1845
-
1846
- }
1847
-
1848
- // Stop as soon as we arrive at the present
1849
- if( horizontalSlide.classList.contains( 'present' ) ) {
1850
- break;
1851
- }
1852
-
1853
- // Don't count the wrapping section for vertical slides
1854
- if( horizontalSlide.classList.contains( 'stack' ) === false ) {
1855
- pastCount++;
1856
- }
1857
-
1858
- }
1859
-
1860
- dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
2326
+ dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';
1861
2327
 
1862
2328
  }
1863
2329
 
@@ -1951,30 +2417,38 @@ var Reveal = (function(){
1951
2417
  // states of their slides (past/present/future)
1952
2418
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1953
2419
 
2420
+ backgroundh.classList.remove( 'past' );
2421
+ backgroundh.classList.remove( 'present' );
2422
+ backgroundh.classList.remove( 'future' );
2423
+
1954
2424
  if( h < indexh ) {
1955
- backgroundh.className = 'slide-background ' + horizontalPast;
2425
+ backgroundh.classList.add( horizontalPast );
1956
2426
  }
1957
2427
  else if ( h > indexh ) {
1958
- backgroundh.className = 'slide-background ' + horizontalFuture;
2428
+ backgroundh.classList.add( horizontalFuture );
1959
2429
  }
1960
2430
  else {
1961
- backgroundh.className = 'slide-background present';
2431
+ backgroundh.classList.add( 'present' );
1962
2432
 
1963
2433
  // Store a reference to the current background element
1964
2434
  currentBackground = backgroundh;
1965
2435
  }
1966
2436
 
1967
2437
  if( includeAll || h === indexh ) {
1968
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
2438
+ toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
2439
+
2440
+ backgroundv.classList.remove( 'past' );
2441
+ backgroundv.classList.remove( 'present' );
2442
+ backgroundv.classList.remove( 'future' );
1969
2443
 
1970
2444
  if( v < indexv ) {
1971
- backgroundv.className = 'slide-background past';
2445
+ backgroundv.classList.add( 'past' );
1972
2446
  }
1973
2447
  else if ( v > indexv ) {
1974
- backgroundv.className = 'slide-background future';
2448
+ backgroundv.classList.add( 'future' );
1975
2449
  }
1976
2450
  else {
1977
- backgroundv.className = 'slide-background present';
2451
+ backgroundv.classList.add( 'present' );
1978
2452
 
1979
2453
  // Only if this is the present horizontal and vertical slide
1980
2454
  if( h === indexh ) currentBackground = backgroundv;
@@ -1985,9 +2459,25 @@ var Reveal = (function(){
1985
2459
 
1986
2460
  } );
1987
2461
 
1988
- // Don't transition between identical backgrounds. This
1989
- // prevents unwanted flicker.
2462
+ // Stop any currently playing video background
2463
+ if( previousBackground ) {
2464
+
2465
+ var previousVideo = previousBackground.querySelector( 'video' );
2466
+ if( previousVideo ) previousVideo.pause();
2467
+
2468
+ }
2469
+
1990
2470
  if( currentBackground ) {
2471
+
2472
+ // Start video playback
2473
+ var currentVideo = currentBackground.querySelector( 'video' );
2474
+ if( currentVideo ) {
2475
+ currentVideo.currentTime = 0;
2476
+ currentVideo.play();
2477
+ }
2478
+
2479
+ // Don't transition between identical backgrounds. This
2480
+ // prevents unwanted flicker.
1991
2481
  var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
2482
  var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
2483
  if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
@@ -1995,6 +2485,20 @@ var Reveal = (function(){
1995
2485
  }
1996
2486
 
1997
2487
  previousBackground = currentBackground;
2488
+
2489
+ }
2490
+
2491
+ // If there's a background brightness flag for this slide,
2492
+ // bubble it to the .reveal container
2493
+ if( currentSlide ) {
2494
+ [ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {
2495
+ if( currentSlide.classList.contains( classToBubble ) ) {
2496
+ dom.wrapper.classList.add( classToBubble );
2497
+ }
2498
+ else {
2499
+ dom.wrapper.classList.remove( classToBubble );
2500
+ }
2501
+ } );
1998
2502
  }
1999
2503
 
2000
2504
  // Allow the first background to apply without transition
@@ -2012,8 +2516,8 @@ var Reveal = (function(){
2012
2516
 
2013
2517
  if( config.parallaxBackgroundImage ) {
2014
2518
 
2015
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2519
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2520
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
2521
 
2018
2522
  var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
2523
  backgroundWidth, backgroundHeight;
@@ -2032,7 +2536,7 @@ var Reveal = (function(){
2032
2536
 
2033
2537
  var slideHeight = dom.background.offsetHeight;
2034
2538
  var verticalSlideCount = verticalSlides.length;
2035
- var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2539
+ var verticalOffset = verticalSlideCount > 1 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2036
2540
 
2037
2541
  dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2038
2542
 
@@ -2040,6 +2544,103 @@ var Reveal = (function(){
2040
2544
 
2041
2545
  }
2042
2546
 
2547
+ /**
2548
+ * Called when the given slide is within the configured view
2549
+ * distance. Shows the slide element and loads any content
2550
+ * that is set to load lazily (data-src).
2551
+ */
2552
+ function showSlide( slide ) {
2553
+
2554
+ // Show the slide element
2555
+ slide.style.display = 'block';
2556
+
2557
+ // Media elements with data-src attributes
2558
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) {
2559
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
2560
+ element.removeAttribute( 'data-src' );
2561
+ } );
2562
+
2563
+ // Media elements with <source> children
2564
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
2565
+ var sources = 0;
2566
+
2567
+ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
2568
+ source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
2569
+ source.removeAttribute( 'data-src' );
2570
+ sources += 1;
2571
+ } );
2572
+
2573
+ // If we rewrote sources for this video/audio element, we need
2574
+ // to manually tell it to load from its new origin
2575
+ if( sources > 0 ) {
2576
+ media.load();
2577
+ }
2578
+ } );
2579
+
2580
+
2581
+ // Show the corresponding background element
2582
+ var indices = getIndices( slide );
2583
+ var background = getSlideBackground( indices.h, indices.v );
2584
+ if( background ) {
2585
+ background.style.display = 'block';
2586
+
2587
+ // If the background contains media, load it
2588
+ if( background.hasAttribute( 'data-loaded' ) === false ) {
2589
+ background.setAttribute( 'data-loaded', 'true' );
2590
+
2591
+ var backgroundImage = slide.getAttribute( 'data-background-image' ),
2592
+ backgroundVideo = slide.getAttribute( 'data-background-video' ),
2593
+ backgroundIframe = slide.getAttribute( 'data-background-iframe' );
2594
+
2595
+ // Images
2596
+ if( backgroundImage ) {
2597
+ background.style.backgroundImage = 'url('+ backgroundImage +')';
2598
+ }
2599
+ // Videos
2600
+ else if ( backgroundVideo && !isSpeakerNotes() ) {
2601
+ var video = document.createElement( 'video' );
2602
+
2603
+ // Support comma separated lists of video sources
2604
+ backgroundVideo.split( ',' ).forEach( function( source ) {
2605
+ video.innerHTML += '<source src="'+ source +'">';
2606
+ } );
2607
+
2608
+ background.appendChild( video );
2609
+ }
2610
+ // Iframes
2611
+ else if ( backgroundIframe ) {
2612
+ var iframe = document.createElement( 'iframe' );
2613
+ iframe.setAttribute( 'src', backgroundIframe );
2614
+ iframe.style.width = '100%';
2615
+ iframe.style.height = '100%';
2616
+ iframe.style.maxHeight = '100%';
2617
+ iframe.style.maxWidth = '100%';
2618
+
2619
+ background.appendChild( iframe );
2620
+ }
2621
+ }
2622
+ }
2623
+
2624
+ }
2625
+
2626
+ /**
2627
+ * Called when the given slide is moved outside of the
2628
+ * configured view distance.
2629
+ */
2630
+ function hideSlide( slide ) {
2631
+
2632
+ // Hide the slide element
2633
+ slide.style.display = 'none';
2634
+
2635
+ // Hide the corresponding background element
2636
+ var indices = getIndices( slide );
2637
+ var background = getSlideBackground( indices.h, indices.v );
2638
+ if( background ) {
2639
+ background.style.display = 'none';
2640
+ }
2641
+
2642
+ }
2643
+
2043
2644
  /**
2044
2645
  * Determine what available routes there are for navigation.
2045
2646
  *
@@ -2047,8 +2648,8 @@ var Reveal = (function(){
2047
2648
  */
2048
2649
  function availableRoutes() {
2049
2650
 
2050
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2051
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2651
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2652
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2052
2653
 
2053
2654
  var routes = {
2054
2655
  left: indexh > 0 || config.loop,
@@ -2091,6 +2692,29 @@ var Reveal = (function(){
2091
2692
 
2092
2693
  }
2093
2694
 
2695
+ /**
2696
+ * Enforces origin-specific format rules for embedded media.
2697
+ */
2698
+ function formatEmbeddedContent() {
2699
+
2700
+ // YouTube frames must include "?enablejsapi=1"
2701
+ toArray( dom.slides.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2702
+ var src = el.getAttribute( 'src' );
2703
+ if( !/enablejsapi\=1/gi.test( src ) ) {
2704
+ el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'enablejsapi=1' );
2705
+ }
2706
+ });
2707
+
2708
+ // Vimeo frames must include "?api=1"
2709
+ toArray( dom.slides.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2710
+ var src = el.getAttribute( 'src' );
2711
+ if( !/api\=1/gi.test( src ) ) {
2712
+ el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'api=1' );
2713
+ }
2714
+ });
2715
+
2716
+ }
2717
+
2094
2718
  /**
2095
2719
  * Start playback of any embedded content inside of
2096
2720
  * the targeted slide.
@@ -2116,6 +2740,13 @@ var Reveal = (function(){
2116
2740
  el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2117
2741
  }
2118
2742
  });
2743
+
2744
+ // Vimeo embeds
2745
+ toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2746
+ if( el.hasAttribute( 'data-autoplay' ) ) {
2747
+ el.contentWindow.postMessage( '{"method":"play"}', '*' );
2748
+ }
2749
+ });
2119
2750
  }
2120
2751
 
2121
2752
  }
@@ -2126,7 +2757,7 @@ var Reveal = (function(){
2126
2757
  */
2127
2758
  function stopEmbeddedContent( slide ) {
2128
2759
 
2129
- if( slide ) {
2760
+ if( slide && slide.parentNode ) {
2130
2761
  // HTML5 media elements
2131
2762
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2132
2763
  if( !el.hasAttribute( 'data-ignore' ) ) {
@@ -2145,8 +2776,79 @@ var Reveal = (function(){
2145
2776
  el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
2146
2777
  }
2147
2778
  });
2779
+
2780
+ // Vimeo embeds
2781
+ toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2782
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2783
+ el.contentWindow.postMessage( '{"method":"pause"}', '*' );
2784
+ }
2785
+ });
2786
+ }
2787
+
2788
+ }
2789
+
2790
+ /**
2791
+ * Returns a value ranging from 0-1 that represents
2792
+ * how far into the presentation we have navigated.
2793
+ */
2794
+ function getProgress() {
2795
+
2796
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2797
+
2798
+ // The number of past and total slides
2799
+ var totalCount = getTotalSlides();
2800
+ var pastCount = 0;
2801
+
2802
+ // Step through all slides and count the past ones
2803
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
2804
+
2805
+ var horizontalSlide = horizontalSlides[i];
2806
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
2807
+
2808
+ for( var j = 0; j < verticalSlides.length; j++ ) {
2809
+
2810
+ // Stop as soon as we arrive at the present
2811
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
2812
+ break mainLoop;
2813
+ }
2814
+
2815
+ pastCount++;
2816
+
2817
+ }
2818
+
2819
+ // Stop as soon as we arrive at the present
2820
+ if( horizontalSlide.classList.contains( 'present' ) ) {
2821
+ break;
2822
+ }
2823
+
2824
+ // Don't count the wrapping section for vertical slides
2825
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
2826
+ pastCount++;
2827
+ }
2828
+
2829
+ }
2830
+
2831
+ if( currentSlide ) {
2832
+
2833
+ var allFragments = currentSlide.querySelectorAll( '.fragment' );
2834
+
2835
+ // If there are fragments in the current slide those should be
2836
+ // accounted for in the progress.
2837
+ if( allFragments.length > 0 ) {
2838
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2839
+
2840
+ // This value represents how big a portion of the slide progress
2841
+ // that is made up by its fragments (0-1)
2842
+ var fragmentWeight = 0.9;
2843
+
2844
+ // Add fragment progress to the past slide count
2845
+ pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
2846
+ }
2847
+
2148
2848
  }
2149
2849
 
2850
+ return pastCount / ( totalCount - 1 );
2851
+
2150
2852
  }
2151
2853
 
2152
2854
  /**
@@ -2173,8 +2875,13 @@ var Reveal = (function(){
2173
2875
  // If the first bit is invalid and there is a name we can
2174
2876
  // assume that this is a named link
2175
2877
  if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
2176
- // Find the slide with the specified name
2177
- var element = document.querySelector( '#' + name );
2878
+ var element;
2879
+
2880
+ // Ensure the named link is a valid HTML ID attribute
2881
+ if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
2882
+ // Find the slide with the specified ID
2883
+ element = document.querySelector( '#' + name );
2884
+ }
2178
2885
 
2179
2886
  if( element ) {
2180
2887
  // Find the position of the named slide and navigate to it
@@ -2216,12 +2923,19 @@ var Reveal = (function(){
2216
2923
  if( typeof delay === 'number' ) {
2217
2924
  writeURLTimeout = setTimeout( writeURL, delay );
2218
2925
  }
2219
- else {
2926
+ else if( currentSlide ) {
2220
2927
  var url = '/';
2221
2928
 
2929
+ // Attempt to create a named link based on the slide's ID
2930
+ var id = currentSlide.getAttribute( 'id' );
2931
+ if( id ) {
2932
+ id = id.toLowerCase();
2933
+ id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
2934
+ }
2935
+
2222
2936
  // If the current slide has an ID, use that as a named link
2223
- if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
2224
- url = '/' + currentSlide.getAttribute( 'id' );
2937
+ if( typeof id === 'string' && id.length ) {
2938
+ url = '/' + id;
2225
2939
  }
2226
2940
  // Otherwise use the /h/v index
2227
2941
  else {
@@ -2258,11 +2972,14 @@ var Reveal = (function(){
2258
2972
  var slideh = isVertical ? slide.parentNode : slide;
2259
2973
 
2260
2974
  // Select all horizontal slides
2261
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2975
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2262
2976
 
2263
2977
  // Now that we know which the horizontal slide is, get its index
2264
2978
  h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
2265
2979
 
2980
+ // Assume we're not vertical
2981
+ v = undefined;
2982
+
2266
2983
  // If this is a vertical slide, grab the vertical index
2267
2984
  if( isVertical ) {
2268
2985
  v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
@@ -2272,8 +2989,13 @@ var Reveal = (function(){
2272
2989
  if( !slide && currentSlide ) {
2273
2990
  var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2274
2991
  if( hasFragments ) {
2275
- var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2276
- f = visibleFragments.length - 1;
2992
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
2993
+ if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
2994
+ f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
2995
+ }
2996
+ else {
2997
+ f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
2998
+ }
2277
2999
  }
2278
3000
  }
2279
3001
 
@@ -2281,6 +3003,107 @@ var Reveal = (function(){
2281
3003
 
2282
3004
  }
2283
3005
 
3006
+ /**
3007
+ * Retrieves the total number of slides in this presentation.
3008
+ */
3009
+ function getTotalSlides() {
3010
+
3011
+ return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
3012
+
3013
+ }
3014
+
3015
+ /**
3016
+ * Returns the slide element matching the specified index.
3017
+ */
3018
+ function getSlide( x, y ) {
3019
+
3020
+ var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3021
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
3022
+
3023
+ if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
3024
+ return verticalSlides ? verticalSlides[ y ] : undefined;
3025
+ }
3026
+
3027
+ return horizontalSlide;
3028
+
3029
+ }
3030
+
3031
+ /**
3032
+ * Returns the background element for the given slide.
3033
+ * All slides, even the ones with no background properties
3034
+ * defined, have a background element so as long as the
3035
+ * index is valid an element will be returned.
3036
+ */
3037
+ function getSlideBackground( x, y ) {
3038
+
3039
+ // When printing to PDF the slide backgrounds are nested
3040
+ // inside of the slides
3041
+ if( isPrintingPDF() ) {
3042
+ var slide = getSlide( x, y );
3043
+ if( slide ) {
3044
+ var background = slide.querySelector( '.slide-background' );
3045
+ if( background && background.parentNode === slide ) {
3046
+ return background;
3047
+ }
3048
+ }
3049
+
3050
+ return undefined;
3051
+ }
3052
+
3053
+ var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
3054
+ var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
3055
+
3056
+ if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
3057
+ return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
3058
+ }
3059
+
3060
+ return horizontalBackground;
3061
+
3062
+ }
3063
+
3064
+ /**
3065
+ * Retrieves the current state of the presentation as
3066
+ * an object. This state can then be restored at any
3067
+ * time.
3068
+ */
3069
+ function getState() {
3070
+
3071
+ var indices = getIndices();
3072
+
3073
+ return {
3074
+ indexh: indices.h,
3075
+ indexv: indices.v,
3076
+ indexf: indices.f,
3077
+ paused: isPaused(),
3078
+ overview: isOverview()
3079
+ };
3080
+
3081
+ }
3082
+
3083
+ /**
3084
+ * Restores the presentation to the given state.
3085
+ *
3086
+ * @param {Object} state As generated by getState()
3087
+ */
3088
+ function setState( state ) {
3089
+
3090
+ if( typeof state === 'object' ) {
3091
+ slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
3092
+
3093
+ var pausedFlag = deserialize( state.paused ),
3094
+ overviewFlag = deserialize( state.overview );
3095
+
3096
+ if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
3097
+ togglePause( pausedFlag );
3098
+ }
3099
+
3100
+ if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
3101
+ toggleOverview( overviewFlag );
3102
+ }
3103
+ }
3104
+
3105
+ }
3106
+
2284
3107
  /**
2285
3108
  * Return a sorted fragments list, ordered by an increasing
2286
3109
  * "data-fragment-index" attribute.
@@ -2392,6 +3215,9 @@ var Reveal = (function(){
2392
3215
  element.classList.add( 'visible' );
2393
3216
  element.classList.remove( 'current-fragment' );
2394
3217
 
3218
+ // Announce the fragments one by one to the Screen Reader
3219
+ dom.statusDiv.textContent = element.textContent;
3220
+
2395
3221
  if( i === index ) {
2396
3222
  element.classList.add( 'current-fragment' );
2397
3223
  }
@@ -2415,6 +3241,7 @@ var Reveal = (function(){
2415
3241
  }
2416
3242
 
2417
3243
  updateControls();
3244
+ updateProgress();
2418
3245
 
2419
3246
  return !!( fragmentsShown.length || fragmentsHidden.length );
2420
3247
 
@@ -2459,14 +3286,21 @@ var Reveal = (function(){
2459
3286
 
2460
3287
  if( currentSlide ) {
2461
3288
 
3289
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3290
+
3291
+ var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
2462
3292
  var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
3293
  var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
3294
 
2465
3295
  // Pick value in the following priority order:
2466
- // 1. Current slide's data-autoslide
2467
- // 2. Parent slide's data-autoslide
2468
- // 3. Global autoSlide setting
2469
- if( slideAutoSlide ) {
3296
+ // 1. Current fragment's data-autoslide
3297
+ // 2. Current slide's data-autoslide
3298
+ // 3. Parent slide's data-autoslide
3299
+ // 4. Global autoSlide setting
3300
+ if( fragmentAutoSlide ) {
3301
+ autoSlide = parseInt( fragmentAutoSlide, 10 );
3302
+ }
3303
+ else if( slideAutoSlide ) {
2470
3304
  autoSlide = parseInt( slideAutoSlide, 10 );
2471
3305
  }
2472
3306
  else if( parentAutoSlide ) {
@@ -2493,7 +3327,7 @@ var Reveal = (function(){
2493
3327
  // - The presentation isn't paused
2494
3328
  // - The overview isn't active
2495
3329
  // - The presentation isn't over
2496
- if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
3330
+ if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
2497
3331
  autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
3332
  autoSlideStartTime = Date.now();
2499
3333
  }
@@ -2518,19 +3352,25 @@ var Reveal = (function(){
2518
3352
 
2519
3353
  function pauseAutoSlide() {
2520
3354
 
2521
- autoSlidePaused = true;
2522
- clearTimeout( autoSlideTimeout );
3355
+ if( autoSlide && !autoSlidePaused ) {
3356
+ autoSlidePaused = true;
3357
+ dispatchEvent( 'autoslidepaused' );
3358
+ clearTimeout( autoSlideTimeout );
2523
3359
 
2524
- if( autoSlidePlayer ) {
2525
- autoSlidePlayer.setPlaying( false );
3360
+ if( autoSlidePlayer ) {
3361
+ autoSlidePlayer.setPlaying( false );
3362
+ }
2526
3363
  }
2527
3364
 
2528
3365
  }
2529
3366
 
2530
3367
  function resumeAutoSlide() {
2531
3368
 
2532
- autoSlidePaused = false;
2533
- cueAutoSlide();
3369
+ if( autoSlide && autoSlidePaused ) {
3370
+ autoSlidePaused = false;
3371
+ dispatchEvent( 'autoslideresumed' );
3372
+ cueAutoSlide();
3373
+ }
2534
3374
 
2535
3375
  }
2536
3376
 
@@ -2597,7 +3437,14 @@ var Reveal = (function(){
2597
3437
  }
2598
3438
  else {
2599
3439
  // Fetch the previous horizontal slide, if there is one
2600
- var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
3440
+ var previousSlide;
3441
+
3442
+ if( config.rtl ) {
3443
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();
3444
+ }
3445
+ else {
3446
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();
3447
+ }
2601
3448
 
2602
3449
  if( previousSlide ) {
2603
3450
  var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
@@ -2610,13 +3457,21 @@ var Reveal = (function(){
2610
3457
  }
2611
3458
 
2612
3459
  /**
2613
- * Same as #navigatePrev() but navigates forwards.
3460
+ * The reverse of #navigatePrev().
2614
3461
  */
2615
3462
  function navigateNext() {
2616
3463
 
2617
3464
  // Prioritize revealing fragments
2618
3465
  if( nextFragment() === false ) {
2619
- availableRoutes().down ? navigateDown() : navigateRight();
3466
+ if( availableRoutes().down ) {
3467
+ navigateDown();
3468
+ }
3469
+ else if( config.rtl ) {
3470
+ navigateLeft();
3471
+ }
3472
+ else {
3473
+ navigateRight();
3474
+ }
2620
3475
  }
2621
3476
 
2622
3477
  // If auto-sliding is enabled we need to cue up
@@ -2642,21 +3497,47 @@ var Reveal = (function(){
2642
3497
 
2643
3498
  }
2644
3499
 
3500
+ /**
3501
+ * Handler for the document level 'keypress' event.
3502
+ */
3503
+ function onDocumentKeyPress( event ) {
3504
+
3505
+ // Check if the pressed key is question mark
3506
+ if( event.shiftKey && event.charCode === 63 ) {
3507
+ if( dom.overlay ) {
3508
+ closeOverlay();
3509
+ }
3510
+ else {
3511
+ showHelp( true );
3512
+ }
3513
+ }
3514
+
3515
+ }
3516
+
2645
3517
  /**
2646
3518
  * Handler for the document level 'keydown' event.
2647
3519
  */
2648
3520
  function onDocumentKeyDown( event ) {
2649
3521
 
3522
+ // If there's a condition specified and it returns false,
3523
+ // ignore this event
3524
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
3525
+ return true;
3526
+ }
3527
+
3528
+ // Remember if auto-sliding was paused so we can toggle it
3529
+ var autoSlideWasPaused = autoSlidePaused;
3530
+
2650
3531
  onUserInput( event );
2651
3532
 
2652
3533
  // Check if there's a focused element that could be using
2653
3534
  // the keyboard
2654
- var activeElement = document.activeElement;
2655
- var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
3535
+ var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
3536
+ var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
2656
3537
 
2657
3538
  // Disregard the event if there's a focused element or a
2658
3539
  // keyboard modifier key is present
2659
- if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
3540
+ if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
2660
3541
 
2661
3542
  // While paused only allow "unpausing" keyboard events (b and .)
2662
3543
  if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
@@ -2719,10 +3600,12 @@ var Reveal = (function(){
2719
3600
  case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2720
3601
  // return
2721
3602
  case 13: isOverview() ? deactivateOverview() : triggered = false; break;
2722
- // b, period, Logitech presenter tools "black screen" button
2723
- case 66: case 190: case 191: togglePause(); break;
3603
+ // two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
3604
+ case 58: case 59: case 66: case 190: case 191: togglePause(); break;
2724
3605
  // f
2725
3606
  case 70: enterFullscreen(); break;
3607
+ // a
3608
+ case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
2726
3609
  default:
2727
3610
  triggered = false;
2728
3611
  }
@@ -2732,18 +3615,18 @@ var Reveal = (function(){
2732
3615
  // If the input resulted in a triggered action we should prevent
2733
3616
  // the browsers default behavior
2734
3617
  if( triggered ) {
2735
- event.preventDefault();
3618
+ event.preventDefault && event.preventDefault();
2736
3619
  }
2737
3620
  // ESC or O key
2738
3621
  else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
- if( dom.preview ) {
2740
- closePreview();
3622
+ if( dom.overlay ) {
3623
+ closeOverlay();
2741
3624
  }
2742
3625
  else {
2743
3626
  toggleOverview();
2744
3627
  }
2745
3628
 
2746
- event.preventDefault();
3629
+ event.preventDefault && event.preventDefault();
2747
3630
  }
2748
3631
 
2749
3632
  // If auto-sliding is enabled we need to cue up
@@ -2877,7 +3760,7 @@ var Reveal = (function(){
2877
3760
  */
2878
3761
  function onPointerDown( event ) {
2879
3762
 
2880
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3763
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2881
3764
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2882
3765
  onTouchStart( event );
2883
3766
  }
@@ -2889,7 +3772,7 @@ var Reveal = (function(){
2889
3772
  */
2890
3773
  function onPointerMove( event ) {
2891
3774
 
2892
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3775
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2893
3776
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2894
3777
  onTouchMove( event );
2895
3778
  }
@@ -2901,7 +3784,7 @@ var Reveal = (function(){
2901
3784
  */
2902
3785
  function onPointerUp( event ) {
2903
3786
 
2904
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3787
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2905
3788
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2906
3789
  onTouchEnd( event );
2907
3790
  }
@@ -2942,7 +3825,7 @@ var Reveal = (function(){
2942
3825
 
2943
3826
  event.preventDefault();
2944
3827
 
2945
- var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
3828
+ var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
2946
3829
  var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
2947
3830
 
2948
3831
  slide( slideIndex );
@@ -3033,10 +3916,12 @@ var Reveal = (function(){
3033
3916
  */
3034
3917
  function onPreviewLinkClicked( event ) {
3035
3918
 
3036
- var url = event.target.getAttribute( 'href' );
3037
- if( url ) {
3038
- openPreview( url );
3039
- event.preventDefault();
3919
+ if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
3920
+ var url = event.currentTarget.getAttribute( 'href' );
3921
+ if( url ) {
3922
+ showPreview( url );
3923
+ event.preventDefault();
3924
+ }
3040
3925
  }
3041
3926
 
3042
3927
  }
@@ -3232,7 +4117,7 @@ var Reveal = (function(){
3232
4117
  // --------------------------------------------------------------------//
3233
4118
 
3234
4119
 
3235
- return {
4120
+ Reveal = {
3236
4121
  initialize: initialize,
3237
4122
  configure: configure,
3238
4123
  sync: sync,
@@ -3275,28 +4160,35 @@ var Reveal = (function(){
3275
4160
  // Toggles the "black screen" mode on/off
3276
4161
  togglePause: togglePause,
3277
4162
 
4163
+ // Toggles the auto slide mode on/off
4164
+ toggleAutoSlide: toggleAutoSlide,
4165
+
3278
4166
  // State checks
3279
4167
  isOverview: isOverview,
3280
4168
  isPaused: isPaused,
4169
+ isAutoSliding: isAutoSliding,
3281
4170
 
3282
4171
  // Adds or removes all internal event listeners (such as keyboard)
3283
4172
  addEventListeners: addEventListeners,
3284
4173
  removeEventListeners: removeEventListeners,
3285
4174
 
4175
+ // Facility for persisting and restoring the presentation state
4176
+ getState: getState,
4177
+ setState: setState,
4178
+
4179
+ // Presentation progress on range of 0-1
4180
+ getProgress: getProgress,
4181
+
3286
4182
  // Returns the indices of the current, or specified, slide
3287
4183
  getIndices: getIndices,
3288
4184
 
3289
- // Returns the slide at the specified index, y is optional
3290
- getSlide: function( x, y ) {
3291
- var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3292
- var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
4185
+ getTotalSlides: getTotalSlides,
3293
4186
 
3294
- if( typeof y !== 'undefined' ) {
3295
- return verticalSlides ? verticalSlides[ y ] : undefined;
3296
- }
4187
+ // Returns the slide element at the specified index
4188
+ getSlide: getSlide,
3297
4189
 
3298
- return horizontalSlide;
3299
- },
4190
+ // Returns the slide background element at the specified index
4191
+ getSlideBackground: getSlideBackground,
3300
4192
 
3301
4193
  // Returns the previous slide element, may be null
3302
4194
  getPreviousSlide: function() {
@@ -3330,12 +4222,7 @@ var Reveal = (function(){
3330
4222
  for( var i in query ) {
3331
4223
  var value = query[ i ];
3332
4224
 
3333
- query[ i ] = unescape( value );
3334
-
3335
- if( value === 'null' ) query[ i ] = null;
3336
- else if( value === 'true' ) query[ i ] = true;
3337
- else if( value === 'false' ) query[ i ] = false;
3338
- else if( value.match( /^\d+$/ ) ) query[ i ] = parseFloat( value );
4225
+ query[ i ] = deserialize( unescape( value ) );
3339
4226
  }
3340
4227
 
3341
4228
  return query;
@@ -3343,7 +4230,7 @@ var Reveal = (function(){
3343
4230
 
3344
4231
  // Returns true if we're currently on the first slide
3345
4232
  isFirstSlide: function() {
3346
- return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
4233
+ return ( indexh === 0 && indexv === 0 );
3347
4234
  },
3348
4235
 
3349
4236
  // Returns true if we're currently on the last slide
@@ -3376,7 +4263,14 @@ var Reveal = (function(){
3376
4263
  if( 'addEventListener' in window ) {
3377
4264
  ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
3378
4265
  }
4266
+ },
4267
+
4268
+ // Programatically triggers a keyboard event
4269
+ triggerKey: function( keyCode ) {
4270
+ onDocumentKeyDown( { keyCode: keyCode } );
3379
4271
  }
3380
4272
  };
3381
4273
 
3382
- })();
4274
+ return Reveal;
4275
+
4276
+ }));