hyla 1.0.7 → 1.0.8

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 (37) hide show
  1. checksums.yaml +8 -8
  2. data/Rakefile +6 -2
  3. data/bin/hyla +3 -2
  4. data/lib/hyla/commands/generate.rb +43 -41
  5. data/lib/hyla/configuration.rb +1 -1
  6. data/lib/hyla/project.rb +1 -1
  7. data/lib/resources/assets/revealjs-redhat/image/collapsed.png +0 -0
  8. data/lib/resources/assets/revealjs-redhat/image/expanded.png +0 -0
  9. data/lib/resources/assets/revealjs-redhat/lib/css/conference.css +663 -0
  10. data/lib/resources/assets/revealjs-redhat/lib/css/font-awesome-4.3.0.css +2886 -1098
  11. data/lib/resources/assets/revealjs-redhat/lib/css/gpe.css +746 -180
  12. data/lib/resources/assets/revealjs-redhat/lib/css/print/pdf.css +32 -65
  13. data/lib/resources/assets/revealjs-redhat/lib/css/theme-output.css +1509 -395
  14. data/lib/resources/assets/revealjs-redhat/lib/css/theme-v2-liberation.css +4332 -1366
  15. data/lib/resources/assets/revealjs-redhat/lib/css/theme-v2-overpass.css +4320 -1364
  16. data/lib/resources/assets/revealjs-redhat/lib/js/debug/gpe.js +8 -8
  17. data/lib/resources/assets/revealjs-redhat/lib/js/debug/reveal.js +129 -91
  18. data/lib/resources/assets/revealjs-redhat/lib/js/gpe.min.js +3 -3
  19. data/lib/resources/assets/revealjs-redhat/lib/js/reveal.min.js +18 -13
  20. data/lib/resources/assets/revealjs/css/theme/conference-redhat.css +14 -6
  21. data/lib/resources/assets/revealjs/css/theme/old-gpe.css +670 -181
  22. data/lib/resources/assets/revealjs/js/{reveal.js → debug/reveal.js} +1619 -492
  23. data/lib/resources/assets/revealjs/js/reveal.min.js +342 -9
  24. data/lib/resources/assets/revealjs/lib/css/font-awesome-4.3.0.css +2886 -1098
  25. data/lib/resources/assets/sass/conference.scss +589 -0
  26. data/lib/resources/assets/sass/new-gpe.scss +79 -0
  27. data/lib/resources/backends/slim/revealjs-redhat/block_paragraph.html.slim +18 -6
  28. data/lib/resources/backends/slim/revealjs-redhat/block_ulist.html.slim +25 -9
  29. data/lib/resources/backends/slim/revealjs/document.html.slim +1 -1
  30. data/lib/templates/course/audio.txt +2 -2
  31. data/lib/templates/course/cover.txt +19 -7
  32. data/lib/templates/course/footer.txt +1 -3
  33. data/lib/templates/course/index.txt +2 -2
  34. data/lib/templates/course/labinstructions.txt +1 -3
  35. data/lib/templates/course/objectives.txt +1 -12
  36. data/lib/templates/course/summary.txt +1 -8
  37. metadata +7 -3
@@ -2,19 +2,36 @@
2
2
  * reveal.js
3
3
  * http://lab.hakim.se/reveal-js
4
4
  * MIT licensed
5
+ * Version 3.1.0
5
6
  *
6
- * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
7
+ * Copyright (C) 2015 Hakim El Hattab, http://hakim.se
7
8
  */
8
- var Reveal = (function(){
9
+ (function( root, factory ) {
10
+ if( typeof define === 'function' && define.amd ) {
11
+ // AMD. Register as an anonymous module.
12
+ define( function() {
13
+ root.Reveal = factory();
14
+ return root.Reveal;
15
+ } );
16
+ } else if( typeof exports === 'object' ) {
17
+ // Node. Does not work with strict CommonJS.
18
+ module.exports = factory();
19
+ } else {
20
+ // Browser globals.
21
+ root.Reveal = factory();
22
+ }
23
+ }( this, function() {
9
24
 
10
25
  'use strict';
11
26
 
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',
27
+ var Reveal;
16
28
 
17
- // Configurations defaults, can be overridden at initialization time
29
+ var SLIDES_SELECTOR = '.slides section',
30
+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
31
+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
32
+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
33
+
34
+ // Configuration defaults, can be overridden at initialization time
18
35
  config = {
19
36
 
20
37
  // The "normal" size of the presentation, aspect ratio will be preserved
@@ -27,7 +44,7 @@ var Reveal = (function(){
27
44
 
28
45
  // Bounds for smallest/largest possible scale to apply to content
29
46
  minScale: 0.2,
30
- maxScale: 1.0,
47
+ maxScale: 1.5,
31
48
 
32
49
  // Display controls in the bottom right corner
33
50
  controls: true,
@@ -44,6 +61,9 @@ var Reveal = (function(){
44
61
  // Enable keyboard shortcuts for navigation
45
62
  keyboard: true,
46
63
 
64
+ // Optional function that blocks keyboard events when retuning false
65
+ keyboardCondition: null,
66
+
47
67
  // Enable the slide overview mode
48
68
  overview: true,
49
69
 
@@ -66,6 +86,13 @@ var Reveal = (function(){
66
86
  // i.e. contained within a limited portion of the screen
67
87
  embedded: false,
68
88
 
89
+ // Flags if we should show a help overlay when the questionmark
90
+ // key is pressed
91
+ help: true,
92
+
93
+ // Flags if it should be possible to pause the presentation (blackout)
94
+ pause: true,
95
+
69
96
  // Number of milliseconds between automatically proceeding to the
70
97
  // next slide, disabled when set to 0, this value can be overwritten
71
98
  // by using a data-autoslide attribute on your slides
@@ -86,20 +113,23 @@ var Reveal = (function(){
86
113
  // Opens links in an iframe preview overlay
87
114
  previewLinks: false,
88
115
 
89
- // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
- focusBodyOnPageVisiblityChange: true,
116
+ // Exposes the reveal.js API through window.postMessage
117
+ postMessage: true,
91
118
 
92
- // Theme (see /css/theme)
93
- theme: null,
119
+ // Dispatches all reveal.js events to the parent window through postMessage
120
+ postMessageEvents: false,
121
+
122
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
123
+ focusBodyOnPageVisibilityChange: true,
94
124
 
95
125
  // Transition style
96
- transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
126
+ transition: 'slide', // none/fade/slide/convex/concave/zoom
97
127
 
98
128
  // Transition speed
99
129
  transitionSpeed: 'default', // default/fast/slow
100
130
 
101
131
  // Transition style for full page slide backgrounds
102
- backgroundTransition: 'default', // default/linear/none
132
+ backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
103
133
 
104
134
  // Parallax background image
105
135
  parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
@@ -107,6 +137,10 @@ var Reveal = (function(){
107
137
  // Parallax background size
108
138
  parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
109
139
 
140
+ // Amount of pixels to move the parallax background per slide step
141
+ parallaxBackgroundHorizontal: null,
142
+ parallaxBackgroundVertical: null,
143
+
110
144
  // Number of slides away from the current that are visible
111
145
  viewDistance: 3,
112
146
 
@@ -115,61 +149,62 @@ var Reveal = (function(){
115
149
 
116
150
  },
117
151
 
118
- // Flags if reveal.js is loaded (has dispatched the 'ready' event)
152
+ // Flags if reveal.js is loaded (has dispatched the 'ready' event)
119
153
  loaded = false,
120
154
 
121
- // The horizontal and vertical index of the currently active slide
155
+ // Flags if the overview mode is currently active
156
+ overview = false,
157
+
158
+ // The horizontal and vertical index of the currently active slide
122
159
  indexh,
123
160
  indexv,
124
161
 
125
- // The previous and current slide HTML elements
162
+ // The previous and current slide HTML elements
126
163
  previousSlide,
127
164
  currentSlide,
128
165
 
129
166
  previousBackground,
130
167
 
131
- // Slides may hold a data-state attribute which we pick up and apply
132
- // as a class to the body. This list contains the combined state of
133
- // all current slides.
168
+ // Slides may hold a data-state attribute which we pick up and apply
169
+ // as a class to the body. This list contains the combined state of
170
+ // all current slides.
134
171
  state = [],
135
172
 
136
- // The current scale of the presentation (see width/height config)
173
+ // The current scale of the presentation (see width/height config)
137
174
  scale = 1,
138
175
 
139
- // Cached references to DOM elements
176
+ // CSS transform that is currently applied to the slides container,
177
+ // split into two groups
178
+ slidesTransform = { layout: '', overview: '' },
179
+
180
+ // Cached references to DOM elements
140
181
  dom = {},
141
182
 
142
- // Features supported by the browser, see #checkCapabilities()
183
+ // Features supported by the browser, see #checkCapabilities()
143
184
  features = {},
144
185
 
145
- // Client is a mobile device, see #checkCapabilities()
186
+ // Client is a mobile device, see #checkCapabilities()
146
187
  isMobileDevice,
147
188
 
148
- // Throttles mouse wheel navigation
189
+ // Throttles mouse wheel navigation
149
190
  lastMouseWheelStep = 0,
150
191
 
151
- // Delays updates to the URL due to a Chrome thumbnailer bug
192
+ // Delays updates to the URL due to a Chrome thumbnailer bug
152
193
  writeURLTimeout = 0,
153
194
 
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
- // Flags if the interaction event listeners are bound
195
+ // Flags if the interaction event listeners are bound
161
196
  eventsAreBound = false,
162
197
 
163
- // The current auto-slide duration
198
+ // The current auto-slide duration
164
199
  autoSlide = 0,
165
200
 
166
- // Auto slide properties
201
+ // Auto slide properties
167
202
  autoSlidePlayer,
168
203
  autoSlideTimeout = 0,
169
204
  autoSlideStartTime = -1,
170
205
  autoSlidePaused = false,
171
206
 
172
- // Holds information about the currently ongoing touch input
207
+ // Holds information about the currently ongoing touch input
173
208
  touch = {
174
209
  startX: 0,
175
210
  startY: 0,
@@ -177,6 +212,21 @@ var Reveal = (function(){
177
212
  startCount: 0,
178
213
  captured: false,
179
214
  threshold: 40
215
+ },
216
+
217
+ // Holds information about the keyboard shortcuts
218
+ keyboardShortcuts = {
219
+ 'N , SPACE': 'Next slide',
220
+ 'P': 'Previous slide',
221
+ '← , H': 'Navigate left',
222
+ '→ , L': 'Navigate right',
223
+ '↑ , K': 'Navigate up',
224
+ '↓ , J': 'Navigate down',
225
+ 'Home': 'First slide',
226
+ 'End': 'Last slide',
227
+ 'B , .': 'Pause',
228
+ 'F': 'Fullscreen',
229
+ 'ESC, O': 'Slide overview'
180
230
  };
181
231
 
182
232
  /**
@@ -189,11 +239,30 @@ var Reveal = (function(){
189
239
  if( !features.transforms2d && !features.transforms3d ) {
190
240
  document.body.setAttribute( 'class', 'no-transforms' );
191
241
 
242
+ // Since JS won't be running any further, we load all lazy
243
+ // loading elements upfront
244
+ var images = toArray( document.getElementsByTagName( 'img' ) ),
245
+ iframes = toArray( document.getElementsByTagName( 'iframe' ) );
246
+
247
+ var lazyLoadable = images.concat( iframes );
248
+
249
+ for( var i = 0, len = lazyLoadable.length; i < len; i++ ) {
250
+ var element = lazyLoadable[i];
251
+ if( element.getAttribute( 'data-src' ) ) {
252
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
253
+ element.removeAttribute( 'data-src' );
254
+ }
255
+ }
256
+
192
257
  // If the browser doesn't support core features we won't be
193
258
  // using JavaScript to control the presentation
194
259
  return;
195
260
  }
196
261
 
262
+ // Cache references to key DOM elements
263
+ dom.wrapper = document.querySelector( '.reveal' );
264
+ dom.slides = document.querySelector( '.reveal .slides' );
265
+
197
266
  // Force a layout when the whole page, incl fonts, has loaded
198
267
  window.addEventListener( 'load', layout, false );
199
268
 
@@ -222,34 +291,39 @@ var Reveal = (function(){
222
291
  function checkCapabilities() {
223
292
 
224
293
  features.transforms3d = 'WebkitPerspective' in document.body.style ||
225
- 'MozPerspective' in document.body.style ||
226
- 'msPerspective' in document.body.style ||
227
- 'OPerspective' in document.body.style ||
228
- 'perspective' in document.body.style;
294
+ 'MozPerspective' in document.body.style ||
295
+ 'msPerspective' in document.body.style ||
296
+ 'OPerspective' in document.body.style ||
297
+ 'perspective' in document.body.style;
229
298
 
230
299
  features.transforms2d = 'WebkitTransform' in document.body.style ||
231
- 'MozTransform' in document.body.style ||
232
- 'msTransform' in document.body.style ||
233
- 'OTransform' in document.body.style ||
234
- 'transform' in document.body.style;
300
+ 'MozTransform' in document.body.style ||
301
+ 'msTransform' in document.body.style ||
302
+ 'OTransform' in document.body.style ||
303
+ 'transform' in document.body.style;
235
304
 
236
305
  features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
237
306
  features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
238
307
 
239
308
  features.canvas = !!document.createElement( 'canvas' ).getContext;
240
309
 
241
- isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
310
+ features.touch = !!( 'ontouchstart' in window );
242
311
 
243
- }
312
+ // Transitions in the overview are disabled in desktop and
313
+ // mobile Safari due to lag
314
+ features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( navigator.userAgent );
244
315
 
316
+ isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( navigator.userAgent );
245
317
 
246
- /**
247
- * Loads the dependencies of reveal.js. Dependencies are
248
- * defined via the configuration option 'dependencies'
249
- * and will be loaded prior to starting/binding reveal.js.
250
- * Some dependencies may have an 'async' flag, if so they
251
- * will load after reveal.js has been started up.
252
- */
318
+ }
319
+
320
+ /**
321
+ * Loads the dependencies of reveal.js. Dependencies are
322
+ * defined via the configuration option 'dependencies'
323
+ * and will be loaded prior to starting/binding reveal.js.
324
+ * Some dependencies may have an 'async' flag, if so they
325
+ * will load after reveal.js has been started up.
326
+ */
253
327
  function load() {
254
328
 
255
329
  var scripts = [],
@@ -316,6 +390,12 @@ var Reveal = (function(){
316
390
  // Make sure we've got all the DOM elements we need
317
391
  setupDOM();
318
392
 
393
+ // Listen to messages posted to this window
394
+ setupPostMessage();
395
+
396
+ // Prevent iframes from scrolling the slides out of view
397
+ setupIframeScrollPrevention();
398
+
319
399
  // Resets all vertical slides so that only the first is visible
320
400
  resetVerticalSlides();
321
401
 
@@ -343,6 +423,20 @@ var Reveal = (function(){
343
423
  } );
344
424
  }, 1 );
345
425
 
426
+ // Special setup and config is required when printing to PDF
427
+ if( isPrintingPDF() ) {
428
+ removeEventListeners();
429
+
430
+ // The document needs to have loaded for the PDF layout
431
+ // measurements to be accurate
432
+ if( document.readyState === 'complete' ) {
433
+ setupPDF();
434
+ }
435
+ else {
436
+ window.addEventListener( 'load', setupPDF );
437
+ }
438
+ }
439
+
346
440
  }
347
441
 
348
442
  /**
@@ -352,11 +446,6 @@ var Reveal = (function(){
352
446
  */
353
447
  function setupDOM() {
354
448
 
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
449
  // Prevent transitions while we're loading
361
450
  dom.slides.classList.add( 'no-transition' );
362
451
 
@@ -377,14 +466,14 @@ var Reveal = (function(){
377
466
  // Slide number
378
467
  dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
468
 
380
- // State background element [DEPRECATED]
381
- createSingletonNode( dom.wrapper, 'div', 'state-background', null );
382
-
383
469
  // Overlay graphic which is displayed during the paused mode
384
470
  createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
385
471
 
386
472
  // Cache references to elements
387
473
  dom.controls = document.querySelector( '.reveal .controls' );
474
+ dom.theme = document.querySelector( '#theme' );
475
+
476
+ dom.wrapper.setAttribute( 'role', 'application' );
388
477
 
389
478
  // There can be multiple instances of controls throughout the page
390
479
  dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
@@ -394,6 +483,120 @@ var Reveal = (function(){
394
483
  dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
484
  dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
396
485
 
486
+ dom.statusDiv = createStatusDiv();
487
+ }
488
+
489
+ /**
490
+ * Creates a hidden div with role aria-live to announce the
491
+ * current slide content. Hide the div off-screen to make it
492
+ * available only to Assistive Technologies.
493
+ */
494
+ function createStatusDiv() {
495
+
496
+ var statusDiv = document.getElementById( 'aria-status-div' );
497
+ if( !statusDiv ) {
498
+ statusDiv = document.createElement( 'div' );
499
+ statusDiv.style.position = 'absolute';
500
+ statusDiv.style.height = '1px';
501
+ statusDiv.style.width = '1px';
502
+ statusDiv.style.overflow ='hidden';
503
+ statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
504
+ statusDiv.setAttribute( 'id', 'aria-status-div' );
505
+ statusDiv.setAttribute( 'aria-live', 'polite' );
506
+ statusDiv.setAttribute( 'aria-atomic','true' );
507
+ dom.wrapper.appendChild( statusDiv );
508
+ }
509
+ return statusDiv;
510
+
511
+ }
512
+
513
+ /**
514
+ * Configures the presentation for printing to a static
515
+ * PDF.
516
+ */
517
+ function setupPDF() {
518
+
519
+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
520
+
521
+ // Dimensions of the PDF pages
522
+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
523
+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
524
+
525
+ // Dimensions of slides within the pages
526
+ var slideWidth = slideSize.width,
527
+ slideHeight = slideSize.height;
528
+
529
+ // Let the browser know what page size we want to print
530
+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' );
531
+
532
+ // Limit the size of certain elements to the dimensions of the slide
533
+ injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
534
+
535
+ document.body.classList.add( 'print-pdf' );
536
+ document.body.style.width = pageWidth + 'px';
537
+ document.body.style.height = pageHeight + 'px';
538
+
539
+ // Slide and slide background layout
540
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
541
+
542
+ // Vertical stacks are not centred since their section
543
+ // children will be
544
+ if( slide.classList.contains( 'stack' ) === false ) {
545
+ // Center the slide inside of the page, giving the slide some margin
546
+ var left = ( pageWidth - slideWidth ) / 2,
547
+ top = ( pageHeight - slideHeight ) / 2;
548
+
549
+ var contentHeight = getAbsoluteHeight( slide );
550
+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
551
+
552
+ // Center slides vertically
553
+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
554
+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
555
+ }
556
+
557
+ // Position the slide inside of the page
558
+ slide.style.left = left + 'px';
559
+ slide.style.top = top + 'px';
560
+ slide.style.width = slideWidth + 'px';
561
+
562
+ // TODO Backgrounds need to be multiplied when the slide
563
+ // stretches over multiple pages
564
+ var background = slide.querySelector( '.slide-background' );
565
+ if( background ) {
566
+ background.style.width = pageWidth + 'px';
567
+ background.style.height = ( pageHeight * numberOfPages ) + 'px';
568
+ background.style.top = -top + 'px';
569
+ background.style.left = -left + 'px';
570
+ }
571
+ }
572
+
573
+ } );
574
+
575
+ // Show all fragments
576
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
577
+ fragment.classList.add( 'visible' );
578
+ } );
579
+
580
+ }
581
+
582
+ /**
583
+ * This is an unfortunate necessity. Iframes can trigger the
584
+ * parent window to scroll, for example by focusing an input.
585
+ * This scrolling can not be prevented by hiding overflow in
586
+ * CSS so we have to resort to repeatedly checking if the
587
+ * browser has decided to offset our slides :(
588
+ */
589
+ function setupIframeScrollPrevention() {
590
+
591
+ if( dom.slides.querySelector( 'iframe' ) ) {
592
+ setInterval( function() {
593
+ if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
594
+ dom.wrapper.scrollTop = 0;
595
+ dom.wrapper.scrollLeft = 0;
596
+ }
597
+ }, 500 );
598
+ }
599
+
397
600
  }
398
601
 
399
602
  /**
@@ -403,15 +606,26 @@ var Reveal = (function(){
403
606
  */
404
607
  function createSingletonNode( container, tagname, classname, innerHTML ) {
405
608
 
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;
609
+ // Find all nodes matching the description
610
+ var nodes = container.querySelectorAll( '.' + classname );
611
+
612
+ // Check all matches to find one which is a direct child of
613
+ // the specified container
614
+ for( var i = 0; i < nodes.length; i++ ) {
615
+ var testNode = nodes[i];
616
+ if( testNode.parentNode === container ) {
617
+ return testNode;
412
618
  }
413
- container.appendChild( node );
414
619
  }
620
+
621
+ // If no node was found, create it now
622
+ var node = document.createElement( tagname );
623
+ node.classList.add( classname );
624
+ if( typeof innerHTML === 'string' ) {
625
+ node.innerHTML = innerHTML;
626
+ }
627
+ container.appendChild( node );
628
+
415
629
  return node;
416
630
 
417
631
  }
@@ -423,81 +637,36 @@ var Reveal = (function(){
423
637
  */
424
638
  function createBackgrounds() {
425
639
 
426
- if( isPrintingPDF() ) {
427
- document.body.classList.add( 'print-pdf' );
428
- }
640
+ var printMode = isPrintingPDF();
429
641
 
430
642
  // Clear prior backgrounds
431
643
  dom.background.innerHTML = '';
432
644
  dom.background.classList.add( 'no-transition' );
433
645
 
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
646
  // Iterate over all horizontal slides
480
- toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
647
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
481
648
 
482
649
  var backgroundStack;
483
650
 
484
- if( isPrintingPDF() ) {
485
- backgroundStack = _createBackground( slideh, slideh );
651
+ if( printMode ) {
652
+ backgroundStack = createBackground( slideh, slideh );
486
653
  }
487
654
  else {
488
- backgroundStack = _createBackground( slideh, dom.background );
655
+ backgroundStack = createBackground( slideh, dom.background );
489
656
  }
490
657
 
491
658
  // Iterate over all vertical slides
492
659
  toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
493
660
 
494
- if( isPrintingPDF() ) {
495
- _createBackground( slidev, slidev );
661
+ if( printMode ) {
662
+ createBackground( slidev, slidev );
496
663
  }
497
664
  else {
498
- _createBackground( slidev, backgroundStack );
665
+ createBackground( slidev, backgroundStack );
499
666
  }
500
667
 
668
+ backgroundStack.classList.add( 'stack' );
669
+
501
670
  } );
502
671
 
503
672
  } );
@@ -526,13 +695,131 @@ var Reveal = (function(){
526
695
 
527
696
  }
528
697
 
698
+ /**
699
+ * Creates a background for the given slide.
700
+ *
701
+ * @param {HTMLElement} slide
702
+ * @param {HTMLElement} container The element that the background
703
+ * should be appended to
704
+ */
705
+ function createBackground( slide, container ) {
706
+
707
+ var data = {
708
+ background: slide.getAttribute( 'data-background' ),
709
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
710
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
711
+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
712
+ backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
713
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
714
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
715
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
716
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
717
+ };
718
+
719
+ var element = document.createElement( 'div' );
720
+
721
+ // Carry over custom classes from the slide to the background
722
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
723
+
724
+ if( data.background ) {
725
+ // Auto-wrap image urls in url(...)
726
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
727
+ slide.setAttribute( 'data-background-image', data.background );
728
+ }
729
+ else {
730
+ element.style.background = data.background;
731
+ }
732
+ }
733
+
734
+ // Create a hash for this combination of background settings.
735
+ // This is used to determine when two slide backgrounds are
736
+ // the same.
737
+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
738
+ element.setAttribute( 'data-background-hash', data.background +
739
+ data.backgroundSize +
740
+ data.backgroundImage +
741
+ data.backgroundVideo +
742
+ data.backgroundIframe +
743
+ data.backgroundColor +
744
+ data.backgroundRepeat +
745
+ data.backgroundPosition +
746
+ data.backgroundTransition );
747
+ }
748
+
749
+ // Additional and optional background properties
750
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
751
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
752
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
753
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
754
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
755
+
756
+ container.appendChild( element );
757
+
758
+ // If backgrounds are being recreated, clear old classes
759
+ slide.classList.remove( 'has-dark-background' );
760
+ slide.classList.remove( 'has-light-background' );
761
+
762
+ // If this slide has a background color, add a class that
763
+ // signals if it is light or dark. If the slide has no background
764
+ // color, no class will be set
765
+ var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
766
+ if( computedBackgroundColor ) {
767
+ var rgb = colorToRgb( computedBackgroundColor );
768
+
769
+ // Ignore fully transparent backgrounds. Some browsers return
770
+ // rgba(0,0,0,0) when reading the computed background color of
771
+ // an element with no background
772
+ if( rgb && rgb.a !== 0 ) {
773
+ if( colorBrightness( computedBackgroundColor ) < 128 ) {
774
+ slide.classList.add( 'has-dark-background' );
775
+ }
776
+ else {
777
+ slide.classList.add( 'has-light-background' );
778
+ }
779
+ }
780
+ }
781
+
782
+ return element;
783
+
784
+ }
785
+
786
+ /**
787
+ * Registers a listener to postMessage events, this makes it
788
+ * possible to call all reveal.js API methods from another
789
+ * window. For example:
790
+ *
791
+ * revealWindow.postMessage( JSON.stringify({
792
+ * method: 'slide',
793
+ * args: [ 2 ]
794
+ * }), '*' );
795
+ */
796
+ function setupPostMessage() {
797
+
798
+ if( config.postMessage ) {
799
+ window.addEventListener( 'message', function ( event ) {
800
+ var data = event.data;
801
+
802
+ // Make sure we're dealing with JSON
803
+ if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
804
+ data = JSON.parse( data );
805
+
806
+ // Check if the requested method can be found
807
+ if( data.method && typeof Reveal[data.method] === 'function' ) {
808
+ Reveal[data.method].apply( Reveal, data.args );
809
+ }
810
+ }
811
+ }, false );
812
+ }
813
+
814
+ }
815
+
529
816
  /**
530
817
  * Applies the configuration settings from the config
531
818
  * object. May be called multiple times.
532
819
  */
533
820
  function configure( options ) {
534
821
 
535
- var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
822
+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
536
823
 
537
824
  dom.wrapper.classList.remove( config.transition );
538
825
 
@@ -565,6 +852,11 @@ var Reveal = (function(){
565
852
  dom.wrapper.classList.remove( 'center' );
566
853
  }
567
854
 
855
+ // Exit the paused mode if it was configured off
856
+ if( config.pause === false ) {
857
+ resume();
858
+ }
859
+
568
860
  if( config.mouseWheel ) {
569
861
  document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
570
862
  document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
@@ -591,7 +883,13 @@ var Reveal = (function(){
591
883
  enablePreviewLinks( '[data-preview-link]' );
592
884
  }
593
885
 
594
- // Auto-slide playback controls
886
+ // Remove existing auto-slide controls
887
+ if( autoSlidePlayer ) {
888
+ autoSlidePlayer.destroy();
889
+ autoSlidePlayer = null;
890
+ }
891
+
892
+ // Generate auto-slide controls if needed
595
893
  if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
894
  autoSlidePlayer = new Playback( dom.wrapper, function() {
597
895
  return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
@@ -600,21 +898,13 @@ var Reveal = (function(){
600
898
  autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
899
  autoSlidePaused = false;
602
900
  }
603
- else if( autoSlidePlayer ) {
604
- autoSlidePlayer.destroy();
605
- autoSlidePlayer = null;
606
- }
607
901
 
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
- }
902
+ // When fragments are turned off they should be visible
903
+ if( config.fragments === false ) {
904
+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
905
+ element.classList.add( 'visible' );
906
+ element.classList.remove( 'current-fragment' );
907
+ } );
618
908
  }
619
909
 
620
910
  sync();
@@ -637,7 +927,14 @@ var Reveal = (function(){
637
927
  dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
638
928
 
639
929
  // Support pointer-style touch interaction as well
640
- if( window.navigator.msPointerEnabled ) {
930
+ if( window.navigator.pointerEnabled ) {
931
+ // IE 11 uses un-prefixed version of pointer events
932
+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
933
+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
934
+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
935
+ }
936
+ else if( window.navigator.msPointerEnabled ) {
937
+ // IE 10 uses prefixed version of pointer events
641
938
  dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
642
939
  dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
643
940
  dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
@@ -646,13 +943,14 @@ var Reveal = (function(){
646
943
 
647
944
  if( config.keyboard ) {
648
945
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
946
+ document.addEventListener( 'keypress', onDocumentKeyPress, false );
649
947
  }
650
948
 
651
949
  if( config.progress && dom.progress ) {
652
950
  dom.progress.addEventListener( 'click', onProgressClicked, false );
653
951
  }
654
952
 
655
- if( config.focusBodyOnPageVisiblityChange ) {
953
+ if( config.focusBodyOnPageVisibilityChange ) {
656
954
  var visibilityChange;
657
955
 
658
956
  if( 'hidden' in document ) {
@@ -670,7 +968,17 @@ var Reveal = (function(){
670
968
  }
671
969
  }
672
970
 
673
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
971
+ // Listen to both touch and click events, in case the device
972
+ // supports both
973
+ var pointerEvents = [ 'touchstart', 'click' ];
974
+
975
+ // Only support touch for Android, fixes double navigations in
976
+ // stock browser
977
+ if( navigator.userAgent.match( /android/gi ) ) {
978
+ pointerEvents = [ 'touchstart' ];
979
+ }
980
+
981
+ pointerEvents.forEach( function( eventName ) {
674
982
  dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
983
  dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
984
  dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
@@ -689,6 +997,7 @@ var Reveal = (function(){
689
997
  eventsAreBound = false;
690
998
 
691
999
  document.removeEventListener( 'keydown', onDocumentKeyDown, false );
1000
+ document.removeEventListener( 'keypress', onDocumentKeyPress, false );
692
1001
  window.removeEventListener( 'hashchange', onWindowHashChange, false );
693
1002
  window.removeEventListener( 'resize', onWindowResize, false );
694
1003
 
@@ -696,7 +1005,14 @@ var Reveal = (function(){
696
1005
  dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
697
1006
  dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
698
1007
 
699
- if( window.navigator.msPointerEnabled ) {
1008
+ // IE11
1009
+ if( window.navigator.pointerEnabled ) {
1010
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
1011
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
1012
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
1013
+ }
1014
+ // IE10
1015
+ else if( window.navigator.msPointerEnabled ) {
700
1016
  dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
701
1017
  dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
702
1018
  dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
@@ -738,6 +1054,22 @@ var Reveal = (function(){
738
1054
 
739
1055
  }
740
1056
 
1057
+ /**
1058
+ * Utility for deserializing a value.
1059
+ */
1060
+ function deserialize( value ) {
1061
+
1062
+ if( typeof value === 'string' ) {
1063
+ if( value === 'null' ) return null;
1064
+ else if( value === 'true' ) return true;
1065
+ else if( value === 'false' ) return false;
1066
+ else if( value.match( /^\d+$/ ) ) return parseFloat( value );
1067
+ }
1068
+
1069
+ return value;
1070
+
1071
+ }
1072
+
741
1073
  /**
742
1074
  * Measures the distance in pixels between point a
743
1075
  * and point b.
@@ -762,11 +1094,119 @@ var Reveal = (function(){
762
1094
  element.style.WebkitTransform = transform;
763
1095
  element.style.MozTransform = transform;
764
1096
  element.style.msTransform = transform;
765
- element.style.OTransform = transform;
766
1097
  element.style.transform = transform;
767
1098
 
768
1099
  }
769
1100
 
1101
+ /**
1102
+ * Applies CSS transforms to the slides container. The container
1103
+ * is transformed from two separate sources: layout and the overview
1104
+ * mode.
1105
+ */
1106
+ function transformSlides( transforms ) {
1107
+
1108
+ // Pick up new transforms from arguments
1109
+ if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;
1110
+ if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;
1111
+
1112
+ // Apply the transforms to the slides container
1113
+ if( slidesTransform.layout ) {
1114
+ transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );
1115
+ }
1116
+ else {
1117
+ transformElement( dom.slides, slidesTransform.overview );
1118
+ }
1119
+
1120
+ }
1121
+
1122
+ /**
1123
+ * Injects the given CSS styles into the DOM.
1124
+ */
1125
+ function injectStyleSheet( value ) {
1126
+
1127
+ var tag = document.createElement( 'style' );
1128
+ tag.type = 'text/css';
1129
+ if( tag.styleSheet ) {
1130
+ tag.styleSheet.cssText = value;
1131
+ }
1132
+ else {
1133
+ tag.appendChild( document.createTextNode( value ) );
1134
+ }
1135
+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
1136
+
1137
+ }
1138
+
1139
+ /**
1140
+ * Converts various color input formats to an {r:0,g:0,b:0} object.
1141
+ *
1142
+ * @param {String} color The string representation of a color,
1143
+ * the following formats are supported:
1144
+ * - #000
1145
+ * - #000000
1146
+ * - rgb(0,0,0)
1147
+ */
1148
+ function colorToRgb( color ) {
1149
+
1150
+ var hex3 = color.match( /^#([0-9a-f]{3})$/i );
1151
+ if( hex3 && hex3[1] ) {
1152
+ hex3 = hex3[1];
1153
+ return {
1154
+ r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
1155
+ g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
1156
+ b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
1157
+ };
1158
+ }
1159
+
1160
+ var hex6 = color.match( /^#([0-9a-f]{6})$/i );
1161
+ if( hex6 && hex6[1] ) {
1162
+ hex6 = hex6[1];
1163
+ return {
1164
+ r: parseInt( hex6.substr( 0, 2 ), 16 ),
1165
+ g: parseInt( hex6.substr( 2, 2 ), 16 ),
1166
+ b: parseInt( hex6.substr( 4, 2 ), 16 )
1167
+ };
1168
+ }
1169
+
1170
+ var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
1171
+ if( rgb ) {
1172
+ return {
1173
+ r: parseInt( rgb[1], 10 ),
1174
+ g: parseInt( rgb[2], 10 ),
1175
+ b: parseInt( rgb[3], 10 )
1176
+ };
1177
+ }
1178
+
1179
+ var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
1180
+ if( rgba ) {
1181
+ return {
1182
+ r: parseInt( rgba[1], 10 ),
1183
+ g: parseInt( rgba[2], 10 ),
1184
+ b: parseInt( rgba[3], 10 ),
1185
+ a: parseFloat( rgba[4] )
1186
+ };
1187
+ }
1188
+
1189
+ return null;
1190
+
1191
+ }
1192
+
1193
+ /**
1194
+ * Calculates brightness on a scale of 0-255.
1195
+ *
1196
+ * @param color See colorStringToRgb for supported formats.
1197
+ */
1198
+ function colorBrightness( color ) {
1199
+
1200
+ if( typeof color === 'string' ) color = colorToRgb( color );
1201
+
1202
+ if( color ) {
1203
+ return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
1204
+ }
1205
+
1206
+ return null;
1207
+
1208
+ }
1209
+
770
1210
  /**
771
1211
  * Retrieves the height of the given element by looking
772
1212
  * at the position and height of its immediate children.
@@ -782,7 +1222,7 @@ var Reveal = (function(){
782
1222
 
783
1223
  if( typeof child.offsetTop === 'number' && child.style ) {
784
1224
  // Count # of abs children
785
- if( child.style.position === 'absolute' ) {
1225
+ if( window.getComputedStyle( child ).position === 'absolute' ) {
786
1226
  absoluteChildren += 1;
787
1227
  }
788
1228
 
@@ -804,40 +1244,26 @@ var Reveal = (function(){
804
1244
 
805
1245
  /**
806
1246
  * Returns the remaining height within the parent of the
807
- * target element after subtracting the height of all
808
- * siblings.
1247
+ * target element.
809
1248
  *
810
- * remaining height = [parent height] - [ siblings height]
1249
+ * remaining height = [ configured parent height ] - [ current parent height ]
811
1250
  */
812
1251
  function getRemainingHeight( element, height ) {
813
1252
 
814
1253
  height = height || 0;
815
1254
 
816
1255
  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 ) {
1256
+ var newHeight, oldHeight = element.style.height;
824
1257
 
825
- var styles = window.getComputedStyle( sibling ),
826
- marginTop = parseInt( styles.marginTop, 10 ),
827
- marginBottom = parseInt( styles.marginBottom, 10 );
1258
+ // Change the .stretch element height to 0 in order find the height of all
1259
+ // the other elements
1260
+ element.style.height = '0px';
1261
+ newHeight = height - element.parentNode.offsetHeight;
828
1262
 
829
- height -= sibling.offsetHeight + marginTop + marginBottom;
830
-
831
- }
832
-
833
- } );
834
-
835
- var elementStyles = window.getComputedStyle( element );
836
-
837
- // Subtract the margins of the target element
838
- height -= parseInt( elementStyles.marginTop, 10 ) +
839
- parseInt( elementStyles.marginBottom, 10 );
1263
+ // Restore the old height, just in case
1264
+ element.style.height = oldHeight + 'px';
840
1265
 
1266
+ return newHeight;
841
1267
  }
842
1268
 
843
1269
  return height;
@@ -882,13 +1308,19 @@ var Reveal = (function(){
882
1308
  * Dispatches an event of the specified type from the
883
1309
  * reveal DOM element.
884
1310
  */
885
- function dispatchEvent( type, properties ) {
1311
+ function dispatchEvent( type, args ) {
886
1312
 
887
- var event = document.createEvent( "HTMLEvents", 1, 2 );
1313
+ var event = document.createEvent( 'HTMLEvents', 1, 2 );
888
1314
  event.initEvent( type, true, true );
889
- extend( event, properties );
1315
+ extend( event, args );
890
1316
  dom.wrapper.dispatchEvent( event );
891
1317
 
1318
+ // If we're in an iframe, post each reveal.js event to the
1319
+ // parent window. Used by the notes plugin
1320
+ if( config.postMessageEvents && window.parent !== window.self ) {
1321
+ window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
1322
+ }
1323
+
892
1324
  }
893
1325
 
894
1326
  /**
@@ -897,7 +1329,7 @@ var Reveal = (function(){
897
1329
  function enableRollingLinks() {
898
1330
 
899
1331
  if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
900
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
1332
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
901
1333
 
902
1334
  for( var i = 0, len = anchors.length; i < len; i++ ) {
903
1335
  var anchor = anchors[i];
@@ -921,7 +1353,7 @@ var Reveal = (function(){
921
1353
  */
922
1354
  function disableRollingLinks() {
923
1355
 
924
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
1356
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
925
1357
 
926
1358
  for( var i = 0, len = anchors.length; i < len; i++ ) {
927
1359
  var anchor = anchors[i];
@@ -968,53 +1400,98 @@ var Reveal = (function(){
968
1400
  /**
969
1401
  * Opens a preview window for the target URL.
970
1402
  */
971
- function openPreview( url ) {
1403
+ function showPreview( url ) {
972
1404
 
973
- closePreview();
1405
+ closeOverlay();
974
1406
 
975
- dom.preview = document.createElement( 'div' );
976
- dom.preview.classList.add( 'preview-link-overlay' );
977
- dom.wrapper.appendChild( dom.preview );
1407
+ dom.overlay = document.createElement( 'div' );
1408
+ dom.overlay.classList.add( 'overlay' );
1409
+ dom.overlay.classList.add( 'overlay-preview' );
1410
+ dom.wrapper.appendChild( dom.overlay );
978
1411
 
979
- dom.preview.innerHTML = [
1412
+ dom.overlay.innerHTML = [
980
1413
  '<header>',
981
- '<a class="close" href="#"><span class="icon"></span></a>',
982
- '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
1414
+ '<a class="close" href="#"><span class="icon"></span></a>',
1415
+ '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
983
1416
  '</header>',
984
1417
  '<div class="spinner"></div>',
985
1418
  '<div class="viewport">',
986
- '<iframe src="'+ url +'"></iframe>',
1419
+ '<iframe src="'+ url +'"></iframe>',
987
1420
  '</div>'
988
1421
  ].join('');
989
1422
 
990
- dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
991
- dom.preview.classList.add( 'loaded' );
1423
+ dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
1424
+ dom.overlay.classList.add( 'loaded' );
992
1425
  }, false );
993
1426
 
994
- dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
995
- closePreview();
1427
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1428
+ closeOverlay();
996
1429
  event.preventDefault();
997
1430
  }, false );
998
1431
 
999
- dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1000
- closePreview();
1432
+ dom.overlay.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1433
+ closeOverlay();
1001
1434
  }, false );
1002
1435
 
1003
1436
  setTimeout( function() {
1004
- dom.preview.classList.add( 'visible' );
1437
+ dom.overlay.classList.add( 'visible' );
1005
1438
  }, 1 );
1006
1439
 
1007
1440
  }
1008
1441
 
1009
1442
  /**
1010
- * Closes the iframe preview window.
1443
+ * Opens a overlay window with help material.
1011
1444
  */
1012
- function closePreview() {
1445
+ function showHelp() {
1446
+
1447
+ if( config.help ) {
1448
+
1449
+ closeOverlay();
1450
+
1451
+ dom.overlay = document.createElement( 'div' );
1452
+ dom.overlay.classList.add( 'overlay' );
1453
+ dom.overlay.classList.add( 'overlay-help' );
1454
+ dom.wrapper.appendChild( dom.overlay );
1455
+
1456
+ var html = '<p class="title">Keyboard Shortcuts</p><br/>';
1457
+
1458
+ html += '<table><th>KEY</th><th>ACTION</th>';
1459
+ for( var key in keyboardShortcuts ) {
1460
+ html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
1461
+ }
1462
+
1463
+ html += '</table>';
1464
+
1465
+ dom.overlay.innerHTML = [
1466
+ '<header>',
1467
+ '<a class="close" href="#"><span class="icon"></span></a>',
1468
+ '</header>',
1469
+ '<div class="viewport">',
1470
+ '<div class="viewport-inner">'+ html +'</div>',
1471
+ '</div>'
1472
+ ].join('');
1473
+
1474
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1475
+ closeOverlay();
1476
+ event.preventDefault();
1477
+ }, false );
1478
+
1479
+ setTimeout( function() {
1480
+ dom.overlay.classList.add( 'visible' );
1481
+ }, 1 );
1013
1482
 
1014
- if( dom.preview ) {
1015
- dom.preview.setAttribute( 'src', '' );
1016
- dom.preview.parentNode.removeChild( dom.preview );
1017
- dom.preview = null;
1483
+ }
1484
+
1485
+ }
1486
+
1487
+ /**
1488
+ * Closes any currently open overlay.
1489
+ */
1490
+ function closeOverlay() {
1491
+
1492
+ if( dom.overlay ) {
1493
+ dom.overlay.parentNode.removeChild( dom.overlay );
1494
+ dom.overlay = null;
1018
1495
  }
1019
1496
 
1020
1497
  }
@@ -1027,54 +1504,50 @@ var Reveal = (function(){
1027
1504
 
1028
1505
  if( dom.wrapper && !isPrintingPDF() ) {
1029
1506
 
1030
- // Available space to scale within
1031
- var availableWidth = dom.wrapper.offsetWidth,
1032
- availableHeight = dom.wrapper.offsetHeight;
1507
+ var size = getComputedSlideSize();
1033
1508
 
1034
- // Reduce available space by margin
1035
- availableWidth -= ( availableHeight * config.margin );
1036
- availableHeight -= ( availableHeight * config.margin );
1037
-
1038
- // Dimensions of the content
1039
- var slideWidth = config.width,
1040
- slideHeight = config.height,
1041
- slidePadding = 20; // TODO Dig this out of DOM
1509
+ var slidePadding = 20; // TODO Dig this out of DOM
1042
1510
 
1043
1511
  // Layout the contents of the slides
1044
1512
  layoutSlideContents( config.width, config.height, slidePadding );
1045
1513
 
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';
1514
+ dom.slides.style.width = size.width + 'px';
1515
+ dom.slides.style.height = size.height + 'px';
1058
1516
 
1059
1517
  // Determine scale of content to fit within available space
1060
- scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
1518
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
1061
1519
 
1062
1520
  // Respect max/min scale settings
1063
1521
  scale = Math.max( scale, config.minScale );
1064
1522
  scale = Math.min( scale, config.maxScale );
1065
1523
 
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;
1524
+ // Don't apply any scaling styles if scale is 1
1525
+ if( scale === 1 ) {
1526
+ dom.slides.style.zoom = '';
1527
+ dom.slides.style.left = '';
1528
+ dom.slides.style.top = '';
1529
+ dom.slides.style.bottom = '';
1530
+ dom.slides.style.right = '';
1531
+ transformSlides( { layout: '' } );
1070
1532
  }
1071
- // Apply scale transform as a fallback
1072
1533
  else {
1073
- transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
1534
+ // Prefer zooming in desktop Chrome so that content remains crisp
1535
+ if( !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
1536
+ dom.slides.style.zoom = scale;
1537
+ transformSlides( { layout: '' } );
1538
+ }
1539
+ // Apply scale transform as a fallback
1540
+ else {
1541
+ dom.slides.style.left = '50%';
1542
+ dom.slides.style.top = '50%';
1543
+ dom.slides.style.bottom = 'auto';
1544
+ dom.slides.style.right = 'auto';
1545
+ transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
1546
+ }
1074
1547
  }
1075
1548
 
1076
1549
  // Select all slides, vertical and horizontal
1077
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1550
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
1078
1551
 
1079
1552
  for( var i = 0, len = slides.length; i < len; i++ ) {
1080
1553
  var slide = slides[ i ];
@@ -1091,7 +1564,7 @@ var Reveal = (function(){
1091
1564
  slide.style.top = 0;
1092
1565
  }
1093
1566
  else {
1094
- slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
1567
+ slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
1095
1568
  }
1096
1569
  }
1097
1570
  else {
@@ -1117,7 +1590,7 @@ var Reveal = (function(){
1117
1590
  toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1118
1591
 
1119
1592
  // Determine how much vertical space we can use
1120
- var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1593
+ var remainingHeight = getRemainingHeight( element, height );
1121
1594
 
1122
1595
  // Consider the aspect ratio of media elements
1123
1596
  if( /(img|video)/gi.test( element.nodeName ) ) {
@@ -1139,6 +1612,41 @@ var Reveal = (function(){
1139
1612
 
1140
1613
  }
1141
1614
 
1615
+ /**
1616
+ * Calculates the computed pixel size of our slides. These
1617
+ * values are based on the width and height configuration
1618
+ * options.
1619
+ */
1620
+ function getComputedSlideSize( presentationWidth, presentationHeight ) {
1621
+
1622
+ var size = {
1623
+ // Slide size
1624
+ width: config.width,
1625
+ height: config.height,
1626
+
1627
+ // Presentation size
1628
+ presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
1629
+ presentationHeight: presentationHeight || dom.wrapper.offsetHeight
1630
+ };
1631
+
1632
+ // Reduce available space by margin
1633
+ size.presentationWidth -= ( size.presentationWidth * config.margin );
1634
+ size.presentationHeight -= ( size.presentationHeight * config.margin );
1635
+
1636
+ // Slide width may be a percentage of available width
1637
+ if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
1638
+ size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
1639
+ }
1640
+
1641
+ // Slide height may be a percentage of available height
1642
+ if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
1643
+ size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
1644
+ }
1645
+
1646
+ return size;
1647
+
1648
+ }
1649
+
1142
1650
  /**
1143
1651
  * Stores the vertical index of a stack so that the same
1144
1652
  * vertical slide can be selected when navigating to and
@@ -1176,92 +1684,122 @@ var Reveal = (function(){
1176
1684
  }
1177
1685
 
1178
1686
  /**
1179
- * Displays the overview of slides (quick nav) by
1180
- * scaling down and arranging all slide elements.
1181
- *
1182
- * Experimental feature, might be dropped if perf
1183
- * can't be improved.
1687
+ * Displays the overview of slides (quick nav) by scaling
1688
+ * down and arranging all slide elements.
1184
1689
  */
1185
1690
  function activateOverview() {
1186
1691
 
1187
1692
  // Only proceed if enabled in config
1188
- if( config.overview ) {
1693
+ if( config.overview && !isOverview() ) {
1189
1694
 
1190
- // Don't auto-slide while in overview mode
1191
- cancelAutoSlide();
1192
-
1193
- var wasActive = dom.wrapper.classList.contains( 'overview' );
1194
-
1195
- // Vary the depth of the overview based on screen size
1196
- var depth = window.innerWidth < 400 ? 1000 : 2500;
1695
+ overview = true;
1197
1696
 
1198
1697
  dom.wrapper.classList.add( 'overview' );
1199
1698
  dom.wrapper.classList.remove( 'overview-deactivating' );
1200
1699
 
1201
- clearTimeout( activateOverviewTimeout );
1202
- clearTimeout( deactivateOverviewTimeout );
1700
+ if( features.overviewTransitions ) {
1701
+ setTimeout( function() {
1702
+ dom.wrapper.classList.add( 'overview-animated' );
1703
+ }, 1 );
1704
+ }
1203
1705
 
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() {
1706
+ // Don't auto-slide while in overview mode
1707
+ cancelAutoSlide();
1208
1708
 
1209
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1709
+ // Move the backgrounds element into the slide container to
1710
+ // that the same scaling is applied
1711
+ dom.slides.appendChild( dom.background );
1210
1712
 
1211
- for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1212
- var hslide = horizontalSlides[i],
1213
- hoffset = config.rtl ? -105 : 105;
1713
+ // Clicking on an overview slide navigates to it
1714
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1715
+ if( !slide.classList.contains( 'stack' ) ) {
1716
+ slide.addEventListener( 'click', onOverviewSlideClicked, true );
1717
+ }
1718
+ } );
1214
1719
 
1215
- hslide.setAttribute( 'data-index-h', i );
1720
+ updateSlidesVisibility();
1721
+ layoutOverview();
1722
+ updateOverview();
1216
1723
 
1217
- // Apply CSS transform
1218
- transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1724
+ layout();
1219
1725
 
1220
- if( hslide.classList.contains( 'stack' ) ) {
1726
+ // Notify observers of the overview showing
1727
+ dispatchEvent( 'overviewshown', {
1728
+ 'indexh': indexh,
1729
+ 'indexv': indexv,
1730
+ 'currentSlide': currentSlide
1731
+ } );
1221
1732
 
1222
- var verticalSlides = hslide.querySelectorAll( 'section' );
1733
+ }
1223
1734
 
1224
- for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1225
- var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1735
+ }
1226
1736
 
1227
- var vslide = verticalSlides[j];
1737
+ /**
1738
+ * Uses CSS transforms to position all slides in a grid for
1739
+ * display inside of the overview mode.
1740
+ */
1741
+ function layoutOverview() {
1228
1742
 
1229
- vslide.setAttribute( 'data-index-h', i );
1230
- vslide.setAttribute( 'data-index-v', j );
1743
+ var margin = 70;
1744
+ var slideWidth = config.width + margin,
1745
+ slideHeight = config.height + margin;
1231
1746
 
1232
- // Apply CSS transform
1233
- transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1747
+ // Reverse in RTL mode
1748
+ if( config.rtl ) {
1749
+ slideWidth = -slideWidth;
1750
+ }
1234
1751
 
1235
- // Navigate to this slide on click
1236
- vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1237
- }
1752
+ // Layout slides
1753
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
1754
+ hslide.setAttribute( 'data-index-h', h );
1755
+ transformElement( hslide, 'translate3d(' + ( h * slideWidth ) + 'px, 0, 0)' );
1238
1756
 
1239
- }
1240
- else {
1757
+ if( hslide.classList.contains( 'stack' ) ) {
1241
1758
 
1242
- // Navigate to this slide on click
1243
- hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1759
+ toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
1760
+ vslide.setAttribute( 'data-index-h', h );
1761
+ vslide.setAttribute( 'data-index-v', v );
1244
1762
 
1245
- }
1246
- }
1763
+ transformElement( vslide, 'translate3d(0, ' + ( v * slideHeight ) + 'px, 0)' );
1764
+ } );
1247
1765
 
1248
- updateSlidesVisibility();
1766
+ }
1767
+ } );
1249
1768
 
1250
- layout();
1769
+ // Layout slide backgrounds
1770
+ toArray( dom.background.childNodes ).forEach( function( hbackground, h ) {
1771
+ transformElement( hbackground, 'translate3d(' + ( h * slideWidth ) + 'px, 0, 0)' );
1251
1772
 
1252
- if( !wasActive ) {
1253
- // Notify observers of the overview showing
1254
- dispatchEvent( 'overviewshown', {
1255
- 'indexh': indexh,
1256
- 'indexv': indexv,
1257
- 'currentSlide': currentSlide
1258
- } );
1259
- }
1773
+ toArray( hbackground.querySelectorAll( '.slide-background' ) ).forEach( function( vbackground, v ) {
1774
+ transformElement( vbackground, 'translate3d(0, ' + ( v * slideHeight ) + 'px, 0)' );
1775
+ } );
1776
+ } );
1260
1777
 
1261
- }, 10 );
1778
+ }
1262
1779
 
1780
+ /**
1781
+ * Moves the overview viewport to the current slides.
1782
+ * Called each time the current slide changes.
1783
+ */
1784
+ function updateOverview() {
1785
+
1786
+ var margin = 70;
1787
+ var slideWidth = config.width + margin,
1788
+ slideHeight = config.height + margin;
1789
+
1790
+ // Reverse in RTL mode
1791
+ if( config.rtl ) {
1792
+ slideWidth = -slideWidth;
1263
1793
  }
1264
1794
 
1795
+ transformSlides( {
1796
+ overview: [
1797
+ 'translateX('+ ( -indexh * slideWidth ) +'px)',
1798
+ 'translateY('+ ( -indexv * slideHeight ) +'px)',
1799
+ 'translateZ('+ ( window.innerWidth < 400 ? -1000 : -2500 ) +'px)'
1800
+ ].join( ' ' )
1801
+ } );
1802
+
1265
1803
  }
1266
1804
 
1267
1805
  /**
@@ -1273,30 +1811,41 @@ var Reveal = (function(){
1273
1811
  // Only proceed if enabled in config
1274
1812
  if( config.overview ) {
1275
1813
 
1276
- clearTimeout( activateOverviewTimeout );
1277
- clearTimeout( deactivateOverviewTimeout );
1814
+ overview = false;
1278
1815
 
1279
1816
  dom.wrapper.classList.remove( 'overview' );
1817
+ dom.wrapper.classList.remove( 'overview-animated' );
1280
1818
 
1281
1819
  // Temporarily add a class so that transitions can do different things
1282
1820
  // depending on whether they are exiting/entering overview, or just
1283
1821
  // moving from slide to slide
1284
1822
  dom.wrapper.classList.add( 'overview-deactivating' );
1285
1823
 
1286
- deactivateOverviewTimeout = setTimeout( function () {
1824
+ setTimeout( function () {
1287
1825
  dom.wrapper.classList.remove( 'overview-deactivating' );
1288
1826
  }, 1 );
1289
1827
 
1290
- // Select all slides
1291
- toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1292
- // Resets all transforms to use the external styles
1828
+ // Move the background element back out
1829
+ dom.wrapper.appendChild( dom.background );
1830
+
1831
+ // Clean up changes made to slides
1832
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1293
1833
  transformElement( slide, '' );
1294
1834
 
1295
1835
  slide.removeEventListener( 'click', onOverviewSlideClicked, true );
1296
1836
  } );
1297
1837
 
1838
+ // Clean up changes made to backgrounds
1839
+ toArray( dom.background.querySelectorAll( '.slide-background' ) ).forEach( function( background ) {
1840
+ transformElement( background, '' );
1841
+ } );
1842
+
1843
+ transformSlides( { overview: '' } );
1844
+
1298
1845
  slide( indexh, indexv );
1299
1846
 
1847
+ layout();
1848
+
1300
1849
  cueAutoSlide();
1301
1850
 
1302
1851
  // Notify observers of the overview hiding
@@ -1335,7 +1884,7 @@ var Reveal = (function(){
1335
1884
  */
1336
1885
  function isOverview() {
1337
1886
 
1338
- return dom.wrapper.classList.contains( 'overview' );
1887
+ return overview;
1339
1888
 
1340
1889
  }
1341
1890
 
@@ -1367,10 +1916,10 @@ var Reveal = (function(){
1367
1916
 
1368
1917
  // Check which implementation is available
1369
1918
  var requestMethod = element.requestFullScreen ||
1370
- element.webkitRequestFullscreen ||
1371
- element.webkitRequestFullScreen ||
1372
- element.mozRequestFullScreen ||
1373
- element.msRequestFullScreen;
1919
+ element.webkitRequestFullscreen ||
1920
+ element.webkitRequestFullScreen ||
1921
+ element.mozRequestFullScreen ||
1922
+ element.msRequestFullscreen;
1374
1923
 
1375
1924
  if( requestMethod ) {
1376
1925
  requestMethod.apply( element );
@@ -1384,13 +1933,15 @@ var Reveal = (function(){
1384
1933
  */
1385
1934
  function pause() {
1386
1935
 
1387
- var wasPaused = dom.wrapper.classList.contains( 'paused' );
1936
+ if( config.pause ) {
1937
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1388
1938
 
1389
- cancelAutoSlide();
1390
- dom.wrapper.classList.add( 'paused' );
1939
+ cancelAutoSlide();
1940
+ dom.wrapper.classList.add( 'paused' );
1391
1941
 
1392
- if( wasPaused === false ) {
1393
- dispatchEvent( 'paused' );
1942
+ if( wasPaused === false ) {
1943
+ dispatchEvent( 'paused' );
1944
+ }
1394
1945
  }
1395
1946
 
1396
1947
  }
@@ -1414,13 +1965,13 @@ var Reveal = (function(){
1414
1965
  /**
1415
1966
  * Toggles the paused mode on and off.
1416
1967
  */
1417
- function togglePause() {
1968
+ function togglePause( override ) {
1418
1969
 
1419
- if( isPaused() ) {
1420
- resume();
1970
+ if( typeof override === 'boolean' ) {
1971
+ override ? pause() : resume();
1421
1972
  }
1422
1973
  else {
1423
- pause();
1974
+ isPaused() ? resume() : pause();
1424
1975
  }
1425
1976
 
1426
1977
  }
@@ -1434,6 +1985,34 @@ var Reveal = (function(){
1434
1985
 
1435
1986
  }
1436
1987
 
1988
+ /**
1989
+ * Toggles the auto slide mode on and off.
1990
+ *
1991
+ * @param {Boolean} override Optional flag which sets the desired state.
1992
+ * True means autoplay starts, false means it stops.
1993
+ */
1994
+
1995
+ function toggleAutoSlide( override ) {
1996
+
1997
+ if( typeof override === 'boolean' ) {
1998
+ override ? resumeAutoSlide() : pauseAutoSlide();
1999
+ }
2000
+
2001
+ else {
2002
+ autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
2003
+ }
2004
+
2005
+ }
2006
+
2007
+ /**
2008
+ * Checks if the auto slide mode is currently on.
2009
+ */
2010
+ function isAutoSliding() {
2011
+
2012
+ return !!( autoSlide && !autoSlidePaused );
2013
+
2014
+ }
2015
+
1437
2016
  /**
1438
2017
  * Steps from the current point in the presentation to the
1439
2018
  * slide which matches the specified horizontal and vertical
@@ -1451,11 +2030,11 @@ var Reveal = (function(){
1451
2030
  previousSlide = currentSlide;
1452
2031
 
1453
2032
  // Query all horizontal slides in the deck
1454
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
2033
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1455
2034
 
1456
2035
  // If no vertical index is specified and the upcoming slide is a
1457
2036
  // stack, resume at its previous vertical index
1458
- if( v === undefined ) {
2037
+ if( v === undefined && !isOverview() ) {
1459
2038
  v = getPreviousVerticalIndex( horizontalSlides[ h ] );
1460
2039
  }
1461
2040
 
@@ -1505,9 +2084,9 @@ var Reveal = (function(){
1505
2084
  document.documentElement.classList.remove( stateBefore.pop() );
1506
2085
  }
1507
2086
 
1508
- // If the overview is active, re-activate it to update positions
2087
+ // Update the overview if it's currently active
1509
2088
  if( isOverview() ) {
1510
- activateOverview();
2089
+ updateOverview();
1511
2090
  }
1512
2091
 
1513
2092
  // Find the current horizontal slide and any possible vertical slides
@@ -1544,13 +2123,14 @@ var Reveal = (function(){
1544
2123
  // stacks
1545
2124
  if( previousSlide ) {
1546
2125
  previousSlide.classList.remove( 'present' );
2126
+ previousSlide.setAttribute( 'aria-hidden', 'true' );
1547
2127
 
1548
2128
  // Reset all slides upon navigate to home
1549
2129
  // Issue: #285
1550
- if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
2130
+ if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
1551
2131
  // Launch async task
1552
2132
  setTimeout( function () {
1553
- var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
2133
+ var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
1554
2134
  for( i in slides ) {
1555
2135
  if( slides[i] ) {
1556
2136
  // Reset stack
@@ -1562,11 +2142,14 @@ var Reveal = (function(){
1562
2142
  }
1563
2143
 
1564
2144
  // Handle embedded content
1565
- if( slideChanged ) {
2145
+ if( slideChanged || !previousSlide ) {
1566
2146
  stopEmbeddedContent( previousSlide );
1567
2147
  startEmbeddedContent( currentSlide );
1568
2148
  }
1569
2149
 
2150
+ // Announce the current slide contents, for screen readers
2151
+ dom.statusDiv.textContent = currentSlide.textContent;
2152
+
1570
2153
  updateControls();
1571
2154
  updateProgress();
1572
2155
  updateBackground();
@@ -1603,12 +2186,23 @@ var Reveal = (function(){
1603
2186
  // Re-create the slide backgrounds
1604
2187
  createBackgrounds();
1605
2188
 
2189
+ // Write the current hash to the URL
2190
+ writeURL();
2191
+
1606
2192
  sortAllFragments();
1607
2193
 
1608
2194
  updateControls();
1609
2195
  updateProgress();
1610
2196
  updateBackground( true );
1611
2197
  updateSlideNumber();
2198
+ updateSlidesVisibility();
2199
+
2200
+ formatEmbeddedContent();
2201
+ startEmbeddedContent( currentSlide );
2202
+
2203
+ if( isOverview() ) {
2204
+ layoutOverview();
2205
+ }
1612
2206
 
1613
2207
  }
1614
2208
 
@@ -1618,7 +2212,7 @@ var Reveal = (function(){
1618
2212
  */
1619
2213
  function resetVerticalSlides() {
1620
2214
 
1621
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2215
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
2216
  horizontalSlides.forEach( function( horizontalSlide ) {
1623
2217
 
1624
2218
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1628,6 +2222,7 @@ var Reveal = (function(){
1628
2222
  verticalSlide.classList.remove( 'present' );
1629
2223
  verticalSlide.classList.remove( 'past' );
1630
2224
  verticalSlide.classList.add( 'future' );
2225
+ verticalSlide.setAttribute( 'aria-hidden', 'true' );
1631
2226
  }
1632
2227
 
1633
2228
  } );
@@ -1642,7 +2237,7 @@ var Reveal = (function(){
1642
2237
  */
1643
2238
  function sortAllFragments() {
1644
2239
 
1645
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2240
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
2241
  horizontalSlides.forEach( function( horizontalSlide ) {
1647
2242
 
1648
2243
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1675,9 +2270,11 @@ var Reveal = (function(){
1675
2270
 
1676
2271
  // Select all slides and convert the NodeList result to
1677
2272
  // an array
1678
- var slides = toArray( document.querySelectorAll( selector ) ),
2273
+ var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),
1679
2274
  slidesLength = slides.length;
1680
2275
 
2276
+ var printMode = isPrintingPDF();
2277
+
1681
2278
  if( slidesLength ) {
1682
2279
 
1683
2280
  // Should the index loop?
@@ -1703,43 +2300,55 @@ var Reveal = (function(){
1703
2300
 
1704
2301
  // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1705
2302
  element.setAttribute( 'hidden', '' );
2303
+ element.setAttribute( 'aria-hidden', 'true' );
2304
+
2305
+ // If this element contains vertical slides
2306
+ if( element.querySelector( 'section' ) ) {
2307
+ element.classList.add( 'stack' );
2308
+ }
2309
+
2310
+ // If we're printing static slides, all slides are "present"
2311
+ if( printMode ) {
2312
+ element.classList.add( 'present' );
2313
+ continue;
2314
+ }
1706
2315
 
1707
2316
  if( i < index ) {
1708
2317
  // Any element previous to index is given the 'past' class
1709
2318
  element.classList.add( reverse ? 'future' : 'past' );
1710
2319
 
1711
- var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
2320
+ if( config.fragments ) {
2321
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
2322
 
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' );
2323
+ // Show all fragments on prior slides
2324
+ while( pastFragments.length ) {
2325
+ var pastFragment = pastFragments.pop();
2326
+ pastFragment.classList.add( 'visible' );
2327
+ pastFragment.classList.remove( 'current-fragment' );
2328
+ }
1718
2329
  }
1719
2330
  }
1720
2331
  else if( i > index ) {
1721
2332
  // Any element subsequent to index is given the 'future' class
1722
2333
  element.classList.add( reverse ? 'past' : 'future' );
1723
2334
 
1724
- var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
2335
+ if( config.fragments ) {
2336
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1725
2337
 
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' );
2338
+ // No fragments in future slides should be visible ahead of time
2339
+ while( futureFragments.length ) {
2340
+ var futureFragment = futureFragments.pop();
2341
+ futureFragment.classList.remove( 'visible' );
2342
+ futureFragment.classList.remove( 'current-fragment' );
2343
+ }
1731
2344
  }
1732
2345
  }
1733
-
1734
- // If this element contains vertical slides
1735
- if( element.querySelector( 'section' ) ) {
1736
- element.classList.add( 'stack' );
1737
- }
1738
2346
  }
1739
2347
 
1740
2348
  // Mark the current slide as present
1741
2349
  slides[index].classList.add( 'present' );
1742
2350
  slides[index].removeAttribute( 'hidden' );
2351
+ slides[index].removeAttribute( 'aria-hidden' );
1743
2352
 
1744
2353
  // If this slide has a state associated with it, add it
1745
2354
  // onto the current state of the deck
@@ -1767,12 +2376,12 @@ var Reveal = (function(){
1767
2376
 
1768
2377
  // Select all slides and convert the NodeList result to
1769
2378
  // an array
1770
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
2379
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1771
2380
  horizontalSlidesLength = horizontalSlides.length,
1772
2381
  distanceX,
1773
2382
  distanceY;
1774
2383
 
1775
- if( horizontalSlidesLength ) {
2384
+ if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
1776
2385
 
1777
2386
  // The number of steps away from the present slide that will
1778
2387
  // be visible
@@ -1780,7 +2389,12 @@ var Reveal = (function(){
1780
2389
 
1781
2390
  // Limit view distance on weaker devices
1782
2391
  if( isMobileDevice ) {
1783
- viewDistance = isOverview() ? 6 : 1;
2392
+ viewDistance = isOverview() ? 6 : 2;
2393
+ }
2394
+
2395
+ // All slides need to be visible when exporting to PDF
2396
+ if( isPrintingPDF() ) {
2397
+ viewDistance = Number.MAX_VALUE;
1784
2398
  }
1785
2399
 
1786
2400
  for( var x = 0; x < horizontalSlidesLength; x++ ) {
@@ -1789,11 +2403,22 @@ var Reveal = (function(){
1789
2403
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
1790
2404
  verticalSlidesLength = verticalSlides.length;
1791
2405
 
1792
- // Loops so that it measures 1 between the first and last slides
1793
- distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2406
+ // Determine how far away this slide is from the present
2407
+ distanceX = Math.abs( ( indexh || 0 ) - x ) || 0;
2408
+
2409
+ // If the presentation is looped, distance should measure
2410
+ // 1 between the first and last slides
2411
+ if( config.loop ) {
2412
+ distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2413
+ }
1794
2414
 
1795
2415
  // Show the horizontal slide if it's within the view distance
1796
- horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
2416
+ if( distanceX < viewDistance ) {
2417
+ showSlide( horizontalSlide );
2418
+ }
2419
+ else {
2420
+ hideSlide( horizontalSlide );
2421
+ }
1797
2422
 
1798
2423
  if( verticalSlidesLength ) {
1799
2424
 
@@ -1802,9 +2427,14 @@ var Reveal = (function(){
1802
2427
  for( var y = 0; y < verticalSlidesLength; y++ ) {
1803
2428
  var verticalSlide = verticalSlides[y];
1804
2429
 
1805
- distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
2430
+ distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
1806
2431
 
1807
- verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
2432
+ if( distanceX + distanceY < viewDistance ) {
2433
+ showSlide( verticalSlide );
2434
+ }
2435
+ else {
2436
+ hideSlide( verticalSlide );
2437
+ }
1808
2438
  }
1809
2439
 
1810
2440
  }
@@ -1820,44 +2450,9 @@ var Reveal = (function(){
1820
2450
  function updateProgress() {
1821
2451
 
1822
2452
  // 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' ) );
1836
-
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
- }
2453
+ if( config.progress && dom.progressbar ) {
1843
2454
 
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';
2455
+ dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';
1861
2456
 
1862
2457
  }
1863
2458
 
@@ -1865,19 +2460,31 @@ var Reveal = (function(){
1865
2460
 
1866
2461
  /**
1867
2462
  * Updates the slide number div to reflect the current slide.
2463
+ *
2464
+ * Slide number format can be defined as a string using the
2465
+ * following variables:
2466
+ * h: current slide's horizontal index
2467
+ * v: current slide's vertical index
2468
+ * c: current slide index (flattened)
2469
+ * t: total number of slides (flattened)
1868
2470
  */
1869
2471
  function updateSlideNumber() {
1870
2472
 
1871
2473
  // Update slide number if enabled
1872
2474
  if( config.slideNumber && dom.slideNumber) {
1873
2475
 
1874
- // Display the number of the page using 'indexh - indexv' format
1875
- var indexString = indexh;
1876
- if( indexv > 0 ) {
1877
- indexString += ' - ' + indexv;
2476
+ // Default to only showing the current slide number
2477
+ var format = 'c';
2478
+
2479
+ // Check if a custom slide number format is available
2480
+ if( typeof config.slideNumber === 'string' ) {
2481
+ format = config.slideNumber;
1878
2482
  }
1879
2483
 
1880
- dom.slideNumber.innerHTML = indexString;
2484
+ dom.slideNumber.innerHTML = format.replace( /h/g, indexh )
2485
+ .replace( /v/g, indexv )
2486
+ .replace( /c/g, getSlidePastCount() + 1 )
2487
+ .replace( /t/g, getTotalSlides() );
1881
2488
  }
1882
2489
 
1883
2490
  }
@@ -1892,13 +2499,13 @@ var Reveal = (function(){
1892
2499
 
1893
2500
  // Remove the 'enabled' class from all directions
1894
2501
  dom.controlsLeft.concat( dom.controlsRight )
1895
- .concat( dom.controlsUp )
1896
- .concat( dom.controlsDown )
1897
- .concat( dom.controlsPrev )
1898
- .concat( dom.controlsNext ).forEach( function( node ) {
1899
- node.classList.remove( 'enabled' );
1900
- node.classList.remove( 'fragmented' );
1901
- } );
2502
+ .concat( dom.controlsUp )
2503
+ .concat( dom.controlsDown )
2504
+ .concat( dom.controlsPrev )
2505
+ .concat( dom.controlsNext ).forEach( function( node ) {
2506
+ node.classList.remove( 'enabled' );
2507
+ node.classList.remove( 'fragmented' );
2508
+ } );
1902
2509
 
1903
2510
  // Add the 'enabled' class to the available routes
1904
2511
  if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
@@ -1951,43 +2558,76 @@ var Reveal = (function(){
1951
2558
  // states of their slides (past/present/future)
1952
2559
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1953
2560
 
2561
+ backgroundh.classList.remove( 'past' );
2562
+ backgroundh.classList.remove( 'present' );
2563
+ backgroundh.classList.remove( 'future' );
2564
+
1954
2565
  if( h < indexh ) {
1955
- backgroundh.className = 'slide-background ' + horizontalPast;
2566
+ backgroundh.classList.add( horizontalPast );
1956
2567
  }
1957
2568
  else if ( h > indexh ) {
1958
- backgroundh.className = 'slide-background ' + horizontalFuture;
2569
+ backgroundh.classList.add( horizontalFuture );
1959
2570
  }
1960
2571
  else {
1961
- backgroundh.className = 'slide-background present';
2572
+ backgroundh.classList.add( 'present' );
1962
2573
 
1963
2574
  // Store a reference to the current background element
1964
2575
  currentBackground = backgroundh;
1965
2576
  }
1966
2577
 
1967
2578
  if( includeAll || h === indexh ) {
1968
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
2579
+ toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
2580
+
2581
+ backgroundv.classList.remove( 'past' );
2582
+ backgroundv.classList.remove( 'present' );
2583
+ backgroundv.classList.remove( 'future' );
1969
2584
 
1970
2585
  if( v < indexv ) {
1971
- backgroundv.className = 'slide-background past';
2586
+ backgroundv.classList.add( 'past' );
1972
2587
  }
1973
2588
  else if ( v > indexv ) {
1974
- backgroundv.className = 'slide-background future';
2589
+ backgroundv.classList.add( 'future' );
1975
2590
  }
1976
2591
  else {
1977
- backgroundv.className = 'slide-background present';
2592
+ backgroundv.classList.add( 'present' );
1978
2593
 
1979
2594
  // Only if this is the present horizontal and vertical slide
1980
2595
  if( h === indexh ) currentBackground = backgroundv;
1981
2596
  }
1982
2597
 
1983
- } );
2598
+ } );
2599
+ }
2600
+
2601
+ } );
2602
+
2603
+ // Stop any currently playing video background
2604
+ if( previousBackground ) {
2605
+
2606
+ var previousVideo = previousBackground.querySelector( 'video' );
2607
+ if( previousVideo ) previousVideo.pause();
2608
+
2609
+ }
2610
+
2611
+ if( currentBackground ) {
2612
+
2613
+ // Start video playback
2614
+ var currentVideo = currentBackground.querySelector( 'video' );
2615
+ if( currentVideo ) {
2616
+ currentVideo.currentTime = 0;
2617
+ currentVideo.play();
1984
2618
  }
1985
2619
 
1986
- } );
2620
+ var backgroundImageURL = currentBackground.style.backgroundImage || '';
1987
2621
 
1988
- // Don't transition between identical backgrounds. This
1989
- // prevents unwanted flicker.
1990
- if( currentBackground ) {
2622
+ // Restart GIFs (doesn't work in Firefox)
2623
+ if( /\.gif/i.test( backgroundImageURL ) ) {
2624
+ currentBackground.style.backgroundImage = '';
2625
+ window.getComputedStyle( currentBackground ).opacity;
2626
+ currentBackground.style.backgroundImage = backgroundImageURL;
2627
+ }
2628
+
2629
+ // Don't transition between identical backgrounds. This
2630
+ // prevents unwanted flicker.
1991
2631
  var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
2632
  var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
2633
  if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
@@ -1995,6 +2635,20 @@ var Reveal = (function(){
1995
2635
  }
1996
2636
 
1997
2637
  previousBackground = currentBackground;
2638
+
2639
+ }
2640
+
2641
+ // If there's a background brightness flag for this slide,
2642
+ // bubble it to the .reveal container
2643
+ if( currentSlide ) {
2644
+ [ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {
2645
+ if( currentSlide.classList.contains( classToBubble ) ) {
2646
+ dom.wrapper.classList.add( classToBubble );
2647
+ }
2648
+ else {
2649
+ dom.wrapper.classList.remove( classToBubble );
2650
+ }
2651
+ } );
1998
2652
  }
1999
2653
 
2000
2654
  // Allow the first background to apply without transition
@@ -2012,8 +2666,8 @@ var Reveal = (function(){
2012
2666
 
2013
2667
  if( config.parallaxBackgroundImage ) {
2014
2668
 
2015
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2669
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2670
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
2671
 
2018
2672
  var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
2673
  backgroundWidth, backgroundHeight;
@@ -2026,16 +2680,138 @@ var Reveal = (function(){
2026
2680
  backgroundHeight = parseInt( backgroundSize[1], 10 );
2027
2681
  }
2028
2682
 
2029
- var slideWidth = dom.background.offsetWidth;
2030
- var horizontalSlideCount = horizontalSlides.length;
2031
- var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
2683
+ var slideWidth = dom.background.offsetWidth,
2684
+ horizontalSlideCount = horizontalSlides.length,
2685
+ horizontalOffsetMultiplier,
2686
+ horizontalOffset;
2687
+
2688
+ if( typeof config.parallaxBackgroundHorizontal === 'number' ) {
2689
+ horizontalOffsetMultiplier = config.parallaxBackgroundHorizontal;
2690
+ }
2691
+ else {
2692
+ horizontalOffsetMultiplier = ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 );
2693
+ }
2694
+
2695
+ horizontalOffset = horizontalOffsetMultiplier * indexh * -1;
2696
+
2697
+ var slideHeight = dom.background.offsetHeight,
2698
+ verticalSlideCount = verticalSlides.length,
2699
+ verticalOffsetMultiplier,
2700
+ verticalOffset;
2701
+
2702
+ if( typeof config.parallaxBackgroundVertical === 'number' ) {
2703
+ verticalOffsetMultiplier = config.parallaxBackgroundVertical;
2704
+ }
2705
+ else {
2706
+ verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );
2707
+ }
2708
+
2709
+ verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indexv * 1 : 0;
2710
+
2711
+ dom.background.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';
2712
+
2713
+ }
2714
+
2715
+ }
2716
+
2717
+ /**
2718
+ * Called when the given slide is within the configured view
2719
+ * distance. Shows the slide element and loads any content
2720
+ * that is set to load lazily (data-src).
2721
+ */
2722
+ function showSlide( slide ) {
2723
+
2724
+ // Show the slide element
2725
+ slide.style.display = 'block';
2726
+
2727
+ // Media elements with data-src attributes
2728
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
2729
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
2730
+ element.removeAttribute( 'data-src' );
2731
+ } );
2732
+
2733
+ // Media elements with <source> children
2734
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
2735
+ var sources = 0;
2736
+
2737
+ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
2738
+ source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
2739
+ source.removeAttribute( 'data-src' );
2740
+ sources += 1;
2741
+ } );
2742
+
2743
+ // If we rewrote sources for this video/audio element, we need
2744
+ // to manually tell it to load from its new origin
2745
+ if( sources > 0 ) {
2746
+ media.load();
2747
+ }
2748
+ } );
2749
+
2750
+
2751
+ // Show the corresponding background element
2752
+ var indices = getIndices( slide );
2753
+ var background = getSlideBackground( indices.h, indices.v );
2754
+ if( background ) {
2755
+ background.style.display = 'block';
2756
+
2757
+ // If the background contains media, load it
2758
+ if( background.hasAttribute( 'data-loaded' ) === false ) {
2759
+ background.setAttribute( 'data-loaded', 'true' );
2760
+
2761
+ var backgroundImage = slide.getAttribute( 'data-background-image' ),
2762
+ backgroundVideo = slide.getAttribute( 'data-background-video' ),
2763
+ backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
2764
+ backgroundIframe = slide.getAttribute( 'data-background-iframe' );
2765
+
2766
+ // Images
2767
+ if( backgroundImage ) {
2768
+ background.style.backgroundImage = 'url('+ backgroundImage +')';
2769
+ }
2770
+ // Videos
2771
+ else if ( backgroundVideo && !isSpeakerNotes() ) {
2772
+ var video = document.createElement( 'video' );
2773
+
2774
+ if( backgroundVideoLoop ) {
2775
+ video.setAttribute( 'loop', '' );
2776
+ }
2777
+
2778
+ // Support comma separated lists of video sources
2779
+ backgroundVideo.split( ',' ).forEach( function( source ) {
2780
+ video.innerHTML += '<source src="'+ source +'">';
2781
+ } );
2782
+
2783
+ background.appendChild( video );
2784
+ }
2785
+ // Iframes
2786
+ else if( backgroundIframe ) {
2787
+ var iframe = document.createElement( 'iframe' );
2788
+ iframe.setAttribute( 'src', backgroundIframe );
2789
+ iframe.style.width = '100%';
2790
+ iframe.style.height = '100%';
2791
+ iframe.style.maxHeight = '100%';
2792
+ iframe.style.maxWidth = '100%';
2793
+
2794
+ background.appendChild( iframe );
2795
+ }
2796
+ }
2797
+ }
2798
+
2799
+ }
2032
2800
 
2033
- var slideHeight = dom.background.offsetHeight;
2034
- var verticalSlideCount = verticalSlides.length;
2035
- var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2801
+ /**
2802
+ * Called when the given slide is moved outside of the
2803
+ * configured view distance.
2804
+ */
2805
+ function hideSlide( slide ) {
2036
2806
 
2037
- dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2807
+ // Hide the slide element
2808
+ slide.style.display = 'none';
2038
2809
 
2810
+ // Hide the corresponding background element
2811
+ var indices = getIndices( slide );
2812
+ var background = getSlideBackground( indices.h, indices.v );
2813
+ if( background ) {
2814
+ background.style.display = 'none';
2039
2815
  }
2040
2816
 
2041
2817
  }
@@ -2047,8 +2823,8 @@ var Reveal = (function(){
2047
2823
  */
2048
2824
  function availableRoutes() {
2049
2825
 
2050
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2051
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2826
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2827
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2052
2828
 
2053
2829
  var routes = {
2054
2830
  left: indexh > 0 || config.loop,
@@ -2091,6 +2867,30 @@ var Reveal = (function(){
2091
2867
 
2092
2868
  }
2093
2869
 
2870
+ /**
2871
+ * Enforces origin-specific format rules for embedded media.
2872
+ */
2873
+ function formatEmbeddedContent() {
2874
+
2875
+ var _appendParamToIframeSource = function( sourceAttribute, sourceURL, param ) {
2876
+ toArray( dom.slides.querySelectorAll( 'iframe['+ sourceAttribute +'*="'+ sourceURL +'"]' ) ).forEach( function( el ) {
2877
+ var src = el.getAttribute( sourceAttribute );
2878
+ if( src && src.indexOf( param ) === -1 ) {
2879
+ el.setAttribute( sourceAttribute, src + ( !/\?/.test( src ) ? '?' : '&' ) + param );
2880
+ }
2881
+ });
2882
+ };
2883
+
2884
+ // YouTube frames must include "?enablejsapi=1"
2885
+ _appendParamToIframeSource( 'src', 'youtube.com/embed/', 'enablejsapi=1' );
2886
+ _appendParamToIframeSource( 'data-src', 'youtube.com/embed/', 'enablejsapi=1' );
2887
+
2888
+ // Vimeo frames must include "?api=1"
2889
+ _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
2890
+ _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
2891
+
2892
+ }
2893
+
2094
2894
  /**
2095
2895
  * Start playback of any embedded content inside of
2096
2896
  * the targeted slide.
@@ -2098,24 +2898,56 @@ var Reveal = (function(){
2098
2898
  function startEmbeddedContent( slide ) {
2099
2899
 
2100
2900
  if( slide && !isSpeakerNotes() ) {
2901
+ // Restart GIFs
2902
+ toArray( slide.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
2903
+ // Setting the same unchanged source like this was confirmed
2904
+ // to work in Chrome, FF & Safari
2905
+ el.setAttribute( 'src', el.getAttribute( 'src' ) );
2906
+ } );
2907
+
2101
2908
  // HTML5 media elements
2102
2909
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2103
- if( el.hasAttribute( 'data-autoplay' ) ) {
2910
+ if( el.hasAttribute( 'data-autoplay' ) && typeof el.play === 'function' ) {
2104
2911
  el.play();
2105
2912
  }
2106
2913
  } );
2107
2914
 
2108
- // iframe embeds
2109
- toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2110
- el.contentWindow.postMessage( 'slide:start', '*' );
2111
- });
2915
+ // Normal iframes
2916
+ toArray( slide.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {
2917
+ startEmbeddedIframe( { target: el } );
2918
+ } );
2112
2919
 
2113
- // YouTube embeds
2114
- toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2115
- if( el.hasAttribute( 'data-autoplay' ) ) {
2116
- el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2920
+ // Lazy loading iframes
2921
+ toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
2922
+ if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
2923
+ el.removeEventListener( 'load', startEmbeddedIframe ); // remove first to avoid dupes
2924
+ el.addEventListener( 'load', startEmbeddedIframe );
2925
+ el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
2117
2926
  }
2118
- });
2927
+ } );
2928
+ }
2929
+
2930
+ }
2931
+
2932
+ /**
2933
+ * "Starts" the content of an embedded iframe using the
2934
+ * postmessage API.
2935
+ */
2936
+ function startEmbeddedIframe( event ) {
2937
+
2938
+ var iframe = event.target;
2939
+
2940
+ // YouTube postMessage API
2941
+ if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
2942
+ iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2943
+ }
2944
+ // Vimeo postMessage API
2945
+ else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
2946
+ iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
2947
+ }
2948
+ // Generic postMessage API
2949
+ else {
2950
+ iframe.contentWindow.postMessage( 'slide:start', '*' );
2119
2951
  }
2120
2952
 
2121
2953
  }
@@ -2126,27 +2958,120 @@ var Reveal = (function(){
2126
2958
  */
2127
2959
  function stopEmbeddedContent( slide ) {
2128
2960
 
2129
- if( slide ) {
2961
+ if( slide && slide.parentNode ) {
2130
2962
  // HTML5 media elements
2131
2963
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2132
- if( !el.hasAttribute( 'data-ignore' ) ) {
2964
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
2133
2965
  el.pause();
2134
2966
  }
2135
2967
  } );
2136
2968
 
2137
- // iframe embeds
2969
+ // Generic postMessage API for non-lazy loaded iframes
2138
2970
  toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2139
2971
  el.contentWindow.postMessage( 'slide:stop', '*' );
2972
+ el.removeEventListener( 'load', startEmbeddedIframe );
2140
2973
  });
2141
2974
 
2142
- // YouTube embeds
2975
+ // YouTube postMessage API
2143
2976
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2144
2977
  if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2145
2978
  el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
2146
2979
  }
2147
2980
  });
2981
+
2982
+ // Vimeo postMessage API
2983
+ toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2984
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2985
+ el.contentWindow.postMessage( '{"method":"pause"}', '*' );
2986
+ }
2987
+ });
2988
+
2989
+ // Lazy loading iframes
2990
+ toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
2991
+ // Only removing the src doesn't actually unload the frame
2992
+ // in all browsers (Firefox) so we set it to blank first
2993
+ el.setAttribute( 'src', 'about:blank' );
2994
+ el.removeAttribute( 'src' );
2995
+ } );
2996
+ }
2997
+
2998
+ }
2999
+
3000
+ /**
3001
+ * Returns the number of past slides. This can be used as a global
3002
+ * flattened index for slides.
3003
+ */
3004
+ function getSlidePastCount() {
3005
+
3006
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
3007
+
3008
+ // The number of past slides
3009
+ var pastCount = 0;
3010
+
3011
+ // Step through all slides and count the past ones
3012
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
3013
+
3014
+ var horizontalSlide = horizontalSlides[i];
3015
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
3016
+
3017
+ for( var j = 0; j < verticalSlides.length; j++ ) {
3018
+
3019
+ // Stop as soon as we arrive at the present
3020
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
3021
+ break mainLoop;
3022
+ }
3023
+
3024
+ pastCount++;
3025
+
3026
+ }
3027
+
3028
+ // Stop as soon as we arrive at the present
3029
+ if( horizontalSlide.classList.contains( 'present' ) ) {
3030
+ break;
3031
+ }
3032
+
3033
+ // Don't count the wrapping section for vertical slides
3034
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
3035
+ pastCount++;
3036
+ }
3037
+
3038
+ }
3039
+
3040
+ return pastCount;
3041
+
3042
+ }
3043
+
3044
+ /**
3045
+ * Returns a value ranging from 0-1 that represents
3046
+ * how far into the presentation we have navigated.
3047
+ */
3048
+ function getProgress() {
3049
+
3050
+ // The number of past and total slides
3051
+ var totalCount = getTotalSlides();
3052
+ var pastCount = getSlidePastCount();
3053
+
3054
+ if( currentSlide ) {
3055
+
3056
+ var allFragments = currentSlide.querySelectorAll( '.fragment' );
3057
+
3058
+ // If there are fragments in the current slide those should be
3059
+ // accounted for in the progress.
3060
+ if( allFragments.length > 0 ) {
3061
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
3062
+
3063
+ // This value represents how big a portion of the slide progress
3064
+ // that is made up by its fragments (0-1)
3065
+ var fragmentWeight = 0.9;
3066
+
3067
+ // Add fragment progress to the past slide count
3068
+ pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
3069
+ }
3070
+
2148
3071
  }
2149
3072
 
3073
+ return pastCount / ( totalCount - 1 );
3074
+
2150
3075
  }
2151
3076
 
2152
3077
  /**
@@ -2173,8 +3098,13 @@ var Reveal = (function(){
2173
3098
  // If the first bit is invalid and there is a name we can
2174
3099
  // assume that this is a named link
2175
3100
  if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
2176
- // Find the slide with the specified name
2177
- var element = document.querySelector( '#' + name );
3101
+ var element;
3102
+
3103
+ // Ensure the named link is a valid HTML ID attribute
3104
+ if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
3105
+ // Find the slide with the specified ID
3106
+ element = document.getElementById( name );
3107
+ }
2178
3108
 
2179
3109
  if( element ) {
2180
3110
  // Find the position of the named slide and navigate to it
@@ -2216,12 +3146,19 @@ var Reveal = (function(){
2216
3146
  if( typeof delay === 'number' ) {
2217
3147
  writeURLTimeout = setTimeout( writeURL, delay );
2218
3148
  }
2219
- else {
3149
+ else if( currentSlide ) {
2220
3150
  var url = '/';
2221
3151
 
3152
+ // Attempt to create a named link based on the slide's ID
3153
+ var id = currentSlide.getAttribute( 'id' );
3154
+ if( id ) {
3155
+ id = id.toLowerCase();
3156
+ id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
3157
+ }
3158
+
2222
3159
  // 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' );
3160
+ if( typeof id === 'string' && id.length ) {
3161
+ url = '/' + id;
2225
3162
  }
2226
3163
  // Otherwise use the /h/v index
2227
3164
  else {
@@ -2258,11 +3195,14 @@ var Reveal = (function(){
2258
3195
  var slideh = isVertical ? slide.parentNode : slide;
2259
3196
 
2260
3197
  // Select all horizontal slides
2261
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
3198
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2262
3199
 
2263
3200
  // Now that we know which the horizontal slide is, get its index
2264
3201
  h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
2265
3202
 
3203
+ // Assume we're not vertical
3204
+ v = undefined;
3205
+
2266
3206
  // If this is a vertical slide, grab the vertical index
2267
3207
  if( isVertical ) {
2268
3208
  v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
@@ -2272,8 +3212,13 @@ var Reveal = (function(){
2272
3212
  if( !slide && currentSlide ) {
2273
3213
  var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2274
3214
  if( hasFragments ) {
2275
- var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2276
- f = visibleFragments.length - 1;
3215
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3216
+ if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
3217
+ f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
3218
+ }
3219
+ else {
3220
+ f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
3221
+ }
2277
3222
  }
2278
3223
  }
2279
3224
 
@@ -2281,6 +3226,107 @@ var Reveal = (function(){
2281
3226
 
2282
3227
  }
2283
3228
 
3229
+ /**
3230
+ * Retrieves the total number of slides in this presentation.
3231
+ */
3232
+ function getTotalSlides() {
3233
+
3234
+ return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
3235
+
3236
+ }
3237
+
3238
+ /**
3239
+ * Returns the slide element matching the specified index.
3240
+ */
3241
+ function getSlide( x, y ) {
3242
+
3243
+ var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3244
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
3245
+
3246
+ if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
3247
+ return verticalSlides ? verticalSlides[ y ] : undefined;
3248
+ }
3249
+
3250
+ return horizontalSlide;
3251
+
3252
+ }
3253
+
3254
+ /**
3255
+ * Returns the background element for the given slide.
3256
+ * All slides, even the ones with no background properties
3257
+ * defined, have a background element so as long as the
3258
+ * index is valid an element will be returned.
3259
+ */
3260
+ function getSlideBackground( x, y ) {
3261
+
3262
+ // When printing to PDF the slide backgrounds are nested
3263
+ // inside of the slides
3264
+ if( isPrintingPDF() ) {
3265
+ var slide = getSlide( x, y );
3266
+ if( slide ) {
3267
+ var background = slide.querySelector( '.slide-background' );
3268
+ if( background && background.parentNode === slide ) {
3269
+ return background;
3270
+ }
3271
+ }
3272
+
3273
+ return undefined;
3274
+ }
3275
+
3276
+ var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
3277
+ var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
3278
+
3279
+ if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
3280
+ return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
3281
+ }
3282
+
3283
+ return horizontalBackground;
3284
+
3285
+ }
3286
+
3287
+ /**
3288
+ * Retrieves the current state of the presentation as
3289
+ * an object. This state can then be restored at any
3290
+ * time.
3291
+ */
3292
+ function getState() {
3293
+
3294
+ var indices = getIndices();
3295
+
3296
+ return {
3297
+ indexh: indices.h,
3298
+ indexv: indices.v,
3299
+ indexf: indices.f,
3300
+ paused: isPaused(),
3301
+ overview: isOverview()
3302
+ };
3303
+
3304
+ }
3305
+
3306
+ /**
3307
+ * Restores the presentation to the given state.
3308
+ *
3309
+ * @param {Object} state As generated by getState()
3310
+ */
3311
+ function setState( state ) {
3312
+
3313
+ if( typeof state === 'object' ) {
3314
+ slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
3315
+
3316
+ var pausedFlag = deserialize( state.paused ),
3317
+ overviewFlag = deserialize( state.overview );
3318
+
3319
+ if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
3320
+ togglePause( pausedFlag );
3321
+ }
3322
+
3323
+ if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
3324
+ toggleOverview( overviewFlag );
3325
+ }
3326
+ }
3327
+
3328
+ }
3329
+
2284
3330
  /**
2285
3331
  * Return a sorted fragments list, ordered by an increasing
2286
3332
  * "data-fragment-index" attribute.
@@ -2392,6 +3438,9 @@ var Reveal = (function(){
2392
3438
  element.classList.add( 'visible' );
2393
3439
  element.classList.remove( 'current-fragment' );
2394
3440
 
3441
+ // Announce the fragments one by one to the Screen Reader
3442
+ dom.statusDiv.textContent = element.textContent;
3443
+
2395
3444
  if( i === index ) {
2396
3445
  element.classList.add( 'current-fragment' );
2397
3446
  }
@@ -2415,6 +3464,7 @@ var Reveal = (function(){
2415
3464
  }
2416
3465
 
2417
3466
  updateControls();
3467
+ updateProgress();
2418
3468
 
2419
3469
  return !!( fragmentsShown.length || fragmentsHidden.length );
2420
3470
 
@@ -2459,14 +3509,21 @@ var Reveal = (function(){
2459
3509
 
2460
3510
  if( currentSlide ) {
2461
3511
 
3512
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3513
+
3514
+ var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
2462
3515
  var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
3516
  var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
3517
 
2465
3518
  // 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 ) {
3519
+ // 1. Current fragment's data-autoslide
3520
+ // 2. Current slide's data-autoslide
3521
+ // 3. Parent slide's data-autoslide
3522
+ // 4. Global autoSlide setting
3523
+ if( fragmentAutoSlide ) {
3524
+ autoSlide = parseInt( fragmentAutoSlide, 10 );
3525
+ }
3526
+ else if( slideAutoSlide ) {
2470
3527
  autoSlide = parseInt( slideAutoSlide, 10 );
2471
3528
  }
2472
3529
  else if( parentAutoSlide ) {
@@ -2478,14 +3535,17 @@ var Reveal = (function(){
2478
3535
 
2479
3536
  // If there are media elements with data-autoplay,
2480
3537
  // automatically set the autoSlide duration to the
2481
- // length of that media
2482
- toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2483
- if( el.hasAttribute( 'data-autoplay' ) ) {
2484
- if( autoSlide && el.duration * 1000 > autoSlide ) {
2485
- autoSlide = ( el.duration * 1000 ) + 1000;
3538
+ // length of that media. Not applicable if the slide
3539
+ // is divided up into fragments.
3540
+ if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {
3541
+ toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
3542
+ if( el.hasAttribute( 'data-autoplay' ) ) {
3543
+ if( autoSlide && el.duration * 1000 > autoSlide ) {
3544
+ autoSlide = ( el.duration * 1000 ) + 1000;
3545
+ }
2486
3546
  }
2487
- }
2488
- } );
3547
+ } );
3548
+ }
2489
3549
 
2490
3550
  // Cue the next auto-slide if:
2491
3551
  // - There is an autoSlide value
@@ -2493,7 +3553,7 @@ var Reveal = (function(){
2493
3553
  // - The presentation isn't paused
2494
3554
  // - The overview isn't active
2495
3555
  // - The presentation isn't over
2496
- if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
3556
+ if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
2497
3557
  autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
3558
  autoSlideStartTime = Date.now();
2499
3559
  }
@@ -2518,19 +3578,25 @@ var Reveal = (function(){
2518
3578
 
2519
3579
  function pauseAutoSlide() {
2520
3580
 
2521
- autoSlidePaused = true;
2522
- clearTimeout( autoSlideTimeout );
3581
+ if( autoSlide && !autoSlidePaused ) {
3582
+ autoSlidePaused = true;
3583
+ dispatchEvent( 'autoslidepaused' );
3584
+ clearTimeout( autoSlideTimeout );
2523
3585
 
2524
- if( autoSlidePlayer ) {
2525
- autoSlidePlayer.setPlaying( false );
3586
+ if( autoSlidePlayer ) {
3587
+ autoSlidePlayer.setPlaying( false );
3588
+ }
2526
3589
  }
2527
3590
 
2528
3591
  }
2529
3592
 
2530
3593
  function resumeAutoSlide() {
2531
3594
 
2532
- autoSlidePaused = false;
2533
- cueAutoSlide();
3595
+ if( autoSlide && autoSlidePaused ) {
3596
+ autoSlidePaused = false;
3597
+ dispatchEvent( 'autoslideresumed' );
3598
+ cueAutoSlide();
3599
+ }
2534
3600
 
2535
3601
  }
2536
3602
 
@@ -2597,7 +3663,14 @@ var Reveal = (function(){
2597
3663
  }
2598
3664
  else {
2599
3665
  // Fetch the previous horizontal slide, if there is one
2600
- var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
3666
+ var previousSlide;
3667
+
3668
+ if( config.rtl ) {
3669
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();
3670
+ }
3671
+ else {
3672
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();
3673
+ }
2601
3674
 
2602
3675
  if( previousSlide ) {
2603
3676
  var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
@@ -2610,13 +3683,21 @@ var Reveal = (function(){
2610
3683
  }
2611
3684
 
2612
3685
  /**
2613
- * Same as #navigatePrev() but navigates forwards.
3686
+ * The reverse of #navigatePrev().
2614
3687
  */
2615
3688
  function navigateNext() {
2616
3689
 
2617
3690
  // Prioritize revealing fragments
2618
3691
  if( nextFragment() === false ) {
2619
- availableRoutes().down ? navigateDown() : navigateRight();
3692
+ if( availableRoutes().down ) {
3693
+ navigateDown();
3694
+ }
3695
+ else if( config.rtl ) {
3696
+ navigateLeft();
3697
+ }
3698
+ else {
3699
+ navigateRight();
3700
+ }
2620
3701
  }
2621
3702
 
2622
3703
  // If auto-sliding is enabled we need to cue up
@@ -2642,21 +3723,47 @@ var Reveal = (function(){
2642
3723
 
2643
3724
  }
2644
3725
 
3726
+ /**
3727
+ * Handler for the document level 'keypress' event.
3728
+ */
3729
+ function onDocumentKeyPress( event ) {
3730
+
3731
+ // Check if the pressed key is question mark
3732
+ if( event.shiftKey && event.charCode === 63 ) {
3733
+ if( dom.overlay ) {
3734
+ closeOverlay();
3735
+ }
3736
+ else {
3737
+ showHelp( true );
3738
+ }
3739
+ }
3740
+
3741
+ }
3742
+
2645
3743
  /**
2646
3744
  * Handler for the document level 'keydown' event.
2647
3745
  */
2648
3746
  function onDocumentKeyDown( event ) {
2649
3747
 
3748
+ // If there's a condition specified and it returns false,
3749
+ // ignore this event
3750
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
3751
+ return true;
3752
+ }
3753
+
3754
+ // Remember if auto-sliding was paused so we can toggle it
3755
+ var autoSlideWasPaused = autoSlidePaused;
3756
+
2650
3757
  onUserInput( event );
2651
3758
 
2652
3759
  // Check if there's a focused element that could be using
2653
3760
  // the keyboard
2654
- var activeElement = document.activeElement;
2655
- var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
3761
+ var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
3762
+ var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
2656
3763
 
2657
3764
  // Disregard the event if there's a focused element or a
2658
3765
  // keyboard modifier key is present
2659
- if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
3766
+ if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
2660
3767
 
2661
3768
  // While paused only allow "unpausing" keyboard events (b and .)
2662
3769
  if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
@@ -2719,10 +3826,12 @@ var Reveal = (function(){
2719
3826
  case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2720
3827
  // return
2721
3828
  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;
3829
+ // two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
3830
+ case 58: case 59: case 66: case 190: case 191: togglePause(); break;
2724
3831
  // f
2725
3832
  case 70: enterFullscreen(); break;
3833
+ // a
3834
+ case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
2726
3835
  default:
2727
3836
  triggered = false;
2728
3837
  }
@@ -2732,18 +3841,18 @@ var Reveal = (function(){
2732
3841
  // If the input resulted in a triggered action we should prevent
2733
3842
  // the browsers default behavior
2734
3843
  if( triggered ) {
2735
- event.preventDefault();
3844
+ event.preventDefault && event.preventDefault();
2736
3845
  }
2737
3846
  // ESC or O key
2738
3847
  else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
- if( dom.preview ) {
2740
- closePreview();
3848
+ if( dom.overlay ) {
3849
+ closeOverlay();
2741
3850
  }
2742
3851
  else {
2743
3852
  toggleOverview();
2744
3853
  }
2745
3854
 
2746
- event.preventDefault();
3855
+ event.preventDefault && event.preventDefault();
2747
3856
  }
2748
3857
 
2749
3858
  // If auto-sliding is enabled we need to cue up
@@ -2877,7 +3986,7 @@ var Reveal = (function(){
2877
3986
  */
2878
3987
  function onPointerDown( event ) {
2879
3988
 
2880
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3989
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2881
3990
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2882
3991
  onTouchStart( event );
2883
3992
  }
@@ -2889,7 +3998,7 @@ var Reveal = (function(){
2889
3998
  */
2890
3999
  function onPointerMove( event ) {
2891
4000
 
2892
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
4001
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2893
4002
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2894
4003
  onTouchMove( event );
2895
4004
  }
@@ -2901,7 +4010,7 @@ var Reveal = (function(){
2901
4010
  */
2902
4011
  function onPointerUp( event ) {
2903
4012
 
2904
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
4013
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2905
4014
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2906
4015
  onTouchEnd( event );
2907
4016
  }
@@ -2942,9 +4051,13 @@ var Reveal = (function(){
2942
4051
 
2943
4052
  event.preventDefault();
2944
4053
 
2945
- var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
4054
+ var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
2946
4055
  var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
2947
4056
 
4057
+ if( config.rtl ) {
4058
+ slideIndex = slidesTotal - slideIndex;
4059
+ }
4060
+
2948
4061
  slide( slideIndex );
2949
4062
 
2950
4063
  }
@@ -2983,13 +4096,16 @@ var Reveal = (function(){
2983
4096
  function onPageVisibilityChange( event ) {
2984
4097
 
2985
4098
  var isHidden = document.webkitHidden ||
2986
- document.msHidden ||
2987
- document.hidden;
4099
+ document.msHidden ||
4100
+ document.hidden;
2988
4101
 
2989
4102
  // If, after clicking a link or similar and we're coming back,
2990
4103
  // focus the document.body to ensure we can use keyboard shortcuts
2991
4104
  if( isHidden === false && document.activeElement !== document.body ) {
2992
- document.activeElement.blur();
4105
+ // Not all elements support .blur() - SVGs among them.
4106
+ if( typeof document.activeElement.blur === 'function' ) {
4107
+ document.activeElement.blur();
4108
+ }
2993
4109
  document.body.focus();
2994
4110
  }
2995
4111
 
@@ -3033,10 +4149,12 @@ var Reveal = (function(){
3033
4149
  */
3034
4150
  function onPreviewLinkClicked( event ) {
3035
4151
 
3036
- var url = event.target.getAttribute( 'href' );
3037
- if( url ) {
3038
- openPreview( url );
3039
- event.preventDefault();
4152
+ if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
4153
+ var url = event.currentTarget.getAttribute( 'href' );
4154
+ if( url ) {
4155
+ showPreview( url );
4156
+ event.preventDefault();
4157
+ }
3040
4158
  }
3041
4159
 
3042
4160
  }
@@ -3232,7 +4350,7 @@ var Reveal = (function(){
3232
4350
  // --------------------------------------------------------------------//
3233
4351
 
3234
4352
 
3235
- return {
4353
+ Reveal = {
3236
4354
  initialize: initialize,
3237
4355
  configure: configure,
3238
4356
  sync: sync,
@@ -3275,28 +4393,35 @@ var Reveal = (function(){
3275
4393
  // Toggles the "black screen" mode on/off
3276
4394
  togglePause: togglePause,
3277
4395
 
4396
+ // Toggles the auto slide mode on/off
4397
+ toggleAutoSlide: toggleAutoSlide,
4398
+
3278
4399
  // State checks
3279
4400
  isOverview: isOverview,
3280
4401
  isPaused: isPaused,
4402
+ isAutoSliding: isAutoSliding,
3281
4403
 
3282
4404
  // Adds or removes all internal event listeners (such as keyboard)
3283
4405
  addEventListeners: addEventListeners,
3284
4406
  removeEventListeners: removeEventListeners,
3285
4407
 
4408
+ // Facility for persisting and restoring the presentation state
4409
+ getState: getState,
4410
+ setState: setState,
4411
+
4412
+ // Presentation progress on range of 0-1
4413
+ getProgress: getProgress,
4414
+
3286
4415
  // Returns the indices of the current, or specified, slide
3287
4416
  getIndices: getIndices,
3288
4417
 
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' );
4418
+ getTotalSlides: getTotalSlides,
3293
4419
 
3294
- if( typeof y !== 'undefined' ) {
3295
- return verticalSlides ? verticalSlides[ y ] : undefined;
3296
- }
4420
+ // Returns the slide element at the specified index
4421
+ getSlide: getSlide,
3297
4422
 
3298
- return horizontalSlide;
3299
- },
4423
+ // Returns the slide background element at the specified index
4424
+ getSlideBackground: getSlideBackground,
3300
4425
 
3301
4426
  // Returns the previous slide element, may be null
3302
4427
  getPreviousSlide: function() {
@@ -3330,12 +4455,7 @@ var Reveal = (function(){
3330
4455
  for( var i in query ) {
3331
4456
  var value = query[ i ];
3332
4457
 
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 );
4458
+ query[ i ] = deserialize( unescape( value ) );
3339
4459
  }
3340
4460
 
3341
4461
  return query;
@@ -3343,7 +4463,7 @@ var Reveal = (function(){
3343
4463
 
3344
4464
  // Returns true if we're currently on the first slide
3345
4465
  isFirstSlide: function() {
3346
- return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
4466
+ return ( indexh === 0 && indexv === 0 );
3347
4467
  },
3348
4468
 
3349
4469
  // Returns true if we're currently on the last slide
@@ -3376,7 +4496,14 @@ var Reveal = (function(){
3376
4496
  if( 'addEventListener' in window ) {
3377
4497
  ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
3378
4498
  }
4499
+ },
4500
+
4501
+ // Programatically triggers a keyboard event
4502
+ triggerKey: function( keyCode ) {
4503
+ onDocumentKeyDown( { keyCode: keyCode } );
3379
4504
  }
3380
4505
  };
3381
4506
 
3382
- })();
4507
+ return Reveal;
4508
+
4509
+ }));