reveal-ck 3.8.1 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/files/reveal.js/Gruntfile.js +41 -40
  3. data/files/reveal.js/LICENSE +1 -1
  4. data/files/reveal.js/README.md +189 -107
  5. data/files/reveal.js/bower.json +2 -2
  6. data/files/reveal.js/css/print/pdf.css +10 -18
  7. data/files/reveal.js/css/reveal.css +344 -140
  8. data/files/reveal.js/css/reveal.scss +371 -76
  9. data/files/reveal.js/css/theme/README.md +2 -2
  10. data/files/reveal.js/css/theme/beige.css +19 -38
  11. data/files/reveal.js/css/theme/black.css +19 -38
  12. data/files/reveal.js/css/theme/blood.css +19 -38
  13. data/files/reveal.js/css/theme/league.css +19 -38
  14. data/files/reveal.js/css/theme/moon.css +19 -38
  15. data/files/reveal.js/css/theme/night.css +19 -38
  16. data/files/reveal.js/css/theme/serif.css +19 -38
  17. data/files/reveal.js/css/theme/simple.css +19 -38
  18. data/files/reveal.js/css/theme/sky.css +19 -38
  19. data/files/reveal.js/css/theme/solarized.css +19 -38
  20. data/files/reveal.js/css/theme/template/theme.scss +18 -45
  21. data/files/reveal.js/css/theme/white.css +19 -38
  22. data/files/reveal.js/demo.html +13 -7
  23. data/files/reveal.js/js/reveal.js +707 -242
  24. data/files/reveal.js/lib/js/head.min.js +1 -4
  25. data/files/reveal.js/package.json +16 -17
  26. data/files/reveal.js/plugin/highlight/highlight.js +3 -4
  27. data/files/reveal.js/plugin/markdown/example.html +7 -0
  28. data/files/reveal.js/plugin/markdown/example.md +5 -0
  29. data/files/reveal.js/plugin/markdown/markdown.js +1 -1
  30. data/files/reveal.js/plugin/math/math.js +4 -4
  31. data/files/reveal.js/plugin/multiplex/master.js +4 -1
  32. data/files/reveal.js/plugin/multiplex/package.json +1 -1
  33. data/files/reveal.js/plugin/notes/notes.html +48 -2
  34. data/files/reveal.js/plugin/notes/notes.js +9 -17
  35. data/files/reveal.js/plugin/print-pdf/print-pdf.js +9 -11
  36. data/files/reveal.js/plugin/search/search.js +127 -117
  37. data/files/reveal.js/plugin/zoom-js/zoom.js +16 -32
  38. data/files/reveal.js/test/qunit-2.5.0.css +436 -0
  39. data/files/reveal.js/test/qunit-2.5.0.js +5188 -0
  40. data/files/reveal.js/test/test-markdown-element-attributes.html +2 -2
  41. data/files/reveal.js/test/test-markdown-element-attributes.js +21 -23
  42. data/files/reveal.js/test/test-markdown-external.html +3 -3
  43. data/files/reveal.js/test/test-markdown-external.js +7 -11
  44. data/files/reveal.js/test/test-markdown-options.html +2 -2
  45. data/files/reveal.js/test/test-markdown-options.js +7 -6
  46. data/files/reveal.js/test/test-markdown-slide-attributes.html +2 -2
  47. data/files/reveal.js/test/test-markdown-slide-attributes.js +23 -26
  48. data/files/reveal.js/test/test-markdown.html +2 -2
  49. data/files/reveal.js/test/test-markdown.js +2 -6
  50. data/files/reveal.js/test/test-pdf.html +2 -2
  51. data/files/reveal.js/test/test-pdf.js +2 -5
  52. data/files/reveal.js/test/test.html +2 -2
  53. data/files/reveal.js/test/test.js +184 -188
  54. data/lib/reveal-ck/version.rb +1 -1
  55. metadata +4 -4
  56. data/files/reveal.js/test/qunit-1.12.0.css +0 -244
  57. data/files/reveal.js/test/qunit-1.12.0.js +0 -2212
@@ -28,8 +28,8 @@ body {
28
28
  text-shadow: none;
29
29
  }
30
30
 
31
- .reveal .slides>section,
32
- .reveal .slides>section>section {
31
+ .reveal .slides section,
32
+ .reveal .slides section>section {
33
33
  line-height: 1.3;
34
34
  font-weight: inherit;
35
35
  }
@@ -134,11 +134,6 @@ body {
134
134
  margin-left: 40px;
135
135
  }
136
136
 
137
- .reveal q,
138
- .reveal blockquote {
139
- quotes: none;
140
- }
141
-
142
137
  .reveal blockquote {
143
138
  display: block;
144
139
  position: relative;
@@ -174,8 +169,10 @@ body {
174
169
 
175
170
  box-shadow: 0px 0px 6px rgba(0,0,0,0.3);
176
171
  }
172
+
177
173
  .reveal code {
178
174
  font-family: monospace;
175
+ text-transform: none;
179
176
  }
180
177
 
181
178
  .reveal pre code {
@@ -220,9 +217,11 @@ body {
220
217
 
221
218
  .reveal sup {
222
219
  vertical-align: super;
220
+ font-size: smaller;
223
221
  }
224
222
  .reveal sub {
225
223
  vertical-align: sub;
224
+ font-size: smaller;
226
225
  }
227
226
 
228
227
  .reveal small {
@@ -297,40 +296,8 @@ body {
297
296
  * NAVIGATION CONTROLS
298
297
  *********************************************/
299
298
 
300
- .reveal .controls .navigate-left,
301
- .reveal .controls .navigate-left.enabled {
302
- border-right-color: $linkColor;
303
- }
304
-
305
- .reveal .controls .navigate-right,
306
- .reveal .controls .navigate-right.enabled {
307
- border-left-color: $linkColor;
308
- }
309
-
310
- .reveal .controls .navigate-up,
311
- .reveal .controls .navigate-up.enabled {
312
- border-bottom-color: $linkColor;
313
- }
314
-
315
- .reveal .controls .navigate-down,
316
- .reveal .controls .navigate-down.enabled {
317
- border-top-color: $linkColor;
318
- }
319
-
320
- .reveal .controls .navigate-left.enabled:hover {
321
- border-right-color: $linkColorHover;
322
- }
323
-
324
- .reveal .controls .navigate-right.enabled:hover {
325
- border-left-color: $linkColorHover;
326
- }
327
-
328
- .reveal .controls .navigate-up.enabled:hover {
329
- border-bottom-color: $linkColorHover;
330
- }
331
-
332
- .reveal .controls .navigate-down.enabled:hover {
333
- border-top-color: $linkColorHover;
299
+ .reveal .controls {
300
+ color: $linkColor;
334
301
  }
335
302
 
336
303
 
@@ -340,13 +307,19 @@ body {
340
307
 
341
308
  .reveal .progress {
342
309
  background: rgba(0,0,0,0.2);
310
+ color: $linkColor;
343
311
  }
344
312
  .reveal .progress span {
345
- background: $linkColor;
346
-
347
313
  -webkit-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
348
314
  -moz-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
349
- transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
315
+ transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
350
316
  }
351
317
 
352
-
318
+ /*********************************************
319
+ * PRINT BACKGROUND
320
+ *********************************************/
321
+ @media print {
322
+ .backgrounds {
323
+ background-color: $backgroundColor;
324
+ }
325
+ }
@@ -30,8 +30,8 @@ body {
30
30
  background: #98bdef;
31
31
  text-shadow: none; }
32
32
 
33
- .reveal .slides > section,
34
- .reveal .slides > section > section {
33
+ .reveal .slides section,
34
+ .reveal .slides section > section {
35
35
  line-height: 1.3;
36
36
  font-weight: inherit; }
37
37
 
@@ -122,10 +122,6 @@ body {
122
122
  .reveal dd {
123
123
  margin-left: 40px; }
124
124
 
125
- .reveal q,
126
- .reveal blockquote {
127
- quotes: none; }
128
-
129
125
  .reveal blockquote {
130
126
  display: block;
131
127
  position: relative;
@@ -156,7 +152,8 @@ body {
156
152
  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
157
153
 
158
154
  .reveal code {
159
- font-family: monospace; }
155
+ font-family: monospace;
156
+ text-transform: none; }
160
157
 
161
158
  .reveal pre code {
162
159
  display: block;
@@ -192,10 +189,12 @@ body {
192
189
  border-bottom: none; }
193
190
 
194
191
  .reveal sup {
195
- vertical-align: super; }
192
+ vertical-align: super;
193
+ font-size: smaller; }
196
194
 
197
195
  .reveal sub {
198
- vertical-align: sub; }
196
+ vertical-align: sub;
197
+ font-size: smaller; }
199
198
 
200
199
  .reveal small {
201
200
  display: inline-block;
@@ -251,42 +250,24 @@ body {
251
250
  /*********************************************
252
251
  * NAVIGATION CONTROLS
253
252
  *********************************************/
254
- .reveal .controls .navigate-left,
255
- .reveal .controls .navigate-left.enabled {
256
- border-right-color: #2a76dd; }
257
-
258
- .reveal .controls .navigate-right,
259
- .reveal .controls .navigate-right.enabled {
260
- border-left-color: #2a76dd; }
261
-
262
- .reveal .controls .navigate-up,
263
- .reveal .controls .navigate-up.enabled {
264
- border-bottom-color: #2a76dd; }
265
-
266
- .reveal .controls .navigate-down,
267
- .reveal .controls .navigate-down.enabled {
268
- border-top-color: #2a76dd; }
269
-
270
- .reveal .controls .navigate-left.enabled:hover {
271
- border-right-color: #6ca0e8; }
272
-
273
- .reveal .controls .navigate-right.enabled:hover {
274
- border-left-color: #6ca0e8; }
275
-
276
- .reveal .controls .navigate-up.enabled:hover {
277
- border-bottom-color: #6ca0e8; }
278
-
279
- .reveal .controls .navigate-down.enabled:hover {
280
- border-top-color: #6ca0e8; }
253
+ .reveal .controls {
254
+ color: #2a76dd; }
281
255
 
282
256
  /*********************************************
283
257
  * PROGRESS BAR
284
258
  *********************************************/
285
259
  .reveal .progress {
286
- background: rgba(0, 0, 0, 0.2); }
260
+ background: rgba(0, 0, 0, 0.2);
261
+ color: #2a76dd; }
287
262
 
288
263
  .reveal .progress span {
289
- background: #2a76dd;
290
264
  -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
291
265
  -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
292
266
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
267
+
268
+ /*********************************************
269
+ * PRINT BACKGROUND
270
+ *********************************************/
271
+ @media print {
272
+ .backgrounds {
273
+ background-color: #fff; } }
@@ -44,7 +44,7 @@
44
44
  <h1>Reveal.js</h1>
45
45
  <h3>The HTML Presentation Framework</h3>
46
46
  <p>
47
- <small>Created by <a href="http://hakim.se">Hakim El Hattab</a> / <a href="http://twitter.com/hakimel">@hakimel</a></small>
47
+ <small>Created by <a href="http://hakim.se">Hakim El Hattab</a> and <a href="https://github.com/hakimel/reveal.js/graphs/contributors">contributors</a></small>
48
48
  </p>
49
49
  </section>
50
50
 
@@ -83,7 +83,7 @@
83
83
  <section>
84
84
  <h2>Slides</h2>
85
85
  <p>
86
- Not a coder? Not a problem. There's a fully-featured visual editor for authoring these, try it out at <a href="http://slides.com" target="_blank">http://slides.com</a>.
86
+ Not a coder? Not a problem. There's a fully-featured visual editor for authoring these, try it out at <a href="https://slides.com" target="_blank">https://slides.com</a>.
87
87
  </p>
88
88
  </section>
89
89
 
@@ -139,8 +139,14 @@
139
139
  <p class="fragment grow">grow</p>
140
140
  <p class="fragment shrink">shrink</p>
141
141
  <p class="fragment fade-out">fade-out</p>
142
- <p class="fragment fade-up">fade-up (also down, left and right!)</p>
143
- <p class="fragment current-visible">current-visible</p>
142
+ <p>
143
+ <span style="display: inline-block;" class="fragment fade-right">fade-right, </span>
144
+ <span style="display: inline-block;" class="fragment fade-up">up, </span>
145
+ <span style="display: inline-block;" class="fragment fade-down">down, </span>
146
+ <span style="display: inline-block;" class="fragment fade-left">left</span>
147
+ </p>
148
+ <p class="fragment fade-in-then-out">fade-in-then-out</p>
149
+ <p class="fragment fade-in-then-semi-out">fade-in-then-semi-out</p>
144
150
  <p>Highlight <span class="fragment highlight-red">red</span> <span class="fragment highlight-blue">blue</span> <span class="fragment highlight-green">green</span></p>
145
151
  </section>
146
152
  </section>
@@ -295,8 +301,7 @@ function linkify( selector ) {
295
301
  <section>
296
302
  <h2>Clever Quotes</h2>
297
303
  <p>
298
- These guys come in two forms, inline: <q cite="http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations">
299
- &ldquo;The nice thing about standards is that there are so many to choose from&rdquo;</q> and block:
304
+ These guys come in two forms, inline: <q cite="http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations">The nice thing about standards is that there are so many to choose from</q> and block:
300
305
  </p>
301
306
  <blockquote cite="http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations">
302
307
  &ldquo;For years there has been a theory that millions of monkeys typing at random on millions of typewriters would
@@ -370,7 +375,7 @@ Reveal.addEventListener( 'customevent', function() {
370
375
  <section style="text-align: left;">
371
376
  <h1>THE END</h1>
372
377
  <p>
373
- - <a href="http://slides.com">Try the online editor</a> <br>
378
+ - <a href="https://slides.com">Try the online editor</a> <br>
374
379
  - <a href="https://github.com/hakimel/reveal.js">Source code &amp; documentation</a>
375
380
  </p>
376
381
  </section>
@@ -399,6 +404,7 @@ Reveal.addEventListener( 'customevent', function() {
399
404
  { src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
400
405
  { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
401
406
  { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
407
+ { src: 'plugin/search/search.js', async: true },
402
408
  { src: 'plugin/zoom-js/zoom.js', async: true },
403
409
  { src: 'plugin/notes/notes.js', async: true }
404
410
  ]
@@ -1,9 +1,9 @@
1
1
  /*!
2
2
  * reveal.js
3
- * http://lab.hakim.se/reveal-js
3
+ * http://revealjs.com
4
4
  * MIT licensed
5
5
  *
6
- * Copyright (C) 2017 Hakim El Hattab, http://hakim.se
6
+ * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
7
7
  */
8
8
  (function( root, factory ) {
9
9
  if( typeof define === 'function' && define.amd ) {
@@ -26,7 +26,7 @@
26
26
  var Reveal;
27
27
 
28
28
  // The reveal.js version
29
- var VERSION = '3.5.0';
29
+ var VERSION = '3.7.0';
30
30
 
31
31
  var SLIDES_SELECTOR = '.slides section',
32
32
  HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
@@ -49,15 +49,30 @@
49
49
  minScale: 0.2,
50
50
  maxScale: 2.0,
51
51
 
52
- // Display controls in the bottom right corner
52
+ // Display presentation control arrows
53
53
  controls: true,
54
54
 
55
+ // Help the user learn the controls by providing hints, for example by
56
+ // bouncing the down arrow when they first encounter a vertical slide
57
+ controlsTutorial: true,
58
+
59
+ // Determines where controls appear, "edges" or "bottom-right"
60
+ controlsLayout: 'bottom-right',
61
+
62
+ // Visibility rule for backwards navigation arrows; "faded", "hidden"
63
+ // or "visible"
64
+ controlsBackArrows: 'faded',
65
+
55
66
  // Display a presentation progress bar
56
67
  progress: true,
57
68
 
58
69
  // Display the page number of the current slide
59
70
  slideNumber: false,
60
71
 
72
+ // Use 1 based indexing for # links to match slide number (default is zero
73
+ // based)
74
+ hashOneBasedIndex: false,
75
+
61
76
  // Determine which displays to show the slide number on
62
77
  showSlideNumber: 'all',
63
78
 
@@ -73,6 +88,10 @@
73
88
  // Enable the slide overview mode
74
89
  overview: true,
75
90
 
91
+ // Disables the default reveal.js slide layout so that you can use
92
+ // custom CSS layout
93
+ disableLayout: false,
94
+
76
95
  // Vertical centering of slides
77
96
  center: true,
78
97
 
@@ -91,6 +110,10 @@
91
110
  // Turns fragments on and off globally
92
111
  fragments: true,
93
112
 
113
+ // Flags whether to include the current fragment in the URL,
114
+ // so that reloading brings you to the same fragment position
115
+ fragmentInURL: false,
116
+
94
117
  // Flags if the presentation is running in an embedded mode,
95
118
  // i.e. contained within a limited portion of the screen
96
119
  embedded: false,
@@ -106,14 +129,16 @@
106
129
  showNotes: false,
107
130
 
108
131
  // Global override for autolaying embedded media (video/audio/iframe)
109
- // - null: Media will only autoplay if data-autoplay is present
110
- // - true: All media will autoplay, regardless of individual setting
111
- // - false: No media will autoplay, regardless of individual setting
132
+ // - null: Media will only autoplay if data-autoplay is present
133
+ // - true: All media will autoplay, regardless of individual setting
134
+ // - false: No media will autoplay, regardless of individual setting
112
135
  autoPlayMedia: null,
113
136
 
114
- // Number of milliseconds between automatically proceeding to the
115
- // next slide, disabled when set to 0, this value can be overwritten
116
- // by using a data-autoslide attribute on your slides
137
+ // Controls automatic progression to the next slide
138
+ // - 0: Auto-sliding only happens if the data-autoslide HTML attribute
139
+ // is present on the current slide or fragment
140
+ // - 1+: All slides will progress automatically at the given interval
141
+ // - false: No auto-sliding, even if data-autoslide is present
117
142
  autoSlide: 0,
118
143
 
119
144
  // Stop auto-sliding after user input
@@ -122,6 +147,11 @@
122
147
  // Use this method for navigation when auto-sliding (defaults to navigateNext)
123
148
  autoSlideMethod: null,
124
149
 
150
+ // Specify the average time in seconds that you think you will spend
151
+ // presenting each slide. This is used to show a pacing timer in the
152
+ // speaker view
153
+ defaultTiming: null,
154
+
125
155
  // Enable slide navigation via mouse wheel
126
156
  mouseWheel: false,
127
157
 
@@ -132,6 +162,8 @@
132
162
  hideAddressBar: true,
133
163
 
134
164
  // Opens links in an iframe preview overlay
165
+ // Add `data-preview-link` and `data-preview-link="false"` to customise each link
166
+ // individually
135
167
  previewLinks: false,
136
168
 
137
169
  // Exposes the reveal.js API through window.postMessage
@@ -158,6 +190,12 @@
158
190
  // Parallax background size
159
191
  parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
160
192
 
193
+ // Parallax background repeat
194
+ parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
195
+
196
+ // Parallax background position
197
+ parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left"
198
+
161
199
  // Amount of pixels to move the parallax background per slide step
162
200
  parallaxBackgroundHorizontal: null,
163
201
  parallaxBackgroundVertical: null,
@@ -166,6 +204,9 @@
166
204
  // to PDF, unlimited by default
167
205
  pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
168
206
 
207
+ // Prints each fragment on a separate slide
208
+ pdfSeparateFragments: true,
209
+
169
210
  // Offset used to reduce the height of content within exported PDF pages.
170
211
  // This exists to account for environment differences based on how you
171
212
  // print to PDF. CLI printing options, like phantomjs and wkpdf, can end
@@ -207,6 +248,10 @@
207
248
 
208
249
  previousBackground,
209
250
 
251
+ // Remember which directions that the user has navigated towards
252
+ hasNavigatedRight = false,
253
+ hasNavigatedDown = false,
254
+
210
255
  // Slides may hold a data-state attribute which we pick up and apply
211
256
  // as a class to the body. This list contains the combined state of
212
257
  // all current slides.
@@ -272,7 +317,10 @@
272
317
  'B , .': 'Pause',
273
318
  'F': 'Fullscreen',
274
319
  'ESC, O': 'Slide overview'
275
- };
320
+ },
321
+
322
+ // Holds custom key code mappings
323
+ registeredKeyBindings = {};
276
324
 
277
325
  /**
278
326
  * Starts up the presentation if the client is capable.
@@ -374,13 +422,13 @@
374
422
 
375
423
  }
376
424
 
377
- /**
378
- * Loads the dependencies of reveal.js. Dependencies are
379
- * defined via the configuration option 'dependencies'
380
- * and will be loaded prior to starting/binding reveal.js.
381
- * Some dependencies may have an 'async' flag, if so they
382
- * will load after reveal.js has been started up.
383
- */
425
+ /**
426
+ * Loads the dependencies of reveal.js. Dependencies are
427
+ * defined via the configuration option 'dependencies'
428
+ * and will be loaded prior to starting/binding reveal.js.
429
+ * Some dependencies may have an 'async' flag, if so they
430
+ * will load after reveal.js has been started up.
431
+ */
384
432
  function load() {
385
433
 
386
434
  var scripts = [],
@@ -398,7 +446,7 @@
398
446
  }
399
447
 
400
448
  function loadScript( s ) {
401
- head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
449
+ head.ready( s.src.match( /([\w\d_\-]*)\.?js(\?[\w\d.=&]*)?$|[^\\\/]*$/i )[0], function() {
402
450
  // Extension may contain callback functions
403
451
  if( typeof s.callback === 'function' ) {
404
452
  s.callback.apply( this );
@@ -444,6 +492,8 @@
444
492
  */
445
493
  function start() {
446
494
 
495
+ loaded = true;
496
+
447
497
  // Make sure we've got all the DOM elements we need
448
498
  setupDOM();
449
499
 
@@ -471,8 +521,6 @@
471
521
  // Enable transitions now that we're loaded
472
522
  dom.slides.classList.remove( 'no-transition' );
473
523
 
474
- loaded = true;
475
-
476
524
  dom.wrapper.classList.add( 'ready' );
477
525
 
478
526
  dispatchEvent( 'ready', {
@@ -508,6 +556,20 @@
508
556
  // Prevent transitions while we're loading
509
557
  dom.slides.classList.add( 'no-transition' );
510
558
 
559
+ if( isMobileDevice ) {
560
+ dom.wrapper.classList.add( 'no-hover' );
561
+ }
562
+ else {
563
+ dom.wrapper.classList.remove( 'no-hover' );
564
+ }
565
+
566
+ if( /iphone/gi.test( UA ) ) {
567
+ dom.wrapper.classList.add( 'ua-iphone' );
568
+ }
569
+ else {
570
+ dom.wrapper.classList.remove( 'ua-iphone' );
571
+ }
572
+
511
573
  // Background element
512
574
  dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
513
575
 
@@ -516,11 +578,11 @@
516
578
  dom.progressbar = dom.progress.querySelector( 'span' );
517
579
 
518
580
  // Arrow controls
519
- createSingletonNode( dom.wrapper, 'aside', 'controls',
520
- '<button class="navigate-left" aria-label="previous slide"></button>' +
521
- '<button class="navigate-right" aria-label="next slide"></button>' +
522
- '<button class="navigate-up" aria-label="above slide"></button>' +
523
- '<button class="navigate-down" aria-label="below slide"></button>' );
581
+ dom.controls = createSingletonNode( dom.wrapper, 'aside', 'controls',
582
+ '<button class="navigate-left" aria-label="previous slide"><div class="controls-arrow"></div></button>' +
583
+ '<button class="navigate-right" aria-label="next slide"><div class="controls-arrow"></div></button>' +
584
+ '<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>' +
585
+ '<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>' );
524
586
 
525
587
  // Slide number
526
588
  dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
@@ -531,10 +593,8 @@
531
593
  dom.speakerNotes.setAttribute( 'tabindex', '0' );
532
594
 
533
595
  // Overlay graphic which is displayed during the paused mode
534
- createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
535
-
536
- // Cache references to elements
537
- dom.controls = document.querySelector( '.reveal .controls' );
596
+ dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', '<button class="resume-button">Resume presentation</button>' );
597
+ dom.resumeButton = dom.pauseOverlay.querySelector( '.resume-button' );
538
598
 
539
599
  dom.wrapper.setAttribute( 'role', 'application' );
540
600
 
@@ -546,6 +606,10 @@
546
606
  dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
547
607
  dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
548
608
 
609
+ // The right and down arrows in the standard reveal.js controls
610
+ dom.controlsRightArrow = dom.controls.querySelector( '.navigate-right' );
611
+ dom.controlsDownArrow = dom.controls.querySelector( '.navigate-down' );
612
+
549
613
  dom.statusDiv = createStatusDiv();
550
614
  }
551
615
 
@@ -727,13 +791,58 @@
727
791
  numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );
728
792
  page.appendChild( numberElement );
729
793
  }
730
- }
731
794
 
732
- } );
795
+ // Copy page and show fragments one after another
796
+ if( config.pdfSeparateFragments ) {
797
+
798
+ // Each fragment 'group' is an array containing one or more
799
+ // fragments. Multiple fragments that appear at the same time
800
+ // are part of the same group.
801
+ var fragmentGroups = sortFragments( page.querySelectorAll( '.fragment' ), true );
802
+
803
+ var previousFragmentStep;
804
+ var previousPage;
805
+
806
+ fragmentGroups.forEach( function( fragments ) {
807
+
808
+ // Remove 'current-fragment' from the previous group
809
+ if( previousFragmentStep ) {
810
+ previousFragmentStep.forEach( function( fragment ) {
811
+ fragment.classList.remove( 'current-fragment' );
812
+ } );
813
+ }
814
+
815
+ // Show the fragments for the current index
816
+ fragments.forEach( function( fragment ) {
817
+ fragment.classList.add( 'visible', 'current-fragment' );
818
+ } );
819
+
820
+ // Create a separate page for the current fragment state
821
+ var clonedPage = page.cloneNode( true );
822
+ page.parentNode.insertBefore( clonedPage, ( previousPage || page ).nextSibling );
823
+
824
+ previousFragmentStep = fragments;
825
+ previousPage = clonedPage;
826
+
827
+ } );
828
+
829
+ // Reset the first/original page so that all fragments are hidden
830
+ fragmentGroups.forEach( function( fragments ) {
831
+ fragments.forEach( function( fragment ) {
832
+ fragment.classList.remove( 'visible', 'current-fragment' );
833
+ } );
834
+ } );
835
+
836
+ }
837
+ // Show all fragments
838
+ else {
839
+ toArray( page.querySelectorAll( '.fragment:not(.fade-out)' ) ).forEach( function( fragment ) {
840
+ fragment.classList.add( 'visible' );
841
+ } );
842
+ }
843
+
844
+ }
733
845
 
734
- // Show all fragments
735
- toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
736
- fragment.classList.add( 'visible' );
737
846
  } );
738
847
 
739
848
  // Notify subscribers that the PDF layout is good to go
@@ -789,7 +898,7 @@
789
898
 
790
899
  // If no node was found, create it now
791
900
  var node = document.createElement( tagname );
792
- node.classList.add( classname );
901
+ node.className = classname;
793
902
  if( typeof innerHTML === 'string' ) {
794
903
  node.innerHTML = innerHTML;
795
904
  }
@@ -833,6 +942,8 @@
833
942
 
834
943
  dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
835
944
  dom.background.style.backgroundSize = config.parallaxBackgroundSize;
945
+ dom.background.style.backgroundRepeat = config.parallaxBackgroundRepeat;
946
+ dom.background.style.backgroundPosition = config.parallaxBackgroundPosition;
836
947
 
837
948
  // Make sure the below properties are set on the element - these properties are
838
949
  // needed for proper transitions to be set on the element via CSS. To remove
@@ -862,6 +973,57 @@
862
973
  */
863
974
  function createBackground( slide, container ) {
864
975
 
976
+
977
+ // Main slide background element
978
+ var element = document.createElement( 'div' );
979
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
980
+
981
+ // Inner background element that wraps images/videos/iframes
982
+ var contentElement = document.createElement( 'div' );
983
+ contentElement.className = 'slide-background-content';
984
+
985
+ element.appendChild( contentElement );
986
+ container.appendChild( element );
987
+
988
+ slide.slideBackgroundElement = element;
989
+ slide.slideBackgroundContentElement = contentElement;
990
+
991
+ // Syncs the background to reflect all current background settings
992
+ syncBackground( slide );
993
+
994
+ return element;
995
+
996
+ }
997
+
998
+ /**
999
+ * Renders all of the visual properties of a slide background
1000
+ * based on the various background attributes.
1001
+ *
1002
+ * @param {HTMLElement} slide
1003
+ */
1004
+ function syncBackground( slide ) {
1005
+
1006
+ var element = slide.slideBackgroundElement,
1007
+ contentElement = slide.slideBackgroundContentElement;
1008
+
1009
+ // Reset the prior background state in case this is not the
1010
+ // initial sync
1011
+ slide.classList.remove( 'has-dark-background' );
1012
+ slide.classList.remove( 'has-light-background' );
1013
+
1014
+ element.removeAttribute( 'data-loaded' );
1015
+ element.removeAttribute( 'data-background-hash' );
1016
+ element.removeAttribute( 'data-background-size' );
1017
+ element.removeAttribute( 'data-background-transition' );
1018
+ element.style.backgroundColor = '';
1019
+
1020
+ contentElement.style.backgroundSize = '';
1021
+ contentElement.style.backgroundRepeat = '';
1022
+ contentElement.style.backgroundPosition = '';
1023
+ contentElement.style.backgroundImage = '';
1024
+ contentElement.style.opacity = '';
1025
+ contentElement.innerHTML = '';
1026
+
865
1027
  var data = {
866
1028
  background: slide.getAttribute( 'data-background' ),
867
1029
  backgroundSize: slide.getAttribute( 'data-background-size' ),
@@ -871,17 +1033,13 @@
871
1033
  backgroundColor: slide.getAttribute( 'data-background-color' ),
872
1034
  backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
873
1035
  backgroundPosition: slide.getAttribute( 'data-background-position' ),
874
- backgroundTransition: slide.getAttribute( 'data-background-transition' )
1036
+ backgroundTransition: slide.getAttribute( 'data-background-transition' ),
1037
+ backgroundOpacity: slide.getAttribute( 'data-background-opacity' )
875
1038
  };
876
1039
 
877
- var element = document.createElement( 'div' );
878
-
879
- // Carry over custom classes from the slide to the background
880
- element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
881
-
882
1040
  if( data.background ) {
883
1041
  // Auto-wrap image urls in url(...)
884
- if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#]|$)/gi.test( data.background ) ) {
1042
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) {
885
1043
  slide.setAttribute( 'data-background-image', data.background );
886
1044
  }
887
1045
  else {
@@ -901,24 +1059,20 @@
901
1059
  data.backgroundColor +
902
1060
  data.backgroundRepeat +
903
1061
  data.backgroundPosition +
904
- data.backgroundTransition );
1062
+ data.backgroundTransition +
1063
+ data.backgroundOpacity );
905
1064
  }
906
1065
 
907
1066
  // Additional and optional background properties
908
- if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
909
1067
  if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );
910
1068
  if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
911
- if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
912
- if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
913
1069
  if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
914
1070
 
915
- container.appendChild( element );
916
-
917
- // If backgrounds are being recreated, clear old classes
918
- slide.classList.remove( 'has-dark-background' );
919
- slide.classList.remove( 'has-light-background' );
920
-
921
- slide.slideBackgroundElement = element;
1071
+ // Background image options are set on the content wrapper
1072
+ if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;
1073
+ if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat;
1074
+ if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;
1075
+ if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;
922
1076
 
923
1077
  // If this slide has a background color, add a class that
924
1078
  // signals if it is light or dark. If the slide has no background
@@ -940,8 +1094,6 @@
940
1094
  }
941
1095
  }
942
1096
 
943
- return element;
944
-
945
1097
  }
946
1098
 
947
1099
  /**
@@ -982,14 +1134,22 @@
982
1134
  */
983
1135
  function configure( options ) {
984
1136
 
985
- var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
986
-
987
- dom.wrapper.classList.remove( config.transition );
1137
+ var oldTransition = config.transition;
988
1138
 
989
1139
  // New config options may be passed when this method
990
1140
  // is invoked through the API after initialization
991
1141
  if( typeof options === 'object' ) extend( config, options );
992
1142
 
1143
+ // Abort if reveal.js hasn't finished loading, config
1144
+ // changes will be applied automatically once loading
1145
+ // finishes
1146
+ if( loaded === false ) return;
1147
+
1148
+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
1149
+
1150
+ // Remove the previously configured transition class
1151
+ dom.wrapper.classList.remove( oldTransition );
1152
+
993
1153
  // Force linear transition based on browser capabilities
994
1154
  if( features.transforms3d === false ) config.transition = 'linear';
995
1155
 
@@ -1001,6 +1161,9 @@
1001
1161
  dom.controls.style.display = config.controls ? 'block' : 'none';
1002
1162
  dom.progress.style.display = config.progress ? 'block' : 'none';
1003
1163
 
1164
+ dom.controls.setAttribute( 'data-controls-layout', config.controlsLayout );
1165
+ dom.controls.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );
1166
+
1004
1167
  if( config.shuffle ) {
1005
1168
  shuffle();
1006
1169
  }
@@ -1025,12 +1188,8 @@
1025
1188
  }
1026
1189
 
1027
1190
  if( config.showNotes ) {
1028
- dom.speakerNotes.classList.add( 'visible' );
1029
1191
  dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );
1030
1192
  }
1031
- else {
1032
- dom.speakerNotes.classList.remove( 'visible' );
1033
- }
1034
1193
 
1035
1194
  if( config.mouseWheel ) {
1036
1195
  document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
@@ -1111,13 +1270,8 @@
1111
1270
  window.addEventListener( 'resize', onWindowResize, false );
1112
1271
 
1113
1272
  if( config.touch ) {
1114
- dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
1115
- dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
1116
- dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
1117
-
1118
- // Support pointer-style touch interaction as well
1119
- if( window.navigator.pointerEnabled ) {
1120
- // IE 11 uses un-prefixed version of pointer events
1273
+ if( 'onpointerdown' in window ) {
1274
+ // Use W3C pointer events
1121
1275
  dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
1122
1276
  dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
1123
1277
  dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
@@ -1128,6 +1282,12 @@
1128
1282
  dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
1129
1283
  dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
1130
1284
  }
1285
+ else {
1286
+ // Fall back to touch events
1287
+ dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
1288
+ dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
1289
+ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
1290
+ }
1131
1291
  }
1132
1292
 
1133
1293
  if( config.keyboard ) {
@@ -1139,6 +1299,8 @@
1139
1299
  dom.progress.addEventListener( 'click', onProgressClicked, false );
1140
1300
  }
1141
1301
 
1302
+ dom.resumeButton.addEventListener( 'click', resume, false );
1303
+
1142
1304
  if( config.focusBodyOnPageVisibilityChange ) {
1143
1305
  var visibilityChange;
1144
1306
 
@@ -1190,22 +1352,19 @@
1190
1352
  window.removeEventListener( 'hashchange', onWindowHashChange, false );
1191
1353
  window.removeEventListener( 'resize', onWindowResize, false );
1192
1354
 
1355
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
1356
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
1357
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
1358
+
1359
+ dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
1360
+ dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
1361
+ dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
1362
+
1193
1363
  dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
1194
1364
  dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
1195
1365
  dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
1196
1366
 
1197
- // IE11
1198
- if( window.navigator.pointerEnabled ) {
1199
- dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
1200
- dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
1201
- dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
1202
- }
1203
- // IE10
1204
- else if( window.navigator.msPointerEnabled ) {
1205
- dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
1206
- dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
1207
- dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
1208
- }
1367
+ dom.resumeButton.removeEventListener( 'click', resume, false );
1209
1368
 
1210
1369
  if ( config.progress && dom.progress ) {
1211
1370
  dom.progress.removeEventListener( 'click', onProgressClicked, false );
@@ -1222,6 +1381,38 @@
1222
1381
 
1223
1382
  }
1224
1383
 
1384
+ /**
1385
+ * Add a custom key binding with optional description to
1386
+ * be added to the help screen.
1387
+ */
1388
+ function addKeyBinding( binding, callback ) {
1389
+
1390
+ if( typeof binding === 'object' && binding.keyCode ) {
1391
+ registeredKeyBindings[binding.keyCode] = {
1392
+ callback: callback,
1393
+ key: binding.key,
1394
+ description: binding.description
1395
+ };
1396
+ }
1397
+ else {
1398
+ registeredKeyBindings[binding] = {
1399
+ callback: callback,
1400
+ key: null,
1401
+ description: null
1402
+ };
1403
+ }
1404
+
1405
+ }
1406
+
1407
+ /**
1408
+ * Removes the specified custom key binding.
1409
+ */
1410
+ function removeKeyBinding( keyCode ) {
1411
+
1412
+ delete registeredKeyBindings[keyCode];
1413
+
1414
+ }
1415
+
1225
1416
  /**
1226
1417
  * Extend object a with the properties of object b.
1227
1418
  * If there's a conflict, object b takes precedence.
@@ -1235,6 +1426,8 @@
1235
1426
  a[ i ] = b[ i ];
1236
1427
  }
1237
1428
 
1429
+ return a;
1430
+
1238
1431
  }
1239
1432
 
1240
1433
  /**
@@ -1261,7 +1454,7 @@
1261
1454
  if( value === 'null' ) return null;
1262
1455
  else if( value === 'true' ) return true;
1263
1456
  else if( value === 'false' ) return false;
1264
- else if( value.match( /^[\d\.]+$/ ) ) return parseFloat( value );
1457
+ else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
1265
1458
  }
1266
1459
 
1267
1460
  return value;
@@ -1497,6 +1690,15 @@
1497
1690
 
1498
1691
  }
1499
1692
 
1693
+ /**
1694
+ * Check if this instance is being used to print a PDF with fragments.
1695
+ */
1696
+ function isPrintingPDFFragments() {
1697
+
1698
+ return ( /print-pdf-fragments/gi ).test( window.location.search );
1699
+
1700
+ }
1701
+
1500
1702
  /**
1501
1703
  * Hides the address bar if we're on a mobile device.
1502
1704
  */
@@ -1707,6 +1909,13 @@
1707
1909
  html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
1708
1910
  }
1709
1911
 
1912
+ // Add custom key bindings that have associated descriptions
1913
+ for( var binding in registeredKeyBindings ) {
1914
+ if( registeredKeyBindings[binding].key && registeredKeyBindings[binding].description ) {
1915
+ html += '<tr><td>' + registeredKeyBindings[binding].key + '</td><td>' + registeredKeyBindings[binding].description + '</td></tr>';
1916
+ }
1917
+ }
1918
+
1710
1919
  html += '</table>';
1711
1920
 
1712
1921
  dom.overlay.innerHTML = [
@@ -1751,76 +1960,80 @@
1751
1960
 
1752
1961
  if( dom.wrapper && !isPrintingPDF() ) {
1753
1962
 
1754
- var size = getComputedSlideSize();
1963
+ if( !config.disableLayout ) {
1755
1964
 
1756
- // Layout the contents of the slides
1757
- layoutSlideContents( config.width, config.height );
1965
+ var size = getComputedSlideSize();
1758
1966
 
1759
- dom.slides.style.width = size.width + 'px';
1760
- dom.slides.style.height = size.height + 'px';
1967
+ // Layout the contents of the slides
1968
+ layoutSlideContents( config.width, config.height );
1761
1969
 
1762
- // Determine scale of content to fit within available space
1763
- scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
1970
+ dom.slides.style.width = size.width + 'px';
1971
+ dom.slides.style.height = size.height + 'px';
1764
1972
 
1765
- // Respect max/min scale settings
1766
- scale = Math.max( scale, config.minScale );
1767
- scale = Math.min( scale, config.maxScale );
1973
+ // Determine scale of content to fit within available space
1974
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
1768
1975
 
1769
- // Don't apply any scaling styles if scale is 1
1770
- if( scale === 1 ) {
1771
- dom.slides.style.zoom = '';
1772
- dom.slides.style.left = '';
1773
- dom.slides.style.top = '';
1774
- dom.slides.style.bottom = '';
1775
- dom.slides.style.right = '';
1776
- transformSlides( { layout: '' } );
1777
- }
1778
- else {
1779
- // Prefer zoom for scaling up so that content remains crisp.
1780
- // Don't use zoom to scale down since that can lead to shifts
1781
- // in text layout/line breaks.
1782
- if( scale > 1 && features.zoom ) {
1783
- dom.slides.style.zoom = scale;
1976
+ // Respect max/min scale settings
1977
+ scale = Math.max( scale, config.minScale );
1978
+ scale = Math.min( scale, config.maxScale );
1979
+
1980
+ // Don't apply any scaling styles if scale is 1
1981
+ if( scale === 1 ) {
1982
+ dom.slides.style.zoom = '';
1784
1983
  dom.slides.style.left = '';
1785
1984
  dom.slides.style.top = '';
1786
1985
  dom.slides.style.bottom = '';
1787
1986
  dom.slides.style.right = '';
1788
1987
  transformSlides( { layout: '' } );
1789
1988
  }
1790
- // Apply scale transform as a fallback
1791
1989
  else {
1792
- dom.slides.style.zoom = '';
1793
- dom.slides.style.left = '50%';
1794
- dom.slides.style.top = '50%';
1795
- dom.slides.style.bottom = 'auto';
1796
- dom.slides.style.right = 'auto';
1797
- transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
1990
+ // Prefer zoom for scaling up so that content remains crisp.
1991
+ // Don't use zoom to scale down since that can lead to shifts
1992
+ // in text layout/line breaks.
1993
+ if( scale > 1 && features.zoom ) {
1994
+ dom.slides.style.zoom = scale;
1995
+ dom.slides.style.left = '';
1996
+ dom.slides.style.top = '';
1997
+ dom.slides.style.bottom = '';
1998
+ dom.slides.style.right = '';
1999
+ transformSlides( { layout: '' } );
2000
+ }
2001
+ // Apply scale transform as a fallback
2002
+ else {
2003
+ dom.slides.style.zoom = '';
2004
+ dom.slides.style.left = '50%';
2005
+ dom.slides.style.top = '50%';
2006
+ dom.slides.style.bottom = 'auto';
2007
+ dom.slides.style.right = 'auto';
2008
+ transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
2009
+ }
1798
2010
  }
1799
- }
1800
2011
 
1801
- // Select all slides, vertical and horizontal
1802
- var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
2012
+ // Select all slides, vertical and horizontal
2013
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
1803
2014
 
1804
- for( var i = 0, len = slides.length; i < len; i++ ) {
1805
- var slide = slides[ i ];
2015
+ for( var i = 0, len = slides.length; i < len; i++ ) {
2016
+ var slide = slides[ i ];
1806
2017
 
1807
- // Don't bother updating invisible slides
1808
- if( slide.style.display === 'none' ) {
1809
- continue;
1810
- }
2018
+ // Don't bother updating invisible slides
2019
+ if( slide.style.display === 'none' ) {
2020
+ continue;
2021
+ }
1811
2022
 
1812
- if( config.center || slide.classList.contains( 'center' ) ) {
1813
- // Vertical stacks are not centred since their section
1814
- // children will be
1815
- if( slide.classList.contains( 'stack' ) ) {
1816
- slide.style.top = 0;
2023
+ if( config.center || slide.classList.contains( 'center' ) ) {
2024
+ // Vertical stacks are not centred since their section
2025
+ // children will be
2026
+ if( slide.classList.contains( 'stack' ) ) {
2027
+ slide.style.top = 0;
2028
+ }
2029
+ else {
2030
+ slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';
2031
+ }
1817
2032
  }
1818
2033
  else {
1819
- slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';
2034
+ slide.style.top = '';
1820
2035
  }
1821
- }
1822
- else {
1823
- slide.style.top = '';
2036
+
1824
2037
  }
1825
2038
 
1826
2039
  }
@@ -2146,6 +2359,41 @@
2146
2359
 
2147
2360
  }
2148
2361
 
2362
+ /**
2363
+ * Return a hash URL that will resolve to the current slide location.
2364
+ */
2365
+ function locationHash() {
2366
+
2367
+ var url = '/';
2368
+
2369
+ // Attempt to create a named link based on the slide's ID
2370
+ var id = currentSlide ? currentSlide.getAttribute( 'id' ) : null;
2371
+ if( id ) {
2372
+ id = encodeURIComponent( id );
2373
+ }
2374
+
2375
+ var indexf;
2376
+ if( config.fragmentInURL ) {
2377
+ indexf = getIndices().f;
2378
+ }
2379
+
2380
+ // If the current slide has an ID, use that as a named link,
2381
+ // but we don't support named links with a fragment index
2382
+ if( typeof id === 'string' && id.length && indexf === undefined ) {
2383
+ url = '/' + id;
2384
+ }
2385
+ // Otherwise use the /h/v index
2386
+ else {
2387
+ var hashIndexBase = config.hashOneBasedIndex ? 1 : 0;
2388
+ if( indexh > 0 || indexv > 0 || indexf !== undefined ) url += indexh + hashIndexBase;
2389
+ if( indexv > 0 || indexf !== undefined ) url += '/' + (indexv + hashIndexBase );
2390
+ if( indexf !== undefined ) url += '/' + indexf;
2391
+ }
2392
+
2393
+ return url;
2394
+
2395
+ }
2396
+
2149
2397
  /**
2150
2398
  * Checks if the current or specified slide is vertical
2151
2399
  * (nested within another slide).
@@ -2370,16 +2618,7 @@
2370
2618
 
2371
2619
  // Dispatch an event if the slide changed
2372
2620
  var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
2373
- if( slideChanged ) {
2374
- dispatchEvent( 'slidechanged', {
2375
- 'indexh': indexh,
2376
- 'indexv': indexv,
2377
- 'previousSlide': previousSlide,
2378
- 'currentSlide': currentSlide,
2379
- 'origin': o
2380
- } );
2381
- }
2382
- else {
2621
+ if (!slideChanged) {
2383
2622
  // Ensure that the previous slide is never the same as the current
2384
2623
  previousSlide = null;
2385
2624
  }
@@ -2387,7 +2626,7 @@
2387
2626
  // Solves an edge case where the previous slide maintains the
2388
2627
  // 'present' class when navigating between adjacent vertical
2389
2628
  // stacks
2390
- if( previousSlide ) {
2629
+ if( previousSlide && previousSlide !== currentSlide ) {
2391
2630
  previousSlide.classList.remove( 'present' );
2392
2631
  previousSlide.setAttribute( 'aria-hidden', 'true' );
2393
2632
 
@@ -2407,6 +2646,16 @@
2407
2646
  }
2408
2647
  }
2409
2648
 
2649
+ if( slideChanged ) {
2650
+ dispatchEvent( 'slidechanged', {
2651
+ 'indexh': indexh,
2652
+ 'indexv': indexv,
2653
+ 'previousSlide': previousSlide,
2654
+ 'currentSlide': currentSlide,
2655
+ 'origin': o
2656
+ } );
2657
+ }
2658
+
2410
2659
  // Handle embedded content
2411
2660
  if( slideChanged || !previousSlide ) {
2412
2661
  stopEmbeddedContent( previousSlide );
@@ -2463,13 +2712,14 @@
2463
2712
  updateSlideNumber();
2464
2713
  updateSlidesVisibility();
2465
2714
  updateBackground( true );
2715
+ updateNotesVisibility();
2466
2716
  updateNotes();
2467
2717
 
2468
2718
  formatEmbeddedContent();
2469
2719
 
2470
2720
  // Start or stop embedded content depending on global config
2471
2721
  if( config.autoPlayMedia === false ) {
2472
- stopEmbeddedContent( currentSlide );
2722
+ stopEmbeddedContent( currentSlide, { unloadIframes: false } );
2473
2723
  }
2474
2724
  else {
2475
2725
  startEmbeddedContent( currentSlide );
@@ -2481,6 +2731,41 @@
2481
2731
 
2482
2732
  }
2483
2733
 
2734
+ /**
2735
+ * Updates reveal.js to keep in sync with new slide attributes. For
2736
+ * example, if you add a new `data-background-image` you can call
2737
+ * this to have reveal.js render the new background image.
2738
+ *
2739
+ * Similar to #sync() but more efficient when you only need to
2740
+ * refresh a specific slide.
2741
+ *
2742
+ * @param {HTMLElement} slide
2743
+ */
2744
+ function syncSlide( slide ) {
2745
+
2746
+ syncBackground( slide );
2747
+ syncFragments( slide );
2748
+
2749
+ updateBackground();
2750
+ updateNotes();
2751
+
2752
+ loadSlide( slide );
2753
+
2754
+ }
2755
+
2756
+ /**
2757
+ * Formats the fragments on the given slide so that they have
2758
+ * valid indices. Call this if fragments are changed in the DOM
2759
+ * after reveal.js has already initialized.
2760
+ *
2761
+ * @param {HTMLElement} slide
2762
+ */
2763
+ function syncFragments( slide ) {
2764
+
2765
+ sortFragments( slide.querySelectorAll( '.fragment' ) );
2766
+
2767
+ }
2768
+
2484
2769
  /**
2485
2770
  * Resets all vertical slides so that only the first
2486
2771
  * is visible.
@@ -2706,10 +2991,10 @@
2706
2991
 
2707
2992
  // Show the horizontal slide if it's within the view distance
2708
2993
  if( distanceX < viewDistance ) {
2709
- showSlide( horizontalSlide );
2994
+ loadSlide( horizontalSlide );
2710
2995
  }
2711
2996
  else {
2712
- hideSlide( horizontalSlide );
2997
+ unloadSlide( horizontalSlide );
2713
2998
  }
2714
2999
 
2715
3000
  if( verticalSlidesLength ) {
@@ -2722,16 +3007,32 @@
2722
3007
  distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
2723
3008
 
2724
3009
  if( distanceX + distanceY < viewDistance ) {
2725
- showSlide( verticalSlide );
3010
+ loadSlide( verticalSlide );
2726
3011
  }
2727
3012
  else {
2728
- hideSlide( verticalSlide );
3013
+ unloadSlide( verticalSlide );
2729
3014
  }
2730
3015
  }
2731
3016
 
2732
3017
  }
2733
3018
  }
2734
3019
 
3020
+ // Flag if there are ANY vertical slides, anywhere in the deck
3021
+ if( dom.wrapper.querySelectorAll( '.slides>section>section' ).length ) {
3022
+ dom.wrapper.classList.add( 'has-vertical-slides' );
3023
+ }
3024
+ else {
3025
+ dom.wrapper.classList.remove( 'has-vertical-slides' );
3026
+ }
3027
+
3028
+ // Flag if there are ANY horizontal slides, anywhere in the deck
3029
+ if( dom.wrapper.querySelectorAll( '.slides>section' ).length > 1 ) {
3030
+ dom.wrapper.classList.add( 'has-horizontal-slides' );
3031
+ }
3032
+ else {
3033
+ dom.wrapper.classList.remove( 'has-horizontal-slides' );
3034
+ }
3035
+
2735
3036
  }
2736
3037
 
2737
3038
  }
@@ -2746,12 +3047,39 @@
2746
3047
 
2747
3048
  if( config.showNotes && dom.speakerNotes && currentSlide && !isPrintingPDF() ) {
2748
3049
 
2749
- dom.speakerNotes.innerHTML = getSlideNotes() || '';
3050
+ dom.speakerNotes.innerHTML = getSlideNotes() || '<span class="notes-placeholder">No notes on this slide.</span>';
2750
3051
 
2751
3052
  }
2752
3053
 
2753
3054
  }
2754
3055
 
3056
+ /**
3057
+ * Updates the visibility of the speaker notes sidebar that
3058
+ * is used to share annotated slides. The notes sidebar is
3059
+ * only visible if showNotes is true and there are notes on
3060
+ * one or more slides in the deck.
3061
+ */
3062
+ function updateNotesVisibility() {
3063
+
3064
+ if( config.showNotes && hasNotes() ) {
3065
+ dom.wrapper.classList.add( 'show-notes' );
3066
+ }
3067
+ else {
3068
+ dom.wrapper.classList.remove( 'show-notes' );
3069
+ }
3070
+
3071
+ }
3072
+
3073
+ /**
3074
+ * Checks if there are speaker notes for ANY slide in the
3075
+ * presentation.
3076
+ */
3077
+ function hasNotes() {
3078
+
3079
+ return dom.slides.querySelectorAll( '[data-notes], aside.notes' ).length > 0;
3080
+
3081
+ }
3082
+
2755
3083
  /**
2756
3084
  * Updates the progress bar to reflect the current slide.
2757
3085
  */
@@ -2766,6 +3094,7 @@
2766
3094
 
2767
3095
  }
2768
3096
 
3097
+
2769
3098
  /**
2770
3099
  * Updates the slide number div to reflect the current slide.
2771
3100
  *
@@ -2788,6 +3117,12 @@
2788
3117
  format = config.slideNumber;
2789
3118
  }
2790
3119
 
3120
+ // If there are ONLY vertical slides in this deck, always use
3121
+ // a flattened slide number
3122
+ if( !/c/.test( format ) && dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length === 1 ) {
3123
+ format = 'c';
3124
+ }
3125
+
2791
3126
  switch( format ) {
2792
3127
  case 'c':
2793
3128
  value.push( getSlidePastCount() + 1 );
@@ -2820,13 +3155,18 @@
2820
3155
  */
2821
3156
  function formatSlideNumber( a, delimiter, b ) {
2822
3157
 
3158
+ var url = '#' + locationHash();
2823
3159
  if( typeof b === 'number' && !isNaN( b ) ) {
2824
- return '<span class="slide-number-a">'+ a +'</span>' +
3160
+ return '<a href="' + url + '">' +
3161
+ '<span class="slide-number-a">'+ a +'</span>' +
2825
3162
  '<span class="slide-number-delimiter">'+ delimiter +'</span>' +
2826
- '<span class="slide-number-b">'+ b +'</span>';
3163
+ '<span class="slide-number-b">'+ b +'</span>' +
3164
+ '</a>';
2827
3165
  }
2828
3166
  else {
2829
- return '<span class="slide-number-a">'+ a +'</span>';
3167
+ return '<a href="' + url + '">' +
3168
+ '<span class="slide-number-a">'+ a +'</span>' +
3169
+ '</a>';
2830
3170
  }
2831
3171
 
2832
3172
  }
@@ -2882,6 +3222,26 @@
2882
3222
 
2883
3223
  }
2884
3224
 
3225
+ if( config.controlsTutorial ) {
3226
+
3227
+ // Highlight control arrows with an animation to ensure
3228
+ // that the viewer knows how to navigate
3229
+ if( !hasNavigatedDown && routes.down ) {
3230
+ dom.controlsDownArrow.classList.add( 'highlight' );
3231
+ }
3232
+ else {
3233
+ dom.controlsDownArrow.classList.remove( 'highlight' );
3234
+
3235
+ if( !hasNavigatedRight && routes.right && indexv === 0 ) {
3236
+ dom.controlsRightArrow.classList.add( 'highlight' );
3237
+ }
3238
+ else {
3239
+ dom.controlsRightArrow.classList.remove( 'highlight' );
3240
+ }
3241
+ }
3242
+
3243
+ }
3244
+
2885
3245
  }
2886
3246
 
2887
3247
  /**
@@ -2957,13 +3317,18 @@
2957
3317
 
2958
3318
  startEmbeddedContent( currentBackground );
2959
3319
 
2960
- var backgroundImageURL = currentBackground.style.backgroundImage || '';
3320
+ var currentBackgroundContent = currentBackground.querySelector( '.slide-background-content' );
3321
+ if( currentBackgroundContent ) {
3322
+
3323
+ var backgroundImageURL = currentBackgroundContent.style.backgroundImage || '';
3324
+
3325
+ // Restart GIFs (doesn't work in Firefox)
3326
+ if( /\.gif/i.test( backgroundImageURL ) ) {
3327
+ currentBackgroundContent.style.backgroundImage = '';
3328
+ window.getComputedStyle( currentBackgroundContent ).opacity;
3329
+ currentBackgroundContent.style.backgroundImage = backgroundImageURL;
3330
+ }
2961
3331
 
2962
- // Restart GIFs (doesn't work in Firefox)
2963
- if( /\.gif/i.test( backgroundImageURL ) ) {
2964
- currentBackground.style.backgroundImage = '';
2965
- window.getComputedStyle( currentBackground ).opacity;
2966
- currentBackground.style.backgroundImage = backgroundImageURL;
2967
3332
  }
2968
3333
 
2969
3334
  // Don't transition between identical backgrounds. This
@@ -3061,14 +3426,9 @@
3061
3426
  *
3062
3427
  * @param {HTMLElement} slide Slide to show
3063
3428
  */
3064
- /**
3065
- * Called when the given slide is within the configured view
3066
- * distance. Shows the slide element and loads any content
3067
- * that is set to load lazily (data-src).
3068
- *
3069
- * @param {HTMLElement} slide Slide to show
3070
- */
3071
- function showSlide( slide ) {
3429
+ function loadSlide( slide, options ) {
3430
+
3431
+ options = options || {};
3072
3432
 
3073
3433
  // Show the slide element
3074
3434
  slide.style.display = config.display;
@@ -3076,6 +3436,7 @@
3076
3436
  // Media elements with data-src attributes
3077
3437
  toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
3078
3438
  element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
3439
+ element.setAttribute( 'data-lazy-loaded', '' );
3079
3440
  element.removeAttribute( 'data-src' );
3080
3441
  } );
3081
3442
 
@@ -3086,6 +3447,7 @@
3086
3447
  toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
3087
3448
  source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
3088
3449
  source.removeAttribute( 'data-src' );
3450
+ source.setAttribute( 'data-lazy-loaded', '' );
3089
3451
  sources += 1;
3090
3452
  } );
3091
3453
 
@@ -3098,11 +3460,12 @@
3098
3460
 
3099
3461
 
3100
3462
  // Show the corresponding background element
3101
- var indices = getIndices( slide );
3102
- var background = getSlideBackground( indices.h, indices.v );
3463
+ var background = slide.slideBackgroundElement;
3103
3464
  if( background ) {
3104
3465
  background.style.display = 'block';
3105
3466
 
3467
+ var backgroundContent = slide.slideBackgroundContentElement;
3468
+
3106
3469
  // If the background contains media, load it
3107
3470
  if( background.hasAttribute( 'data-loaded' ) === false ) {
3108
3471
  background.setAttribute( 'data-loaded', 'true' );
@@ -3115,7 +3478,7 @@
3115
3478
 
3116
3479
  // Images
3117
3480
  if( backgroundImage ) {
3118
- background.style.backgroundImage = 'url('+ backgroundImage +')';
3481
+ backgroundContent.style.backgroundImage = 'url('+ encodeURI( backgroundImage ) +')';
3119
3482
  }
3120
3483
  // Videos
3121
3484
  else if ( backgroundVideo && !isSpeakerNotes() ) {
@@ -3143,10 +3506,10 @@
3143
3506
  video.innerHTML += '<source src="'+ source +'">';
3144
3507
  } );
3145
3508
 
3146
- background.appendChild( video );
3509
+ backgroundContent.appendChild( video );
3147
3510
  }
3148
3511
  // Iframes
3149
- else if( backgroundIframe ) {
3512
+ else if( backgroundIframe && options.excludeIframes !== true ) {
3150
3513
  var iframe = document.createElement( 'iframe' );
3151
3514
  iframe.setAttribute( 'allowfullscreen', '' );
3152
3515
  iframe.setAttribute( 'mozallowfullscreen', '' );
@@ -3166,7 +3529,7 @@
3166
3529
  iframe.style.maxHeight = '100%';
3167
3530
  iframe.style.maxWidth = '100%';
3168
3531
 
3169
- background.appendChild( iframe );
3532
+ backgroundContent.appendChild( iframe );
3170
3533
  }
3171
3534
  }
3172
3535
 
@@ -3175,23 +3538,34 @@
3175
3538
  }
3176
3539
 
3177
3540
  /**
3178
- * Called when the given slide is moved outside of the
3179
- * configured view distance.
3541
+ * Unloads and hides the given slide. This is called when the
3542
+ * slide is moved outside of the configured view distance.
3180
3543
  *
3181
3544
  * @param {HTMLElement} slide
3182
3545
  */
3183
- function hideSlide( slide ) {
3546
+ function unloadSlide( slide ) {
3184
3547
 
3185
3548
  // Hide the slide element
3186
3549
  slide.style.display = 'none';
3187
3550
 
3188
3551
  // Hide the corresponding background element
3189
- var indices = getIndices( slide );
3190
- var background = getSlideBackground( indices.h, indices.v );
3552
+ var background = getSlideBackground( slide );
3191
3553
  if( background ) {
3192
3554
  background.style.display = 'none';
3193
3555
  }
3194
3556
 
3557
+ // Reset lazy-loaded media elements with src attributes
3558
+ toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src]' ) ).forEach( function( element ) {
3559
+ element.setAttribute( 'data-src', element.getAttribute( 'src' ) );
3560
+ element.removeAttribute( 'src' );
3561
+ } );
3562
+
3563
+ // Reset lazy-loaded media elements with <source> children
3564
+ toArray( slide.querySelectorAll( 'video[data-lazy-loaded] source[src], audio source[src]' ) ).forEach( function( source ) {
3565
+ source.setAttribute( 'data-src', source.getAttribute( 'src' ) );
3566
+ source.removeAttribute( 'src' );
3567
+ } );
3568
+
3195
3569
  }
3196
3570
 
3197
3571
  /**
@@ -3205,13 +3579,27 @@
3205
3579
  verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
3206
3580
 
3207
3581
  var routes = {
3208
- left: indexh > 0 || config.loop,
3209
- right: indexh < horizontalSlides.length - 1 || config.loop,
3582
+ left: indexh > 0,
3583
+ right: indexh < horizontalSlides.length - 1,
3210
3584
  up: indexv > 0,
3211
3585
  down: indexv < verticalSlides.length - 1
3212
3586
  };
3213
3587
 
3214
- // reverse horizontal controls for rtl
3588
+ // Looped presentations can always be navigated as long as
3589
+ // there are slides available
3590
+ if( config.loop ) {
3591
+ if( horizontalSlides.length > 1 ) {
3592
+ routes.left = true;
3593
+ routes.right = true;
3594
+ }
3595
+
3596
+ if( verticalSlides.length > 1 ) {
3597
+ routes.up = true;
3598
+ routes.down = true;
3599
+ }
3600
+ }
3601
+
3602
+ // Reverse horizontal controls for rtl
3215
3603
  if( config.rtl ) {
3216
3604
  var left = routes.left;
3217
3605
  routes.left = routes.right;
@@ -3267,6 +3655,13 @@
3267
3655
  _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
3268
3656
  _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
3269
3657
 
3658
+ // Always show media controls on mobile devices
3659
+ if( isMobileDevice ) {
3660
+ toArray( dom.slides.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
3661
+ el.controls = true;
3662
+ } );
3663
+ }
3664
+
3270
3665
  }
3271
3666
 
3272
3667
  /**
@@ -3303,9 +3698,16 @@
3303
3698
 
3304
3699
  if( autoplay && typeof el.play === 'function' ) {
3305
3700
 
3701
+ // If the media is ready, start playback
3306
3702
  if( el.readyState > 1 ) {
3307
3703
  startEmbeddedMedia( { target: el } );
3308
3704
  }
3705
+ // Mobile devices never fire a loaded event so instead
3706
+ // of waiting, we initiate playback
3707
+ else if( isMobileDevice ) {
3708
+ el.play();
3709
+ }
3710
+ // If the media isn't loaded, wait before playing
3309
3711
  else {
3310
3712
  el.removeEventListener( 'loadeddata', startEmbeddedMedia ); // remove first to avoid dupes
3311
3713
  el.addEventListener( 'loadeddata', startEmbeddedMedia );
@@ -3411,7 +3813,12 @@
3411
3813
  *
3412
3814
  * @param {HTMLElement} element
3413
3815
  */
3414
- function stopEmbeddedContent( element ) {
3816
+ function stopEmbeddedContent( element, options ) {
3817
+
3818
+ options = extend( {
3819
+ // Defaults
3820
+ unloadIframes: true
3821
+ }, options || {} );
3415
3822
 
3416
3823
  if( element && element.parentNode ) {
3417
3824
  // HTML5 media elements
@@ -3442,13 +3849,15 @@
3442
3849
  }
3443
3850
  });
3444
3851
 
3445
- // Lazy loading iframes
3446
- toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
3447
- // Only removing the src doesn't actually unload the frame
3448
- // in all browsers (Firefox) so we set it to blank first
3449
- el.setAttribute( 'src', 'about:blank' );
3450
- el.removeAttribute( 'src' );
3451
- } );
3852
+ if( options.unloadIframes === true ) {
3853
+ // Unload lazy-loaded iframes
3854
+ toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
3855
+ // Only removing the src doesn't actually unload the frame
3856
+ // in all browsers (Firefox) so we set it to blank first
3857
+ el.setAttribute( 'src', 'about:blank' );
3858
+ el.removeAttribute( 'src' );
3859
+ } );
3860
+ }
3452
3861
  }
3453
3862
 
3454
3863
  }
@@ -3563,12 +3972,15 @@
3563
3972
  var element;
3564
3973
 
3565
3974
  // Ensure the named link is a valid HTML ID attribute
3566
- if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
3567
- // Find the slide with the specified ID
3568
- element = document.getElementById( name );
3975
+ try {
3976
+ element = document.getElementById( decodeURIComponent( name ) );
3569
3977
  }
3978
+ catch ( error ) { }
3570
3979
 
3571
- if( element ) {
3980
+ // Ensure that we're not already on a slide with the same name
3981
+ var isSameNameAsCurrentSlide = currentSlide ? currentSlide.getAttribute( 'id' ) === name : false;
3982
+
3983
+ if( element && !isSameNameAsCurrentSlide ) {
3572
3984
  // Find the position of the named slide and navigate to it
3573
3985
  var indices = Reveal.getIndices( element );
3574
3986
  slide( indices.h, indices.v );
@@ -3579,12 +3991,22 @@
3579
3991
  }
3580
3992
  }
3581
3993
  else {
3994
+ var hashIndexBase = config.hashOneBasedIndex ? 1 : 0;
3995
+
3582
3996
  // Read the index components of the hash
3583
- var h = parseInt( bits[0], 10 ) || 0,
3584
- v = parseInt( bits[1], 10 ) || 0;
3997
+ var h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0,
3998
+ v = ( parseInt( bits[1], 10 ) - hashIndexBase ) || 0,
3999
+ f;
4000
+
4001
+ if( config.fragmentInURL ) {
4002
+ f = parseInt( bits[2], 10 );
4003
+ if( isNaN( f ) ) {
4004
+ f = undefined;
4005
+ }
4006
+ }
3585
4007
 
3586
- if( h !== indexh || v !== indexv ) {
3587
- slide( h, v );
4008
+ if( h !== indexh || v !== indexv || f !== undefined ) {
4009
+ slide( h, v, f );
3588
4010
  }
3589
4011
  }
3590
4012
 
@@ -3609,25 +4031,7 @@
3609
4031
  writeURLTimeout = setTimeout( writeURL, delay );
3610
4032
  }
3611
4033
  else if( currentSlide ) {
3612
- var url = '/';
3613
-
3614
- // Attempt to create a named link based on the slide's ID
3615
- var id = currentSlide.getAttribute( 'id' );
3616
- if( id ) {
3617
- id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
3618
- }
3619
-
3620
- // If the current slide has an ID, use that as a named link
3621
- if( typeof id === 'string' && id.length ) {
3622
- url = '/' + id;
3623
- }
3624
- // Otherwise use the /h/v index
3625
- else {
3626
- if( indexh > 0 || indexv > 0 ) url += indexh;
3627
- if( indexv > 0 ) url += '/' + indexv;
3628
- }
3629
-
3630
- window.location.hash = url;
4034
+ window.location.hash = locationHash();
3631
4035
  }
3632
4036
  }
3633
4037
 
@@ -3730,31 +4134,19 @@
3730
4134
  * defined, have a background element so as long as the
3731
4135
  * index is valid an element will be returned.
3732
4136
  *
3733
- * @param {number} x Horizontal background index
4137
+ * @param {mixed} x Horizontal background index OR a slide
4138
+ * HTML element
3734
4139
  * @param {number} y Vertical background index
3735
4140
  * @return {(HTMLElement[]|*)}
3736
4141
  */
3737
4142
  function getSlideBackground( x, y ) {
3738
4143
 
3739
- // When printing to PDF the slide backgrounds are nested
3740
- // inside of the slides
3741
- if( isPrintingPDF() ) {
3742
- var slide = getSlide( x, y );
3743
- if( slide ) {
3744
- return slide.slideBackgroundElement;
3745
- }
3746
-
3747
- return undefined;
3748
- }
3749
-
3750
- var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
3751
- var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
3752
-
3753
- if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
3754
- return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
4144
+ var slide = typeof x === 'number' ? getSlide( x, y ) : x;
4145
+ if( slide ) {
4146
+ return slide.slideBackgroundElement;
3755
4147
  }
3756
4148
 
3757
- return horizontalBackground;
4149
+ return undefined;
3758
4150
 
3759
4151
  }
3760
4152
 
@@ -3848,9 +4240,11 @@
3848
4240
  * the fragment within the fragments list.
3849
4241
  *
3850
4242
  * @param {object[]|*} fragments
4243
+ * @param {boolean} grouped If true the returned array will contain
4244
+ * nested arrays for all fragments with the same index
3851
4245
  * @return {object[]} sorted Sorted array of fragments
3852
4246
  */
3853
- function sortFragments( fragments ) {
4247
+ function sortFragments( fragments, grouped ) {
3854
4248
 
3855
4249
  fragments = toArray( fragments );
3856
4250
 
@@ -3893,7 +4287,7 @@
3893
4287
  index ++;
3894
4288
  } );
3895
4289
 
3896
- return sorted;
4290
+ return grouped === true ? ordered : sorted;
3897
4291
 
3898
4292
  }
3899
4293
 
@@ -3974,6 +4368,9 @@
3974
4368
 
3975
4369
  updateControls();
3976
4370
  updateProgress();
4371
+ if( config.fragmentInURL ) {
4372
+ writeURL();
4373
+ }
3977
4374
 
3978
4375
  return !!( fragmentsShown.length || fragmentsHidden.length );
3979
4376
 
@@ -4016,7 +4413,7 @@
4016
4413
 
4017
4414
  cancelAutoSlide();
4018
4415
 
4019
- if( currentSlide ) {
4416
+ if( currentSlide && config.autoSlide !== false ) {
4020
4417
 
4021
4418
  var fragment = currentSlide.querySelector( '.current-fragment' );
4022
4419
 
@@ -4134,6 +4531,8 @@
4134
4531
 
4135
4532
  function navigateRight() {
4136
4533
 
4534
+ hasNavigatedRight = true;
4535
+
4137
4536
  // Reverse for RTL
4138
4537
  if( config.rtl ) {
4139
4538
  if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
@@ -4158,6 +4557,8 @@
4158
4557
 
4159
4558
  function navigateDown() {
4160
4559
 
4560
+ hasNavigatedDown = true;
4561
+
4161
4562
  // Prioritize revealing fragments
4162
4563
  if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
4163
4564
  slide( indexh, indexv + 1 );
@@ -4204,9 +4605,22 @@
4204
4605
  */
4205
4606
  function navigateNext() {
4206
4607
 
4608
+ hasNavigatedRight = true;
4609
+ hasNavigatedDown = true;
4610
+
4207
4611
  // Prioritize revealing fragments
4208
4612
  if( nextFragment() === false ) {
4209
- if( availableRoutes().down ) {
4613
+
4614
+ var routes = availableRoutes();
4615
+
4616
+ // When looping is enabled `routes.down` is always available
4617
+ // so we need a separate check for when we've reached the
4618
+ // end of a stack and should move horizontally
4619
+ if( routes.down && routes.right && config.loop && Reveal.isLastVerticalSlide( currentSlide ) ) {
4620
+ routes.down = false;
4621
+ }
4622
+
4623
+ if( routes.down ) {
4210
4624
  navigateDown();
4211
4625
  }
4212
4626
  else if( config.rtl ) {
@@ -4276,7 +4690,7 @@
4276
4690
 
4277
4691
  // If there's a condition specified and it returns false,
4278
4692
  // ignore this event
4279
- if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
4693
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition(event) === false ) {
4280
4694
  return true;
4281
4695
  }
4282
4696
 
@@ -4341,7 +4755,31 @@
4341
4755
 
4342
4756
  }
4343
4757
 
4344
- // 2. System defined key bindings
4758
+ // 2. Registered custom key bindings
4759
+ if( triggered === false ) {
4760
+
4761
+ for( key in registeredKeyBindings ) {
4762
+
4763
+ // Check if this binding matches the pressed key
4764
+ if( parseInt( key, 10 ) === event.keyCode ) {
4765
+
4766
+ var action = registeredKeyBindings[ key ].callback;
4767
+
4768
+ // Callback function
4769
+ if( typeof action === 'function' ) {
4770
+ action.apply( null, [ event ] );
4771
+ }
4772
+ // String shortcuts to reveal.js API
4773
+ else if( typeof action === 'string' && typeof Reveal[ action ] === 'function' ) {
4774
+ Reveal[ action ].call();
4775
+ }
4776
+
4777
+ triggered = true;
4778
+ }
4779
+ }
4780
+ }
4781
+
4782
+ // 3. System defined key bindings
4345
4783
  if( triggered === false ) {
4346
4784
 
4347
4785
  // Assume true and try to prove false
@@ -4872,7 +5310,7 @@
4872
5310
  this.context.beginPath();
4873
5311
  this.context.arc( x, y, radius, 0, Math.PI * 2, false );
4874
5312
  this.context.lineWidth = this.thickness;
4875
- this.context.strokeStyle = '#666';
5313
+ this.context.strokeStyle = 'rgba( 255, 255, 255, 0.2 )';
4876
5314
  this.context.stroke();
4877
5315
 
4878
5316
  if( this.playing ) {
@@ -4935,7 +5373,10 @@
4935
5373
 
4936
5374
  initialize: initialize,
4937
5375
  configure: configure,
5376
+
4938
5377
  sync: sync,
5378
+ syncSlide: syncSlide,
5379
+ syncFragments: syncFragments,
4939
5380
 
4940
5381
  // Navigation methods
4941
5382
  slide: slide,
@@ -4988,6 +5429,11 @@
4988
5429
  isOverview: isOverview,
4989
5430
  isPaused: isPaused,
4990
5431
  isAutoSliding: isAutoSliding,
5432
+ isSpeakerNotes: isSpeakerNotes,
5433
+
5434
+ // Slide preloading
5435
+ loadSlide: loadSlide,
5436
+ unloadSlide: unloadSlide,
4991
5437
 
4992
5438
  // Adds or removes all internal event listeners (such as keyboard)
4993
5439
  addEventListeners: addEventListeners,
@@ -5067,7 +5513,7 @@
5067
5513
  // Returns true if we're currently on the last slide
5068
5514
  isLastSlide: function() {
5069
5515
  if( currentSlide ) {
5070
- // Does this slide has next a sibling?
5516
+ // Does this slide have a next sibling?
5071
5517
  if( currentSlide.nextElementSibling ) return false;
5072
5518
 
5073
5519
  // If it's vertical, does its parent have a next sibling?
@@ -5079,6 +5525,19 @@
5079
5525
  return false;
5080
5526
  },
5081
5527
 
5528
+ // Returns true if we're on the last slide in the current
5529
+ // vertical stack
5530
+ isLastVerticalSlide: function() {
5531
+ if( currentSlide && isVerticalSlide( currentSlide ) ) {
5532
+ // Does this slide have a next sibling?
5533
+ if( currentSlide.nextElementSibling ) return false;
5534
+
5535
+ return true;
5536
+ }
5537
+
5538
+ return false;
5539
+ },
5540
+
5082
5541
  // Checks if reveal.js has been loaded and is ready for use
5083
5542
  isReady: function() {
5084
5543
  return loaded;
@@ -5096,6 +5555,12 @@
5096
5555
  }
5097
5556
  },
5098
5557
 
5558
+ // Adds a custom key binding
5559
+ addKeyBinding: addKeyBinding,
5560
+
5561
+ // Removes a custom key binding
5562
+ removeKeyBinding: removeKeyBinding,
5563
+
5099
5564
  // Programatically triggers a keyboard event
5100
5565
  triggerKey: function( keyCode ) {
5101
5566
  onDocumentKeyDown( { keyCode: keyCode } );