reveal-ck 3.9.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/features/step_definitions/serve_steps.rb +1 -1
  3. data/files/reveal-ck/templates/index.html/body.html.erb +0 -2
  4. data/files/reveal-ck/templates/index.html/head.html.erb +6 -6
  5. data/files/reveal-ck/templates/index.html/index.html.erb +2 -2
  6. data/files/reveal-ck/templates/index.html/script.js.erb +4 -6
  7. data/files/reveal.js/LICENSE +1 -1
  8. data/files/reveal.js/README.md +268 -82
  9. data/files/reveal.js/bower.json +1 -4
  10. data/files/reveal.js/css/reset.css +30 -0
  11. data/files/reveal.js/css/reveal.css +114 -99
  12. data/files/reveal.js/css/reveal.scss +108 -95
  13. data/files/reveal.js/css/theme/README.md +1 -1
  14. data/files/reveal.js/css/theme/beige.css +1 -1
  15. data/files/reveal.js/css/theme/black.css +4 -4
  16. data/files/reveal.js/css/theme/blood.css +1 -1
  17. data/files/reveal.js/css/theme/league.css +1 -1
  18. data/files/reveal.js/css/theme/moon.css +1 -1
  19. data/files/reveal.js/css/theme/night.css +1 -1
  20. data/files/reveal.js/css/theme/serif.css +1 -1
  21. data/files/reveal.js/css/theme/simple.css +1 -1
  22. data/files/reveal.js/css/theme/sky.css +1 -1
  23. data/files/reveal.js/css/theme/solarized.css +1 -1
  24. data/files/reveal.js/css/theme/source/black.scss +1 -1
  25. data/files/reveal.js/css/theme/template/settings.scss +3 -1
  26. data/files/reveal.js/css/theme/template/theme.scss +3 -3
  27. data/files/reveal.js/css/theme/white.css +1 -1
  28. data/files/reveal.js/demo.html +35 -26
  29. data/files/reveal.js/{Gruntfile.js → gruntfile.js} +21 -25
  30. data/files/reveal.js/index.html +5 -4
  31. data/files/reveal.js/js/reveal.js +961 -347
  32. data/files/reveal.js/lib/css/monokai.css +71 -0
  33. data/files/reveal.js/lib/js/promise.js +2 -0
  34. data/files/reveal.js/package-lock.json +5703 -0
  35. data/files/reveal.js/package.json +13 -12
  36. data/files/reveal.js/plugin/highlight/highlight.js +247 -21
  37. data/files/reveal.js/plugin/markdown/example.html +1 -3
  38. data/files/reveal.js/plugin/markdown/markdown.js +107 -73
  39. data/files/reveal.js/plugin/markdown/marked.js +3 -3
  40. data/files/reveal.js/plugin/math/math.js +51 -26
  41. data/files/reveal.js/plugin/notes/notes.html +138 -76
  42. data/files/reveal.js/plugin/notes/notes.js +46 -15
  43. data/files/reveal.js/plugin/search/search.js +1 -1
  44. data/files/reveal.js/plugin/zoom-js/zoom.js +22 -17
  45. data/files/reveal.js/test/assets/external-script-a.js +1 -0
  46. data/files/reveal.js/test/assets/external-script-b.js +1 -0
  47. data/files/reveal.js/test/assets/external-script-c.js +1 -0
  48. data/files/reveal.js/test/assets/external-script-d.js +1 -0
  49. data/files/reveal.js/test/examples/assets/beeping.txt +2 -0
  50. data/files/reveal.js/test/examples/assets/beeping.wav +0 -0
  51. data/files/reveal.js/test/examples/embedded-media.html +5 -1
  52. data/files/reveal.js/test/examples/math.html +23 -3
  53. data/files/reveal.js/test/examples/slide-backgrounds.html +0 -1
  54. data/files/reveal.js/test/examples/slide-transitions.html +0 -1
  55. data/files/reveal.js/test/test-dependencies-async.html +78 -0
  56. data/files/reveal.js/test/test-dependencies.html +54 -0
  57. data/files/reveal.js/test/test-grid-navigation.html +74 -0
  58. data/files/reveal.js/test/test-iframe-backgrounds.html +104 -0
  59. data/files/reveal.js/test/test-iframes.html +108 -0
  60. data/files/reveal.js/test/test-markdown-element-attributes.html +1 -3
  61. data/files/reveal.js/test/test-markdown-external.html +5 -4
  62. data/files/reveal.js/test/test-markdown-options.html +0 -1
  63. data/files/reveal.js/test/test-markdown-slide-attributes.html +0 -1
  64. data/files/reveal.js/test/test-markdown.html +0 -1
  65. data/files/reveal.js/test/test-pdf.html +1 -2
  66. data/files/reveal.js/test/test-plugins.html +105 -0
  67. data/files/reveal.js/test/test-state.html +139 -0
  68. data/files/reveal.js/test/test.html +3 -4
  69. data/files/reveal.js/test/test.js +21 -0
  70. data/lib/reveal-ck/builders/create_index_html.rb +0 -1
  71. data/lib/reveal-ck/builders/index_html.rb +2 -4
  72. data/lib/reveal-ck/commands/listen_to_reload_browser.rb +4 -1
  73. data/lib/reveal-ck/commands/serve.rb +2 -2
  74. data/lib/reveal-ck/commands/start_web_server.rb +1 -1
  75. data/lib/reveal-ck/commands/thread_waker.rb +1 -1
  76. data/lib/reveal-ck/config.rb +1 -1
  77. data/lib/reveal-ck/version.rb +1 -1
  78. data/spec/lib/reveal-ck/builders/index_html_spec.rb +1 -11
  79. data/spec/lib/reveal-ck/commands/thread_waker_spec.rb +2 -0
  80. metadata +94 -80
  81. data/files/reveal.js/lib/js/classList.js +0 -2
  82. data/files/reveal.js/lib/js/head.min.js +0 -6
@@ -4,7 +4,7 @@ Themes are written using Sass to keep things modular and reduce the need for rep
4
4
 
5
5
  ## Creating a Theme
6
6
 
7
- To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled by Grunt from Sass to CSS (see the [Gruntfile](https://github.com/hakimel/reveal.js/blob/master/Gruntfile.js)) when you run `npm run build -- css-themes`.
7
+ To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled by Grunt from Sass to CSS (see the [Gruntfile](https://github.com/hakimel/reveal.js/blob/master/gruntfile.js)) when you run `npm run build -- css-themes`.
8
8
 
9
9
  Each theme file does four things in the following order:
10
10
 
@@ -153,7 +153,7 @@ body {
153
153
  font-family: monospace;
154
154
  line-height: 1.2em;
155
155
  word-wrap: break-word;
156
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
156
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
157
157
 
158
158
  .reveal code {
159
159
  font-family: monospace;
@@ -11,8 +11,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
11
11
  * GLOBAL STYLES
12
12
  *********************************************/
13
13
  body {
14
- background: #222;
15
- background-color: #222; }
14
+ background: #191919;
15
+ background-color: #191919; }
16
16
 
17
17
  .reveal {
18
18
  font-family: "Source Sans Pro", Helvetica, sans-serif;
@@ -149,7 +149,7 @@ body {
149
149
  font-family: monospace;
150
150
  line-height: 1.2em;
151
151
  word-wrap: break-word;
152
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
152
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
153
153
 
154
154
  .reveal code {
155
155
  font-family: monospace;
@@ -270,4 +270,4 @@ body {
270
270
  *********************************************/
271
271
  @media print {
272
272
  .backgrounds {
273
- background-color: #222; } }
273
+ background-color: #191919; } }
@@ -152,7 +152,7 @@ body {
152
152
  font-family: monospace;
153
153
  line-height: 1.2em;
154
154
  word-wrap: break-word;
155
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
155
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
156
156
 
157
157
  .reveal code {
158
158
  font-family: monospace;
@@ -155,7 +155,7 @@ body {
155
155
  font-family: monospace;
156
156
  line-height: 1.2em;
157
157
  word-wrap: break-word;
158
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
158
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
159
159
 
160
160
  .reveal code {
161
161
  font-family: monospace;
@@ -153,7 +153,7 @@ body {
153
153
  font-family: monospace;
154
154
  line-height: 1.2em;
155
155
  word-wrap: break-word;
156
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
156
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
157
157
 
158
158
  .reveal code {
159
159
  font-family: monospace;
@@ -147,7 +147,7 @@ body {
147
147
  font-family: monospace;
148
148
  line-height: 1.2em;
149
149
  word-wrap: break-word;
150
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
150
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
151
151
 
152
152
  .reveal code {
153
153
  font-family: monospace;
@@ -149,7 +149,7 @@ body {
149
149
  font-family: monospace;
150
150
  line-height: 1.2em;
151
151
  word-wrap: break-word;
152
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
152
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
153
153
 
154
154
  .reveal code {
155
155
  font-family: monospace;
@@ -152,7 +152,7 @@ body {
152
152
  font-family: monospace;
153
153
  line-height: 1.2em;
154
154
  word-wrap: break-word;
155
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
155
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
156
156
 
157
157
  .reveal code {
158
158
  font-family: monospace;
@@ -156,7 +156,7 @@ body {
156
156
  font-family: monospace;
157
157
  line-height: 1.2em;
158
158
  word-wrap: break-word;
159
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
159
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
160
160
 
161
161
  .reveal code {
162
162
  font-family: monospace;
@@ -153,7 +153,7 @@ body {
153
153
  font-family: monospace;
154
154
  line-height: 1.2em;
155
155
  word-wrap: break-word;
156
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
156
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
157
157
 
158
158
  .reveal code {
159
159
  font-family: monospace;
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  // Override theme settings (see ../template/settings.scss)
19
- $backgroundColor: #222;
19
+ $backgroundColor: #191919;
20
20
 
21
21
  $mainColor: #fff;
22
22
  $headingColor: #fff;
@@ -28,6 +28,8 @@ $heading2Size: 2.11em;
28
28
  $heading3Size: 1.55em;
29
29
  $heading4Size: 1.00em;
30
30
 
31
+ $codeFont: monospace;
32
+
31
33
  // Links and actions
32
34
  $linkColor: #13DAEC;
33
35
  $linkColorHover: lighten( $linkColor, 20% );
@@ -40,4 +42,4 @@ $selectionColor: #fff;
40
42
  // to return a background image or gradient
41
43
  @mixin bodyBackground() {
42
44
  background: $backgroundColor;
43
- }
45
+ }
@@ -162,16 +162,16 @@ body {
162
162
 
163
163
  text-align: left;
164
164
  font-size: 0.55em;
165
- font-family: monospace;
165
+ font-family: $codeFont;
166
166
  line-height: 1.2em;
167
167
 
168
168
  word-wrap: break-word;
169
169
 
170
- box-shadow: 0px 0px 6px rgba(0,0,0,0.3);
170
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
171
171
  }
172
172
 
173
173
  .reveal code {
174
- font-family: monospace;
174
+ font-family: $codeFont;
175
175
  text-transform: none;
176
176
  }
177
177
 
@@ -149,7 +149,7 @@ body {
149
149
  font-family: monospace;
150
150
  line-height: 1.2em;
151
151
  word-wrap: break-word;
152
- box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
152
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
153
153
 
154
154
  .reveal code {
155
155
  font-family: monospace;
@@ -12,13 +12,14 @@
12
12
  <meta name="apple-mobile-web-app-capable" content="yes">
13
13
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
14
14
 
15
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
16
 
17
+ <link rel="stylesheet" href="css/reset.css">
17
18
  <link rel="stylesheet" href="css/reveal.css">
18
19
  <link rel="stylesheet" href="css/theme/black.css" id="theme">
19
20
 
20
21
  <!-- Theme used for syntax highlighting of code -->
21
- <link rel="stylesheet" href="lib/css/zenburn.css">
22
+ <link rel="stylesheet" href="lib/css/monokai.css">
22
23
 
23
24
  <!-- Printing and PDF exports -->
24
25
  <script>
@@ -93,7 +94,10 @@
93
94
  Press <strong>ESC</strong> to enter the slide overview.
94
95
  </p>
95
96
  <p>
96
- Hold down alt and click on any element to zoom in on it using <a href="http://lab.hakim.se/zoom-js">zoom.js</a>. Alt + click anywhere to zoom back out.
97
+ Hold down the <strong>alt</strong> key (<strong>ctrl</strong> in Linux) and click on any element to zoom towards it using <a href="http://lab.hakim.se/zoom-js">zoom.js</a>. Click again to zoom back out.
98
+ </p>
99
+ <p>
100
+ (NOTE: Use ctrl + click in Linux.)
97
101
  </p>
98
102
  </section>
99
103
 
@@ -195,16 +199,16 @@
195
199
  </section>
196
200
  <section data-background="https://s3.amazonaws.com/hakim-static/reveal-js/image-placeholder.png">
197
201
  <h2>Image Backgrounds</h2>
198
- <pre><code class="hljs">&lt;section data-background="image.png"&gt;</code></pre>
202
+ <pre><code class="hljs html">&lt;section data-background="image.png"&gt;</code></pre>
199
203
  </section>
200
204
  <section data-background="https://s3.amazonaws.com/hakim-static/reveal-js/image-placeholder.png" data-background-repeat="repeat" data-background-size="100px">
201
205
  <h2>Tiled Backgrounds</h2>
202
- <pre><code class="hljs" style="word-wrap: break-word;">&lt;section data-background="image.png" data-background-repeat="repeat" data-background-size="100px"&gt;</code></pre>
206
+ <pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background="image.png" data-background-repeat="repeat" data-background-size="100px"&gt;</code></pre>
203
207
  </section>
204
208
  <section data-background-video="https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.webm" data-background-color="#000000">
205
209
  <div style="background-color: rgba(0, 0, 0, 0.9); color: #fff; padding: 20px;">
206
210
  <h2>Video Backgrounds</h2>
207
- <pre><code class="hljs" style="word-wrap: break-word;">&lt;section data-background-video="video.mp4,video.webm"&gt;</code></pre>
211
+ <pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background-video="video.mp4,video.webm"&gt;</code></pre>
208
212
  </div>
209
213
  </section>
210
214
  <section data-background="http://i.giphy.com/90F8aUepslB84.gif">
@@ -217,7 +221,7 @@
217
221
  <p>
218
222
  Different background transitions are available via the backgroundTransition option. This one's called "zoom".
219
223
  </p>
220
- <pre><code class="hljs">Reveal.configure({ backgroundTransition: 'zoom' })</code></pre>
224
+ <pre><code class="hljs javascript">Reveal.configure({ backgroundTransition: 'zoom' })</code></pre>
221
225
  </section>
222
226
 
223
227
  <section data-transition="slide" data-background="#b5533c" data-background-transition="zoom">
@@ -225,25 +229,32 @@
225
229
  <p>
226
230
  You can override background transitions per-slide.
227
231
  </p>
228
- <pre><code class="hljs" style="word-wrap: break-word;">&lt;section data-background-transition="zoom"&gt;</code></pre>
232
+ <pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background-transition="zoom"&gt;</code></pre>
233
+ </section>
234
+
235
+ <section data-background-iframe="https://hakim.se" data-background-interactive>
236
+ <div style="position: absolute; width: 40%; right: 0; box-shadow: 0 1px 4px rgba(0,0,0,0.5), 0 5px 25px rgba(0,0,0,0.2); background-color: rgba(0, 0, 0, 0.9); color: #fff; padding: 20px; font-size: 20px; text-align: left;">
237
+ <h2>Iframe Backgrounds</h2>
238
+ <p>Since reveal.js runs on the web, you can easily embed other web content. Try interacting with the page in the background.</p>
239
+ </div>
229
240
  </section>
230
241
 
231
242
  <section>
232
243
  <h2>Pretty Code</h2>
233
- <pre><code class="hljs" data-trim contenteditable>
234
- function linkify( selector ) {
235
- if( supports3DTransforms ) {
236
-
237
- var nodes = document.querySelectorAll( selector );
238
-
239
- for( var i = 0, len = nodes.length; i &lt; len; i++ ) {
240
- var node = nodes[i];
241
-
242
- if( !node.className ) {
243
- node.className += ' roll';
244
- }
245
- }
246
- }
244
+ <pre><code class="hljs" data-trim data-line-numbers="4|9|4,8-11">
245
+ import React, { useState } from 'react';
246
+
247
+ function Example() {
248
+ const [count, setCount] = useState(0);
249
+
250
+ return (
251
+ &lt;div&gt;
252
+ &lt;p&gt;You clicked {count} times&lt;/p&gt;
253
+ &lt;button onClick={() =&gt; setCount(count + 1)}&gt;
254
+ Click me
255
+ &lt;/button&gt;
256
+ &lt;/div&gt;
257
+ );
247
258
  }
248
259
  </code></pre>
249
260
  <p>Code syntax highlighting courtesy of <a href="http://softwaremaniacs.org/soft/highlight/en/description/">highlight.js</a>.</p>
@@ -384,7 +395,6 @@ Reveal.addEventListener( 'customevent', function() {
384
395
 
385
396
  </div>
386
397
 
387
- <script src="lib/js/head.min.js"></script>
388
398
  <script src="js/reveal.js"></script>
389
399
 
390
400
  <script>
@@ -393,17 +403,16 @@ Reveal.addEventListener( 'customevent', function() {
393
403
  Reveal.initialize({
394
404
  controls: true,
395
405
  progress: true,
396
- history: true,
397
406
  center: true,
407
+ hash: true,
398
408
 
399
409
  transition: 'slide', // none/fade/slide/convex/concave/zoom
400
410
 
401
411
  // More info https://github.com/hakimel/reveal.js#dependencies
402
412
  dependencies: [
403
- { src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
404
413
  { src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
405
414
  { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
406
- { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
415
+ { src: 'plugin/highlight/highlight.js' },
407
416
  { src: 'plugin/search/search.js', async: true },
408
417
  { src: 'plugin/zoom-js/zoom.js', async: true },
409
418
  { src: 'plugin/notes/notes.js', async: true }
@@ -1,7 +1,11 @@
1
- /* global module:false */
2
- module.exports = function(grunt) {
3
- var port = grunt.option('port') || 8000;
4
- var root = grunt.option('root') || '.';
1
+ const sass = require('node-sass');
2
+
3
+ module.exports = grunt => {
4
+
5
+ require('load-grunt-tasks')(grunt);
6
+
7
+ let port = grunt.option('port') || 8000;
8
+ let root = grunt.option('root') || '.';
5
9
 
6
10
  if (!Array.isArray(root)) root = [root];
7
11
 
@@ -15,7 +19,7 @@ module.exports = function(grunt) {
15
19
  ' * http://revealjs.com\n' +
16
20
  ' * MIT licensed\n' +
17
21
  ' *\n' +
18
- ' * Copyright (C) 2018 Hakim El Hattab, http://hakim.se\n' +
22
+ ' * Copyright (C) 2020 Hakim El Hattab, http://hakim.se\n' +
19
23
  ' */'
20
24
  },
21
25
 
@@ -35,6 +39,10 @@ module.exports = function(grunt) {
35
39
  },
36
40
 
37
41
  sass: {
42
+ options: {
43
+ implementation: sass,
44
+ sourceMap: false
45
+ },
38
46
  core: {
39
47
  src: 'css/reveal.scss',
40
48
  dest: 'css/reveal.css'
@@ -85,10 +93,11 @@ module.exports = function(grunt) {
85
93
  console: false,
86
94
  unescape: false,
87
95
  define: false,
88
- exports: false
96
+ exports: false,
97
+ require: false
89
98
  }
90
99
  },
91
- files: [ 'Gruntfile.js', 'js/reveal.js' ]
100
+ files: [ 'gruntfile.js', 'js/reveal.js' ]
92
101
  },
93
102
 
94
103
  connect: {
@@ -120,7 +129,7 @@ module.exports = function(grunt) {
120
129
 
121
130
  watch: {
122
131
  js: {
123
- files: [ 'Gruntfile.js', 'js/reveal.js' ],
132
+ files: [ 'gruntfile.js', 'js/reveal.js' ],
124
133
  tasks: 'js'
125
134
  },
126
135
  theme: {
@@ -136,6 +145,10 @@ module.exports = function(grunt) {
136
145
  files: [ 'css/reveal.scss' ],
137
146
  tasks: 'css-core'
138
147
  },
148
+ test: {
149
+ files: [ 'test/*.html' ],
150
+ tasks: 'test'
151
+ },
139
152
  html: {
140
153
  files: root.map(path => path + '/*.html')
141
154
  },
@@ -145,27 +158,10 @@ module.exports = function(grunt) {
145
158
  options: {
146
159
  livereload: true
147
160
  }
148
- },
149
-
150
- retire: {
151
- js: [ 'js/reveal.js', 'lib/js/*.js', 'plugin/**/*.js' ],
152
- node: [ '.' ]
153
161
  }
154
162
 
155
163
  });
156
164
 
157
- // Dependencies
158
- grunt.loadNpmTasks( 'grunt-contrib-connect' );
159
- grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
160
- grunt.loadNpmTasks( 'grunt-contrib-jshint' );
161
- grunt.loadNpmTasks( 'grunt-contrib-qunit' );
162
- grunt.loadNpmTasks( 'grunt-contrib-uglify' );
163
- grunt.loadNpmTasks( 'grunt-contrib-watch' );
164
- grunt.loadNpmTasks( 'grunt-autoprefixer' );
165
- grunt.loadNpmTasks( 'grunt-retire' );
166
- grunt.loadNpmTasks( 'grunt-sass' );
167
- grunt.loadNpmTasks( 'grunt-zip' );
168
-
169
165
  // Default task
170
166
  grunt.registerTask( 'default', [ 'css', 'js' ] );
171
167
 
@@ -6,11 +6,12 @@
6
6
 
7
7
  <title>reveal.js</title>
8
8
 
9
+ <link rel="stylesheet" href="css/reset.css">
9
10
  <link rel="stylesheet" href="css/reveal.css">
10
11
  <link rel="stylesheet" href="css/theme/black.css">
11
12
 
12
13
  <!-- Theme used for syntax highlighting of code -->
13
- <link rel="stylesheet" href="lib/css/zenburn.css">
14
+ <link rel="stylesheet" href="lib/css/monokai.css">
14
15
 
15
16
  <!-- Printing and PDF exports -->
16
17
  <script>
@@ -29,7 +30,6 @@
29
30
  </div>
30
31
  </div>
31
32
 
32
- <script src="lib/js/head.min.js"></script>
33
33
  <script src="js/reveal.js"></script>
34
34
 
35
35
  <script>
@@ -37,11 +37,12 @@
37
37
  // - https://github.com/hakimel/reveal.js#configuration
38
38
  // - https://github.com/hakimel/reveal.js#dependencies
39
39
  Reveal.initialize({
40
+ hash: true,
40
41
  dependencies: [
41
42
  { src: 'plugin/markdown/marked.js' },
42
43
  { src: 'plugin/markdown/markdown.js' },
43
- { src: 'plugin/notes/notes.js', async: true },
44
- { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }
44
+ { src: 'plugin/highlight/highlight.js' },
45
+ { src: 'plugin/notes/notes.js', async: true }
45
46
  ]
46
47
  });
47
48
  </script>
@@ -3,7 +3,7 @@
3
3
  * http://revealjs.com
4
4
  * MIT licensed
5
5
  *
6
- * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
6
+ * Copyright (C) 2020 Hakim El Hattab, http://hakim.se
7
7
  */
8
8
  (function( root, factory ) {
9
9
  if( typeof define === 'function' && define.amd ) {
@@ -26,14 +26,18 @@
26
26
  var Reveal;
27
27
 
28
28
  // The reveal.js version
29
- var VERSION = '3.7.0';
29
+ var VERSION = '3.9.2';
30
30
 
31
31
  var SLIDES_SELECTOR = '.slides section',
32
32
  HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
33
33
  VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
34
34
  HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
35
+
35
36
  UA = navigator.userAgent,
36
37
 
38
+ // Methods that may not be invoked via the postMessage API
39
+ POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener/,
40
+
37
41
  // Configuration defaults, can be overridden at initialization time
38
42
  config = {
39
43
 
@@ -67,16 +71,36 @@
67
71
  progress: true,
68
72
 
69
73
  // Display the page number of the current slide
74
+ // - true: Show slide number
75
+ // - false: Hide slide number
76
+ //
77
+ // Can optionally be set as a string that specifies the number formatting:
78
+ // - "h.v": Horizontal . vertical slide number (default)
79
+ // - "h/v": Horizontal / vertical slide number
80
+ // - "c": Flattened slide number
81
+ // - "c/t": Flattened slide number / total slides
82
+ //
83
+ // Alternatively, you can provide a function that returns the slide
84
+ // number for the current slide. The function should take in a slide
85
+ // object and return an array with one string [slideNumber] or
86
+ // three strings [n1,delimiter,n2]. See #formatSlideNumber().
70
87
  slideNumber: false,
71
88
 
89
+ // Can be used to limit the contexts in which the slide number appears
90
+ // - "all": Always show the slide number
91
+ // - "print": Only when printing to PDF
92
+ // - "speaker": Only in the speaker view
93
+ showSlideNumber: 'all',
94
+
72
95
  // Use 1 based indexing for # links to match slide number (default is zero
73
96
  // based)
74
97
  hashOneBasedIndex: false,
75
98
 
76
- // Determine which displays to show the slide number on
77
- showSlideNumber: 'all',
99
+ // Add the current slide number to the URL hash so that reloading the
100
+ // page/copying the URL will return you to the same slide
101
+ hash: false,
78
102
 
79
- // Push each slide change to the browser history
103
+ // Push each slide change to the browser history. Implies `hash: true`
80
104
  history: false,
81
105
 
82
106
  // Enable keyboard shortcuts for navigation
@@ -104,6 +128,32 @@
104
128
  // Change the presentation direction to be RTL
105
129
  rtl: false,
106
130
 
131
+ // Changes the behavior of our navigation directions.
132
+ //
133
+ // "default"
134
+ // Left/right arrow keys step between horizontal slides, up/down
135
+ // arrow keys step between vertical slides. Space key steps through
136
+ // all slides (both horizontal and vertical).
137
+ //
138
+ // "linear"
139
+ // Removes the up/down arrows. Left/right arrows step through all
140
+ // slides (both horizontal and vertical).
141
+ //
142
+ // "grid"
143
+ // When this is enabled, stepping left/right from a vertical stack
144
+ // to an adjacent vertical stack will land you at the same vertical
145
+ // index.
146
+ //
147
+ // Consider a deck with six slides ordered in two vertical stacks:
148
+ // 1.1 2.1
149
+ // 1.2 2.2
150
+ // 1.3 2.3
151
+ //
152
+ // If you're on slide 1.3 and navigate right, you will normally move
153
+ // from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
154
+ // from 1.3 -> 2.3.
155
+ navigationMode: 'default',
156
+
107
157
  // Randomizes the order of slides each time the presentation loads
108
158
  shuffle: false,
109
159
 
@@ -134,6 +184,13 @@
134
184
  // - false: No media will autoplay, regardless of individual setting
135
185
  autoPlayMedia: null,
136
186
 
187
+ // Global override for preloading lazy-loaded iframes
188
+ // - null: Iframes with data-src AND data-preload will be loaded when within
189
+ // the viewDistance, iframes with only data-src will be loaded when visible
190
+ // - true: All iframes with data-src will be loaded when within the viewDistance
191
+ // - false: All iframes with data-src will be loaded only when visible
192
+ preloadIframes: null,
193
+
137
194
  // Controls automatic progression to the next slide
138
195
  // - 0: Auto-sliding only happens if the data-autoslide HTML attribute
139
196
  // is present on the current slide or fragment
@@ -217,9 +274,20 @@
217
274
  // Number of slides away from the current that are visible
218
275
  viewDistance: 3,
219
276
 
277
+ // Number of slides away from the current that are visible on mobile
278
+ // devices. It is advisable to set this to a lower number than
279
+ // viewDistance in order to save resources.
280
+ mobileViewDistance: 2,
281
+
220
282
  // The display mode that will be used to show slides
221
283
  display: 'block',
222
284
 
285
+ // Hide cursor if inactive
286
+ hideInactiveCursor: true,
287
+
288
+ // Time before the cursor is hidden (in ms)
289
+ hideCursorTime: 5000,
290
+
223
291
  // Script dependencies to load
224
292
  dependencies: []
225
293
 
@@ -267,6 +335,12 @@
267
335
  // Cached references to DOM elements
268
336
  dom = {},
269
337
 
338
+ // A list of registered reveal.js plugins
339
+ plugins = {},
340
+
341
+ // List of asynchronously loaded reveal.js dependencies
342
+ asyncDependencies = [],
343
+
270
344
  // Features supported by the browser, see #checkCapabilities()
271
345
  features = {},
272
346
 
@@ -282,6 +356,12 @@
282
356
  // Delays updates to the URL due to a Chrome thumbnailer bug
283
357
  writeURLTimeout = 0,
284
358
 
359
+ // Is the mouse pointer currently hidden from view
360
+ cursorHidden = false,
361
+
362
+ // Timeout used to determine when the cursor is inactive
363
+ cursorInactiveTimeout = 0,
364
+
285
365
  // Flags if the interaction event listeners are bound
286
366
  eventsAreBound = false,
287
367
 
@@ -298,26 +378,14 @@
298
378
  touch = {
299
379
  startX: 0,
300
380
  startY: 0,
301
- startSpan: 0,
302
381
  startCount: 0,
303
382
  captured: false,
304
383
  threshold: 40
305
384
  },
306
385
 
307
- // Holds information about the keyboard shortcuts
308
- keyboardShortcuts = {
309
- 'N , SPACE': 'Next slide',
310
- 'P': 'Previous slide',
311
- '&#8592; , H': 'Navigate left',
312
- '&#8594; , L': 'Navigate right',
313
- '&#8593; , K': 'Navigate up',
314
- '&#8595; , J': 'Navigate down',
315
- 'Home': 'First slide',
316
- 'End': 'Last slide',
317
- 'B , .': 'Pause',
318
- 'F': 'Fullscreen',
319
- 'ESC, O': 'Slide overview'
320
- },
386
+ // A key:value map of shortcut keyboard keys and descriptions of
387
+ // the actions they trigger, generated in #configure()
388
+ keyboardShortcuts = {},
321
389
 
322
390
  // Holds custom key code mappings
323
391
  registeredKeyBindings = {};
@@ -377,7 +445,7 @@
377
445
  // Hide the address bar in mobile browsers
378
446
  hideAddressBar();
379
447
 
380
- // Loads the dependencies and continues to #start() once done
448
+ // Loads dependencies and continues to #start() once done
381
449
  load();
382
450
 
383
451
  }
@@ -388,7 +456,8 @@
388
456
  */
389
457
  function checkCapabilities() {
390
458
 
391
- isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( UA );
459
+ isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( UA ) ||
460
+ ( navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 ); // iPadOS
392
461
  isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );
393
462
 
394
463
  var testElement = document.createElement( 'div' );
@@ -432,58 +501,149 @@
432
501
  function load() {
433
502
 
434
503
  var scripts = [],
435
- scriptsAsync = [],
436
- scriptsToPreload = 0;
504
+ scriptsToLoad = 0;
437
505
 
438
- // Called once synchronous scripts finish loading
439
- function proceed() {
440
- if( scriptsAsync.length ) {
441
- // Load asynchronous scripts
442
- head.js.apply( null, scriptsAsync );
506
+ config.dependencies.forEach( function( s ) {
507
+ // Load if there's no condition or the condition is truthy
508
+ if( !s.condition || s.condition() ) {
509
+ if( s.async ) {
510
+ asyncDependencies.push( s );
511
+ }
512
+ else {
513
+ scripts.push( s );
514
+ }
443
515
  }
516
+ } );
517
+
518
+ if( scripts.length ) {
519
+ scriptsToLoad = scripts.length;
520
+
521
+ // Load synchronous scripts
522
+ scripts.forEach( function( s ) {
523
+ loadScript( s.src, function() {
524
+
525
+ if( typeof s.callback === 'function' ) s.callback();
526
+
527
+ if( --scriptsToLoad === 0 ) {
528
+ initPlugins();
529
+ }
530
+
531
+ } );
532
+ } );
533
+ }
534
+ else {
535
+ initPlugins();
536
+ }
537
+
538
+ }
539
+
540
+ /**
541
+ * Initializes our plugins and waits for them to be ready
542
+ * before proceeding.
543
+ */
544
+ function initPlugins() {
545
+
546
+ var pluginsToInitialize = Object.keys( plugins ).length;
444
547
 
445
- start();
548
+ // If there are no plugins, skip this step
549
+ if( pluginsToInitialize === 0 ) {
550
+ loadAsyncDependencies();
446
551
  }
552
+ // ... otherwise initialize plugins
553
+ else {
447
554
 
448
- function loadScript( s ) {
449
- head.ready( s.src.match( /([\w\d_\-]*)\.?js(\?[\w\d.=&]*)?$|[^\\\/]*$/i )[0], function() {
450
- // Extension may contain callback functions
451
- if( typeof s.callback === 'function' ) {
452
- s.callback.apply( this );
555
+ var afterPlugInitialized = function() {
556
+ if( --pluginsToInitialize === 0 ) {
557
+ loadAsyncDependencies();
453
558
  }
559
+ };
454
560
 
455
- if( --scriptsToPreload === 0 ) {
456
- proceed();
457
- }
458
- });
459
- }
561
+ for( var i in plugins ) {
460
562
 
461
- for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
462
- var s = config.dependencies[i];
563
+ var plugin = plugins[i];
463
564
 
464
- // Load if there's no condition or the condition is truthy
465
- if( !s.condition || s.condition() ) {
466
- if( s.async ) {
467
- scriptsAsync.push( s.src );
565
+ // If the plugin has an 'init' method, invoke it
566
+ if( typeof plugin.init === 'function' ) {
567
+ var callback = plugin.init();
568
+
569
+ // If the plugin returned a Promise, wait for it
570
+ if( callback && typeof callback.then === 'function' ) {
571
+ callback.then( afterPlugInitialized );
572
+ }
573
+ else {
574
+ afterPlugInitialized();
575
+ }
468
576
  }
469
577
  else {
470
- scripts.push( s.src );
578
+ afterPlugInitialized();
471
579
  }
472
580
 
473
- loadScript( s );
474
581
  }
582
+
475
583
  }
476
584
 
477
- if( scripts.length ) {
478
- scriptsToPreload = scripts.length;
585
+ }
479
586
 
480
- // Load synchronous scripts
481
- head.js.apply( null, scripts );
587
+ /**
588
+ * Loads all async reveal.js dependencies.
589
+ */
590
+ function loadAsyncDependencies() {
591
+
592
+ if( asyncDependencies.length ) {
593
+ asyncDependencies.forEach( function( s ) {
594
+ loadScript( s.src, s.callback );
595
+ } );
482
596
  }
483
- else {
484
- proceed();
597
+
598
+ start();
599
+
600
+ }
601
+
602
+ /**
603
+ * Loads a JavaScript file from the given URL and executes it.
604
+ *
605
+ * @param {string} url Address of the .js file to load
606
+ * @param {function} callback Method to invoke when the script
607
+ * has loaded and executed
608
+ */
609
+ function loadScript( url, callback ) {
610
+
611
+ var script = document.createElement( 'script' );
612
+ script.type = 'text/javascript';
613
+ script.async = false;
614
+ script.defer = false;
615
+ script.src = url;
616
+
617
+ if( callback ) {
618
+
619
+ // Success callback
620
+ script.onload = script.onreadystatechange = function( event ) {
621
+ if( event.type === "load" || (/loaded|complete/.test( script.readyState ) ) ) {
622
+
623
+ // Kill event listeners
624
+ script.onload = script.onreadystatechange = script.onerror = null;
625
+
626
+ callback();
627
+
628
+ }
629
+ };
630
+
631
+ // Error callback
632
+ script.onerror = function( err ) {
633
+
634
+ // Kill event listeners
635
+ script.onload = script.onreadystatechange = script.onerror = null;
636
+
637
+ callback( new Error( 'Failed loading script: ' + script.src + '\n' + err) );
638
+
639
+ };
640
+
485
641
  }
486
642
 
643
+ // Append the script at the end of <head>
644
+ var head = document.querySelector( 'head' );
645
+ head.insertBefore( script, head.lastChild );
646
+
487
647
  }
488
648
 
489
649
  /**
@@ -593,8 +753,7 @@
593
753
  dom.speakerNotes.setAttribute( 'tabindex', '0' );
594
754
 
595
755
  // Overlay graphic which is displayed during the paused mode
596
- dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', '<button class="resume-button">Resume presentation</button>' );
597
- dom.resumeButton = dom.pauseOverlay.querySelector( '.resume-button' );
756
+ dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', config.controls ? '<button class="resume-button">Resume presentation</button>' : null );
598
757
 
599
758
  dom.wrapper.setAttribute( 'role', 'application' );
600
759
 
@@ -700,17 +859,10 @@
700
859
  // Make sure stretch elements fit on slide
701
860
  layoutSlideContents( slideWidth, slideHeight );
702
861
 
703
- // Add each slide's index as attributes on itself, we need these
704
- // indices to generate slide numbers below
705
- toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
706
- hslide.setAttribute( 'data-index-h', h );
707
-
708
- if( hslide.classList.contains( 'stack' ) ) {
709
- toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
710
- vslide.setAttribute( 'data-index-h', h );
711
- vslide.setAttribute( 'data-index-v', v );
712
- } );
713
- }
862
+ // Compute slide numbers now, before we start duplicating slides
863
+ var doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber );
864
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
865
+ slide.setAttribute( 'data-slide-number', getSlideNumber( slide ) );
714
866
  } );
715
867
 
716
868
  // Slide and slide background layout
@@ -781,14 +933,11 @@
781
933
  }
782
934
 
783
935
  // Inject slide numbers if `slideNumbers` are enabled
784
- if( config.slideNumber && /all|print/i.test( config.showSlideNumber ) ) {
785
- var slideNumberH = parseInt( slide.getAttribute( 'data-index-h' ), 10 ) + 1,
786
- slideNumberV = parseInt( slide.getAttribute( 'data-index-v' ), 10 ) + 1;
787
-
936
+ if( doingSlideNumbers ) {
788
937
  var numberElement = document.createElement( 'div' );
789
938
  numberElement.classList.add( 'slide-number' );
790
939
  numberElement.classList.add( 'slide-number-pdf' );
791
- numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );
940
+ numberElement.innerHTML = slide.getAttribute( 'data-slide-number' );
792
941
  page.appendChild( numberElement );
793
942
  }
794
943
 
@@ -1068,24 +1217,35 @@
1068
1217
  if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
1069
1218
  if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
1070
1219
 
1220
+ if( slide.hasAttribute( 'data-preload' ) ) element.setAttribute( 'data-preload', '' );
1221
+
1071
1222
  // Background image options are set on the content wrapper
1072
1223
  if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;
1073
1224
  if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat;
1074
1225
  if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;
1075
1226
  if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;
1076
1227
 
1077
- // If this slide has a background color, add a class that
1228
+ // If this slide has a background color, we add a class that
1078
1229
  // signals if it is light or dark. If the slide has no background
1079
- // color, no class will be set
1080
- var computedBackgroundStyle = window.getComputedStyle( element );
1081
- if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
1082
- var rgb = colorToRgb( computedBackgroundStyle.backgroundColor );
1230
+ // color, no class will be added
1231
+ var contrastColor = data.backgroundColor;
1232
+
1233
+ // If no bg color was found, check the computed background
1234
+ if( !contrastColor ) {
1235
+ var computedBackgroundStyle = window.getComputedStyle( element );
1236
+ if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
1237
+ contrastColor = computedBackgroundStyle.backgroundColor;
1238
+ }
1239
+ }
1240
+
1241
+ if( contrastColor ) {
1242
+ var rgb = colorToRgb( contrastColor );
1083
1243
 
1084
1244
  // Ignore fully transparent backgrounds. Some browsers return
1085
1245
  // rgba(0,0,0,0) when reading the computed background color of
1086
1246
  // an element with no background
1087
1247
  if( rgb && rgb.a !== 0 ) {
1088
- if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) {
1248
+ if( colorBrightness( contrastColor ) < 128 ) {
1089
1249
  slide.classList.add( 'has-dark-background' );
1090
1250
  }
1091
1251
  else {
@@ -1118,7 +1278,20 @@
1118
1278
 
1119
1279
  // Check if the requested method can be found
1120
1280
  if( data.method && typeof Reveal[data.method] === 'function' ) {
1121
- Reveal[data.method].apply( Reveal, data.args );
1281
+
1282
+ if( POST_MESSAGE_METHOD_BLACKLIST.test( data.method ) === false ) {
1283
+
1284
+ var result = Reveal[data.method].apply( Reveal, data.args );
1285
+
1286
+ // Dispatch a postMessage event with the returned value from
1287
+ // our method invocation for getter functions
1288
+ dispatchPostMessage( 'callback', { method: data.method, result: result } );
1289
+
1290
+ }
1291
+ else {
1292
+ console.warn( 'reveal.js: "'+ data.method +'" is is blacklisted from the postMessage API' );
1293
+ }
1294
+
1122
1295
  }
1123
1296
  }
1124
1297
  }, false );
@@ -1208,6 +1381,18 @@
1208
1381
  disableRollingLinks();
1209
1382
  }
1210
1383
 
1384
+ // Auto-hide the mouse pointer when its inactive
1385
+ if( config.hideInactiveCursor ) {
1386
+ document.addEventListener( 'mousemove', onDocumentCursorActive, false );
1387
+ document.addEventListener( 'mousedown', onDocumentCursorActive, false );
1388
+ }
1389
+ else {
1390
+ showCursor();
1391
+
1392
+ document.removeEventListener( 'mousemove', onDocumentCursorActive, false );
1393
+ document.removeEventListener( 'mousedown', onDocumentCursorActive, false );
1394
+ }
1395
+
1211
1396
  // Iframe link previews
1212
1397
  if( config.previewLinks ) {
1213
1398
  enablePreviewLinks();
@@ -1255,6 +1440,34 @@
1255
1440
 
1256
1441
  dom.slideNumber.style.display = slideNumberDisplay;
1257
1442
 
1443
+ // Add the navigation mode to the DOM so we can adjust styling
1444
+ if( config.navigationMode !== 'default' ) {
1445
+ dom.wrapper.setAttribute( 'data-navigation-mode', config.navigationMode );
1446
+ }
1447
+ else {
1448
+ dom.wrapper.removeAttribute( 'data-navigation-mode' );
1449
+ }
1450
+
1451
+ // Define our contextual list of keyboard shortcuts
1452
+ if( config.navigationMode === 'linear' ) {
1453
+ keyboardShortcuts['&#8594; , &#8595; , SPACE , N , L , J'] = 'Next slide';
1454
+ keyboardShortcuts['&#8592; , &#8593; , P , H , K'] = 'Previous slide';
1455
+ }
1456
+ else {
1457
+ keyboardShortcuts['N , SPACE'] = 'Next slide';
1458
+ keyboardShortcuts['P'] = 'Previous slide';
1459
+ keyboardShortcuts['&#8592; , H'] = 'Navigate left';
1460
+ keyboardShortcuts['&#8594; , L'] = 'Navigate right';
1461
+ keyboardShortcuts['&#8593; , K'] = 'Navigate up';
1462
+ keyboardShortcuts['&#8595; , J'] = 'Navigate down';
1463
+ }
1464
+
1465
+ keyboardShortcuts['Home , Shift &#8592;'] = 'First slide';
1466
+ keyboardShortcuts['End , Shift &#8594;'] = 'Last slide';
1467
+ keyboardShortcuts['B , .'] = 'Pause';
1468
+ keyboardShortcuts['F'] = 'Fullscreen';
1469
+ keyboardShortcuts['ESC, O'] = 'Slide overview';
1470
+
1258
1471
  sync();
1259
1472
 
1260
1473
  }
@@ -1299,7 +1512,7 @@
1299
1512
  dom.progress.addEventListener( 'click', onProgressClicked, false );
1300
1513
  }
1301
1514
 
1302
- dom.resumeButton.addEventListener( 'click', resume, false );
1515
+ dom.pauseOverlay.addEventListener( 'click', resume, false );
1303
1516
 
1304
1517
  if( config.focusBodyOnPageVisibilityChange ) {
1305
1518
  var visibilityChange;
@@ -1364,7 +1577,7 @@
1364
1577
  dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
1365
1578
  dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
1366
1579
 
1367
- dom.resumeButton.removeEventListener( 'click', resume, false );
1580
+ dom.pauseOverlay.removeEventListener( 'click', resume, false );
1368
1581
 
1369
1582
  if ( config.progress && dom.progress ) {
1370
1583
  dom.progress.removeEventListener( 'click', onProgressClicked, false );
@@ -1381,6 +1594,53 @@
1381
1594
 
1382
1595
  }
1383
1596
 
1597
+ /**
1598
+ * Registers a new plugin with this reveal.js instance.
1599
+ *
1600
+ * reveal.js waits for all regisered plugins to initialize
1601
+ * before considering itself ready, as long as the plugin
1602
+ * is registered before calling `Reveal.initialize()`.
1603
+ */
1604
+ function registerPlugin( id, plugin ) {
1605
+
1606
+ if( plugins[id] === undefined ) {
1607
+ plugins[id] = plugin;
1608
+
1609
+ // If a plugin is registered after reveal.js is loaded,
1610
+ // initialize it right away
1611
+ if( loaded && typeof plugin.init === 'function' ) {
1612
+ plugin.init();
1613
+ }
1614
+ }
1615
+ else {
1616
+ console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' );
1617
+ }
1618
+
1619
+ }
1620
+
1621
+ /**
1622
+ * Checks if a specific plugin has been registered.
1623
+ *
1624
+ * @param {String} id Unique plugin identifier
1625
+ */
1626
+ function hasPlugin( id ) {
1627
+
1628
+ return !!plugins[id];
1629
+
1630
+ }
1631
+
1632
+ /**
1633
+ * Returns the specific plugin instance, if a plugin
1634
+ * with the given ID has been registered.
1635
+ *
1636
+ * @param {String} id Unique plugin identifier
1637
+ */
1638
+ function getPlugin( id ) {
1639
+
1640
+ return plugins[id];
1641
+
1642
+ }
1643
+
1384
1644
  /**
1385
1645
  * Add a custom key binding with optional description to
1386
1646
  * be added to the help screen.
@@ -1669,11 +1929,19 @@
1669
1929
  // Change the .stretch element height to 0 in order find the height of all
1670
1930
  // the other elements
1671
1931
  element.style.height = '0px';
1932
+
1933
+ // In Overview mode, the parent (.slide) height is set of 700px.
1934
+ // Restore it temporarily to its natural height.
1935
+ element.parentNode.style.height = 'auto';
1936
+
1672
1937
  newHeight = height - element.parentNode.offsetHeight;
1673
1938
 
1674
1939
  // Restore the old height, just in case
1675
1940
  element.style.height = oldHeight + 'px';
1676
1941
 
1942
+ // Clear the parent (.slide) height. .removeProperty works in IE9+
1943
+ element.parentNode.style.removeProperty('height');
1944
+
1677
1945
  return newHeight;
1678
1946
  }
1679
1947
 
@@ -1690,15 +1958,6 @@
1690
1958
 
1691
1959
  }
1692
1960
 
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
-
1702
1961
  /**
1703
1962
  * Hides the address bar if we're on a mobile device.
1704
1963
  */
@@ -1737,8 +1996,25 @@
1737
1996
 
1738
1997
  // If we're in an iframe, post each reveal.js event to the
1739
1998
  // parent window. Used by the notes plugin
1999
+ dispatchPostMessage( type );
2000
+
2001
+ }
2002
+
2003
+ /**
2004
+ * Dispatched a postMessage of the given type from our window.
2005
+ */
2006
+ function dispatchPostMessage( type, data ) {
2007
+
1740
2008
  if( config.postMessageEvents && window.parent !== window.self ) {
1741
- window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
2009
+ var message = {
2010
+ namespace: 'reveal',
2011
+ eventName: type,
2012
+ state: getState()
2013
+ };
2014
+
2015
+ extend( message, data );
2016
+
2017
+ window.parent.postMessage( JSON.stringify( message ), '*' );
1742
2018
  }
1743
2019
 
1744
2020
  }
@@ -1962,8 +2238,20 @@
1962
2238
 
1963
2239
  if( !config.disableLayout ) {
1964
2240
 
2241
+ // On some mobile devices '100vh' is taller than the visible
2242
+ // viewport which leads to part of the presentation being
2243
+ // cut off. To work around this we define our own '--vh' custom
2244
+ // property where 100x adds up to the correct height.
2245
+ //
2246
+ // https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
2247
+ if( isMobileDevice ) {
2248
+ document.documentElement.style.setProperty( '--vh', ( window.innerHeight * 0.01 ) + 'px' );
2249
+ }
2250
+
1965
2251
  var size = getComputedSlideSize();
1966
2252
 
2253
+ var oldScale = scale;
2254
+
1967
2255
  // Layout the contents of the slides
1968
2256
  layoutSlideContents( config.width, config.height );
1969
2257
 
@@ -1987,10 +2275,12 @@
1987
2275
  transformSlides( { layout: '' } );
1988
2276
  }
1989
2277
  else {
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 ) {
2278
+ // Zoom Scaling
2279
+ // Content remains crisp no matter how much we scale. Side
2280
+ // effects are minor differences in text layout and iframe
2281
+ // viewports changing size. A 200x200 iframe viewport in a
2282
+ // 2x zoomed presentation ends up having a 400x400 viewport.
2283
+ if( scale > 1 && features.zoom && window.devicePixelRatio < 2 ) {
1994
2284
  dom.slides.style.zoom = scale;
1995
2285
  dom.slides.style.left = '';
1996
2286
  dom.slides.style.top = '';
@@ -1998,7 +2288,10 @@
1998
2288
  dom.slides.style.right = '';
1999
2289
  transformSlides( { layout: '' } );
2000
2290
  }
2001
- // Apply scale transform as a fallback
2291
+ // Transform Scaling
2292
+ // Content layout remains the exact same when scaled up.
2293
+ // Side effect is content becoming blurred, especially with
2294
+ // high scale values on ldpi screens.
2002
2295
  else {
2003
2296
  dom.slides.style.zoom = '';
2004
2297
  dom.slides.style.left = '50%';
@@ -2036,6 +2329,13 @@
2036
2329
 
2037
2330
  }
2038
2331
 
2332
+ if( oldScale !== scale ) {
2333
+ dispatchEvent( 'resize', {
2334
+ 'oldScale': oldScale,
2335
+ 'scale': scale,
2336
+ 'size': size
2337
+ } );
2338
+ }
2039
2339
  }
2040
2340
 
2041
2341
  updateProgress();
@@ -2360,34 +2660,37 @@
2360
2660
  }
2361
2661
 
2362
2662
  /**
2363
- * Return a hash URL that will resolve to the current slide location.
2663
+ * Return a hash URL that will resolve to the given slide location.
2664
+ *
2665
+ * @param {HTMLElement} [slide=currentSlide] The slide to link to
2364
2666
  */
2365
- function locationHash() {
2667
+ function locationHash( slide ) {
2366
2668
 
2367
2669
  var url = '/';
2368
2670
 
2369
2671
  // Attempt to create a named link based on the slide's ID
2370
- var id = currentSlide ? currentSlide.getAttribute( 'id' ) : null;
2672
+ var s = slide || currentSlide;
2673
+ var id = s ? s.getAttribute( 'id' ) : null;
2371
2674
  if( id ) {
2372
2675
  id = encodeURIComponent( id );
2373
2676
  }
2374
2677
 
2375
- var indexf;
2376
- if( config.fragmentInURL ) {
2377
- indexf = getIndices().f;
2678
+ var index = getIndices( slide );
2679
+ if( !config.fragmentInURL ) {
2680
+ index.f = undefined;
2378
2681
  }
2379
2682
 
2380
2683
  // If the current slide has an ID, use that as a named link,
2381
2684
  // but we don't support named links with a fragment index
2382
- if( typeof id === 'string' && id.length && indexf === undefined ) {
2685
+ if( typeof id === 'string' && id.length && index.f === undefined ) {
2383
2686
  url = '/' + id;
2384
2687
  }
2385
2688
  // Otherwise use the /h/v index
2386
2689
  else {
2387
2690
  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;
2691
+ if( index.h > 0 || index.v > 0 || index.f !== undefined ) url += index.h + hashIndexBase;
2692
+ if( index.v > 0 || index.f !== undefined ) url += '/' + (index.v + hashIndexBase );
2693
+ if( index.f !== undefined ) url += '/' + index.f;
2391
2694
  }
2392
2695
 
2393
2696
  return url;
@@ -2434,6 +2737,32 @@
2434
2737
 
2435
2738
  }
2436
2739
 
2740
+ /**
2741
+ * Shows the mouse pointer after it has been hidden with
2742
+ * #hideCursor.
2743
+ */
2744
+ function showCursor() {
2745
+
2746
+ if( cursorHidden ) {
2747
+ cursorHidden = false;
2748
+ dom.wrapper.style.cursor = '';
2749
+ }
2750
+
2751
+ }
2752
+
2753
+ /**
2754
+ * Hides the mouse pointer when it's on top of the .reveal
2755
+ * container.
2756
+ */
2757
+ function hideCursor() {
2758
+
2759
+ if( cursorHidden === false ) {
2760
+ cursorHidden = true;
2761
+ dom.wrapper.style.cursor = 'none';
2762
+ }
2763
+
2764
+ }
2765
+
2437
2766
  /**
2438
2767
  * Enters the paused mode which fades everything on screen to
2439
2768
  * black.
@@ -2576,28 +2905,6 @@
2576
2905
 
2577
2906
  layout();
2578
2907
 
2579
- // Apply the new state
2580
- stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
2581
- // Check if this state existed on the previous slide. If it
2582
- // did, we will avoid adding it repeatedly
2583
- for( var j = 0; j < stateBefore.length; j++ ) {
2584
- if( stateBefore[j] === state[i] ) {
2585
- stateBefore.splice( j, 1 );
2586
- continue stateLoop;
2587
- }
2588
- }
2589
-
2590
- document.documentElement.classList.add( state[i] );
2591
-
2592
- // Dispatch custom event matching the state's name
2593
- dispatchEvent( state[i] );
2594
- }
2595
-
2596
- // Clean up the remains of the previous state
2597
- while( stateBefore.length ) {
2598
- document.documentElement.classList.remove( stateBefore.pop() );
2599
- }
2600
-
2601
2908
  // Update the overview if it's currently active
2602
2909
  if( isOverview() ) {
2603
2910
  updateOverview();
@@ -2646,6 +2953,28 @@
2646
2953
  }
2647
2954
  }
2648
2955
 
2956
+ // Apply the new state
2957
+ stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
2958
+ // Check if this state existed on the previous slide. If it
2959
+ // did, we will avoid adding it repeatedly
2960
+ for( var j = 0; j < stateBefore.length; j++ ) {
2961
+ if( stateBefore[j] === state[i] ) {
2962
+ stateBefore.splice( j, 1 );
2963
+ continue stateLoop;
2964
+ }
2965
+ }
2966
+
2967
+ document.documentElement.classList.add( state[i] );
2968
+
2969
+ // Dispatch custom event matching the state's name
2970
+ dispatchEvent( state[i] );
2971
+ }
2972
+
2973
+ // Clean up the remains of the previous state
2974
+ while( stateBefore.length ) {
2975
+ document.documentElement.classList.remove( stateBefore.pop() );
2976
+ }
2977
+
2649
2978
  if( slideChanged ) {
2650
2979
  dispatchEvent( 'slidechanged', {
2651
2980
  'indexh': indexh,
@@ -2671,6 +3000,7 @@
2671
3000
  updateParallax();
2672
3001
  updateSlideNumber();
2673
3002
  updateNotes();
3003
+ updateFragments();
2674
3004
 
2675
3005
  // Update the URL hash
2676
3006
  writeURL();
@@ -2743,14 +3073,17 @@
2743
3073
  */
2744
3074
  function syncSlide( slide ) {
2745
3075
 
3076
+ // Default to the current slide
3077
+ slide = slide || currentSlide;
3078
+
2746
3079
  syncBackground( slide );
2747
3080
  syncFragments( slide );
2748
3081
 
3082
+ loadSlide( slide );
3083
+
2749
3084
  updateBackground();
2750
3085
  updateNotes();
2751
3086
 
2752
- loadSlide( slide );
2753
-
2754
3087
  }
2755
3088
 
2756
3089
  /**
@@ -2759,10 +3092,14 @@
2759
3092
  * after reveal.js has already initialized.
2760
3093
  *
2761
3094
  * @param {HTMLElement} slide
3095
+ * @return {Array} a list of the HTML fragments that were synced
2762
3096
  */
2763
3097
  function syncFragments( slide ) {
2764
3098
 
2765
- sortFragments( slide.querySelectorAll( '.fragment' ) );
3099
+ // Default to the current slide
3100
+ slide = slide || currentSlide;
3101
+
3102
+ return sortFragments( slide.querySelectorAll( '.fragment' ) );
2766
3103
 
2767
3104
  }
2768
3105
 
@@ -2895,14 +3232,11 @@
2895
3232
  element.classList.add( reverse ? 'future' : 'past' );
2896
3233
 
2897
3234
  if( config.fragments ) {
2898
- var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
2899
-
2900
- // Show all fragments on prior slides
2901
- while( pastFragments.length ) {
2902
- var pastFragment = pastFragments.pop();
2903
- pastFragment.classList.add( 'visible' );
2904
- pastFragment.classList.remove( 'current-fragment' );
2905
- }
3235
+ // Show all fragments in prior slides
3236
+ toArray( element.querySelectorAll( '.fragment' ) ).forEach( function( fragment ) {
3237
+ fragment.classList.add( 'visible' );
3238
+ fragment.classList.remove( 'current-fragment' );
3239
+ } );
2906
3240
  }
2907
3241
  }
2908
3242
  else if( i > index ) {
@@ -2910,14 +3244,11 @@
2910
3244
  element.classList.add( reverse ? 'past' : 'future' );
2911
3245
 
2912
3246
  if( config.fragments ) {
2913
- var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
2914
-
2915
- // No fragments in future slides should be visible ahead of time
2916
- while( futureFragments.length ) {
2917
- var futureFragment = futureFragments.pop();
2918
- futureFragment.classList.remove( 'visible' );
2919
- futureFragment.classList.remove( 'current-fragment' );
2920
- }
3247
+ // Hide all fragments in future slides
3248
+ toArray( element.querySelectorAll( '.fragment.visible' ) ).forEach( function( fragment ) {
3249
+ fragment.classList.remove( 'visible' );
3250
+ fragment.classList.remove( 'current-fragment' );
3251
+ } );
2921
3252
  }
2922
3253
  }
2923
3254
  }
@@ -2964,9 +3295,10 @@
2964
3295
  // be visible
2965
3296
  var viewDistance = isOverview() ? 10 : config.viewDistance;
2966
3297
 
2967
- // Limit view distance on weaker devices
3298
+ // Shorten the view distance on devices that typically have
3299
+ // less resources
2968
3300
  if( isMobileDevice ) {
2969
- viewDistance = isOverview() ? 6 : 2;
3301
+ viewDistance = isOverview() ? 6 : config.mobileViewDistance;
2970
3302
  }
2971
3303
 
2972
3304
  // All slides need to be visible when exporting to PDF
@@ -3018,7 +3350,7 @@
3018
3350
  }
3019
3351
 
3020
3352
  // Flag if there are ANY vertical slides, anywhere in the deck
3021
- if( dom.wrapper.querySelectorAll( '.slides>section>section' ).length ) {
3353
+ if( hasVerticalSlides() ) {
3022
3354
  dom.wrapper.classList.add( 'has-vertical-slides' );
3023
3355
  }
3024
3356
  else {
@@ -3026,7 +3358,7 @@
3026
3358
  }
3027
3359
 
3028
3360
  // Flag if there are ANY horizontal slides, anywhere in the deck
3029
- if( dom.wrapper.querySelectorAll( '.slides>section' ).length > 1 ) {
3361
+ if( hasHorizontalSlides() ) {
3030
3362
  dom.wrapper.classList.add( 'has-horizontal-slides' );
3031
3363
  }
3032
3364
  else {
@@ -3096,22 +3428,32 @@
3096
3428
 
3097
3429
 
3098
3430
  /**
3099
- * Updates the slide number div to reflect the current slide.
3100
- *
3101
- * The following slide number formats are available:
3102
- * "h.v": horizontal . vertical slide number (default)
3103
- * "h/v": horizontal / vertical slide number
3104
- * "c": flattened slide number
3105
- * "c/t": flattened slide number / total slides
3431
+ * Updates the slide number to match the current slide.
3106
3432
  */
3107
3433
  function updateSlideNumber() {
3108
3434
 
3109
3435
  // Update slide number if enabled
3110
3436
  if( config.slideNumber && dom.slideNumber ) {
3437
+ dom.slideNumber.innerHTML = getSlideNumber();
3438
+ }
3439
+
3440
+ }
3441
+
3442
+ /**
3443
+ * Returns the HTML string corresponding to the current slide number,
3444
+ * including formatting.
3445
+ */
3446
+ function getSlideNumber( slide ) {
3111
3447
 
3112
- var value = [];
3113
- var format = 'h.v';
3448
+ var value;
3449
+ var format = 'h.v';
3450
+ if( slide === undefined ) {
3451
+ slide = currentSlide;
3452
+ }
3114
3453
 
3454
+ if ( typeof config.slideNumber === 'function' ) {
3455
+ value = config.slideNumber( slide );
3456
+ } else {
3115
3457
  // Check if a custom number format is available
3116
3458
  if( typeof config.slideNumber === 'string' ) {
3117
3459
  format = config.slideNumber;
@@ -3123,25 +3465,25 @@
3123
3465
  format = 'c';
3124
3466
  }
3125
3467
 
3468
+ value = [];
3126
3469
  switch( format ) {
3127
3470
  case 'c':
3128
- value.push( getSlidePastCount() + 1 );
3471
+ value.push( getSlidePastCount( slide ) + 1 );
3129
3472
  break;
3130
3473
  case 'c/t':
3131
- value.push( getSlidePastCount() + 1, '/', getTotalSlides() );
3132
- break;
3133
- case 'h/v':
3134
- value.push( indexh + 1 );
3135
- if( isVerticalSlide() ) value.push( '/', indexv + 1 );
3474
+ value.push( getSlidePastCount( slide ) + 1, '/', getTotalSlides() );
3136
3475
  break;
3137
3476
  default:
3138
- value.push( indexh + 1 );
3139
- if( isVerticalSlide() ) value.push( '.', indexv + 1 );
3477
+ var indices = getIndices( slide );
3478
+ value.push( indices.h + 1 );
3479
+ var sep = format === 'h/v' ? '/' : '.';
3480
+ if( isVerticalSlide( slide ) ) value.push( sep, indices.v + 1 );
3140
3481
  }
3141
-
3142
- dom.slideNumber.innerHTML = formatSlideNumber( value[0], value[1], value[2] );
3143
3482
  }
3144
3483
 
3484
+ var url = '#' + locationHash( slide );
3485
+ return formatSlideNumber( value[0], value[1], value[2], url );
3486
+
3145
3487
  }
3146
3488
 
3147
3489
  /**
@@ -3151,11 +3493,14 @@
3151
3493
  * @param {number} a Current slide
3152
3494
  * @param {string} delimiter Character to separate slide numbers
3153
3495
  * @param {(number|*)} b Total slides
3496
+ * @param {HTMLElement} [url='#'+locationHash()] The url to link to
3154
3497
  * @return {string} HTML string fragment
3155
3498
  */
3156
- function formatSlideNumber( a, delimiter, b ) {
3499
+ function formatSlideNumber( a, delimiter, b, url ) {
3157
3500
 
3158
- var url = '#' + locationHash();
3501
+ if( url === undefined ) {
3502
+ url = '#' + locationHash();
3503
+ }
3159
3504
  if( typeof b === 'number' && !isNaN( b ) ) {
3160
3505
  return '<a href="' + url + '">' +
3161
3506
  '<span class="slide-number-a">'+ a +'</span>' +
@@ -3308,7 +3653,7 @@
3308
3653
  // Stop content inside of previous backgrounds
3309
3654
  if( previousBackground ) {
3310
3655
 
3311
- stopEmbeddedContent( previousBackground );
3656
+ stopEmbeddedContent( previousBackground, { unloadIframes: !shouldPreload( previousBackground ) } );
3312
3657
 
3313
3658
  }
3314
3659
 
@@ -3419,6 +3764,26 @@
3419
3764
 
3420
3765
  }
3421
3766
 
3767
+ /**
3768
+ * Should the given element be preloaded?
3769
+ * Decides based on local element attributes and global config.
3770
+ *
3771
+ * @param {HTMLElement} element
3772
+ */
3773
+ function shouldPreload( element ) {
3774
+
3775
+ // Prefer an explicit global preload setting
3776
+ var preload = config.preloadIframes;
3777
+
3778
+ // If no global setting is available, fall back on the element's
3779
+ // own preload setting
3780
+ if( typeof preload !== 'boolean' ) {
3781
+ preload = element.hasAttribute( 'data-preload' );
3782
+ }
3783
+
3784
+ return preload;
3785
+ }
3786
+
3422
3787
  /**
3423
3788
  * Called when the given slide is within the configured view
3424
3789
  * distance. Shows the slide element and loads any content
@@ -3434,10 +3799,12 @@
3434
3799
  slide.style.display = config.display;
3435
3800
 
3436
3801
  // Media elements with data-src attributes
3437
- toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
3438
- element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
3439
- element.setAttribute( 'data-lazy-loaded', '' );
3440
- element.removeAttribute( 'data-src' );
3802
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) {
3803
+ if( element.tagName !== 'IFRAME' || shouldPreload( element ) ) {
3804
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
3805
+ element.setAttribute( 'data-lazy-loaded', '' );
3806
+ element.removeAttribute( 'data-src' );
3807
+ }
3441
3808
  } );
3442
3809
 
3443
3810
  // Media elements with <source> children
@@ -3465,6 +3832,7 @@
3465
3832
  background.style.display = 'block';
3466
3833
 
3467
3834
  var backgroundContent = slide.slideBackgroundContentElement;
3835
+ var backgroundIframe = slide.getAttribute( 'data-background-iframe' );
3468
3836
 
3469
3837
  // If the background contains media, load it
3470
3838
  if( background.hasAttribute( 'data-loaded' ) === false ) {
@@ -3473,8 +3841,7 @@
3473
3841
  var backgroundImage = slide.getAttribute( 'data-background-image' ),
3474
3842
  backgroundVideo = slide.getAttribute( 'data-background-video' ),
3475
3843
  backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
3476
- backgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' ),
3477
- backgroundIframe = slide.getAttribute( 'data-background-iframe' );
3844
+ backgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' );
3478
3845
 
3479
3846
  // Images
3480
3847
  if( backgroundImage ) {
@@ -3514,15 +3881,9 @@
3514
3881
  iframe.setAttribute( 'allowfullscreen', '' );
3515
3882
  iframe.setAttribute( 'mozallowfullscreen', '' );
3516
3883
  iframe.setAttribute( 'webkitallowfullscreen', '' );
3884
+ iframe.setAttribute( 'allow', 'autoplay' );
3517
3885
 
3518
- // Only load autoplaying content when the slide is shown to
3519
- // avoid having it play in the background
3520
- if( /autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {
3521
- iframe.setAttribute( 'data-src', backgroundIframe );
3522
- }
3523
- else {
3524
- iframe.setAttribute( 'src', backgroundIframe );
3525
- }
3886
+ iframe.setAttribute( 'data-src', backgroundIframe );
3526
3887
 
3527
3888
  iframe.style.width = '100%';
3528
3889
  iframe.style.height = '100%';
@@ -3533,6 +3894,19 @@
3533
3894
  }
3534
3895
  }
3535
3896
 
3897
+ // Start loading preloadable iframes
3898
+ var backgroundIframeElement = backgroundContent.querySelector( 'iframe[data-src]' );
3899
+ if( backgroundIframeElement ) {
3900
+
3901
+ // Check if this iframe is eligible to be preloaded
3902
+ if( shouldPreload( background ) && !/autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {
3903
+ if( backgroundIframeElement.getAttribute( 'src' ) !== backgroundIframe ) {
3904
+ backgroundIframeElement.setAttribute( 'src', backgroundIframe );
3905
+ }
3906
+ }
3907
+
3908
+ }
3909
+
3536
3910
  }
3537
3911
 
3538
3912
  }
@@ -3552,10 +3926,15 @@
3552
3926
  var background = getSlideBackground( slide );
3553
3927
  if( background ) {
3554
3928
  background.style.display = 'none';
3929
+
3930
+ // Unload any background iframes
3931
+ toArray( background.querySelectorAll( 'iframe[src]' ) ).forEach( function( element ) {
3932
+ element.removeAttribute( 'src' );
3933
+ } );
3555
3934
  }
3556
3935
 
3557
3936
  // 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 ) {
3937
+ toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src], iframe[data-lazy-loaded][src]' ) ).forEach( function( element ) {
3559
3938
  element.setAttribute( 'data-src', element.getAttribute( 'src' ) );
3560
3939
  element.removeAttribute( 'src' );
3561
3940
  } );
@@ -3655,13 +4034,6 @@
3655
4034
  _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
3656
4035
  _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
3657
4036
 
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
-
3665
4037
  }
3666
4038
 
3667
4039
  /**
@@ -3705,7 +4077,20 @@
3705
4077
  // Mobile devices never fire a loaded event so instead
3706
4078
  // of waiting, we initiate playback
3707
4079
  else if( isMobileDevice ) {
3708
- el.play();
4080
+ var promise = el.play();
4081
+
4082
+ // If autoplay does not work, ensure that the controls are visible so
4083
+ // that the viewer can start the media on their own
4084
+ if( promise && typeof promise.catch === 'function' && el.controls === false ) {
4085
+ promise.catch( function() {
4086
+ el.controls = true;
4087
+
4088
+ // Once the video does start playing, hide the controls again
4089
+ el.addEventListener( 'play', function() {
4090
+ el.controls = false;
4091
+ } );
4092
+ } );
4093
+ }
3709
4094
  }
3710
4095
  // If the media isn't loaded, wait before playing
3711
4096
  else {
@@ -3866,9 +4251,15 @@
3866
4251
  * Returns the number of past slides. This can be used as a global
3867
4252
  * flattened index for slides.
3868
4253
  *
4254
+ * @param {HTMLElement} [slide=currentSlide] The slide we're counting before
4255
+ *
3869
4256
  * @return {number} Past slide count
3870
4257
  */
3871
- function getSlidePastCount() {
4258
+ function getSlidePastCount( slide ) {
4259
+
4260
+ if( slide === undefined ) {
4261
+ slide = currentSlide;
4262
+ }
3872
4263
 
3873
4264
  var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
3874
4265
 
@@ -3884,7 +4275,7 @@
3884
4275
  for( var j = 0; j < verticalSlides.length; j++ ) {
3885
4276
 
3886
4277
  // Stop as soon as we arrive at the present
3887
- if( verticalSlides[j].classList.contains( 'present' ) ) {
4278
+ if( verticalSlides[j] === slide ) {
3888
4279
  break mainLoop;
3889
4280
  }
3890
4281
 
@@ -3893,7 +4284,7 @@
3893
4284
  }
3894
4285
 
3895
4286
  // Stop as soon as we arrive at the present
3896
- if( horizontalSlide.classList.contains( 'present' ) ) {
4287
+ if( horizontalSlide === slide ) {
3897
4288
  break;
3898
4289
  }
3899
4290
 
@@ -3939,7 +4330,7 @@
3939
4330
 
3940
4331
  }
3941
4332
 
3942
- return pastCount / ( totalCount - 1 );
4333
+ return Math.min( pastCount / ( totalCount - 1 ), 1 );
3943
4334
 
3944
4335
  }
3945
4336
 
@@ -3966,9 +4357,9 @@
3966
4357
  var bits = hash.slice( 2 ).split( '/' ),
3967
4358
  name = hash.replace( /#|\//gi, '' );
3968
4359
 
3969
- // If the first bit is invalid and there is a name we can
3970
- // assume that this is a named link
3971
- if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
4360
+ // If the first bit is not fully numeric and there is a name we
4361
+ // can assume that this is a named link
4362
+ if( !/^[0-9]*$/.test( bits[0] ) && name.length ) {
3972
4363
  var element;
3973
4364
 
3974
4365
  // Ensure the named link is a valid HTML ID attribute
@@ -3980,10 +4371,13 @@
3980
4371
  // Ensure that we're not already on a slide with the same name
3981
4372
  var isSameNameAsCurrentSlide = currentSlide ? currentSlide.getAttribute( 'id' ) === name : false;
3982
4373
 
3983
- if( element && !isSameNameAsCurrentSlide ) {
3984
- // Find the position of the named slide and navigate to it
3985
- var indices = Reveal.getIndices( element );
3986
- slide( indices.h, indices.v );
4374
+ if( element ) {
4375
+ // If the slide exists and is not the current slide...
4376
+ if ( !isSameNameAsCurrentSlide ) {
4377
+ // ...find the position of the named slide and navigate to it
4378
+ var indices = Reveal.getIndices(element);
4379
+ slide(indices.h, indices.v);
4380
+ }
3987
4381
  }
3988
4382
  // If the slide doesn't exist, navigate to the current slide
3989
4383
  else {
@@ -4021,18 +4415,30 @@
4021
4415
  */
4022
4416
  function writeURL( delay ) {
4023
4417
 
4024
- if( config.history ) {
4025
-
4026
- // Make sure there's never more than one timeout running
4027
- clearTimeout( writeURLTimeout );
4418
+ // Make sure there's never more than one timeout running
4419
+ clearTimeout( writeURLTimeout );
4028
4420
 
4029
- // If a delay is specified, timeout this call
4030
- if( typeof delay === 'number' ) {
4031
- writeURLTimeout = setTimeout( writeURL, delay );
4032
- }
4033
- else if( currentSlide ) {
4421
+ // If a delay is specified, timeout this call
4422
+ if( typeof delay === 'number' ) {
4423
+ writeURLTimeout = setTimeout( writeURL, delay );
4424
+ }
4425
+ else if( currentSlide ) {
4426
+ // If we're configured to push to history OR the history
4427
+ // API is not avaialble.
4428
+ if( config.history || !window.history ) {
4034
4429
  window.location.hash = locationHash();
4035
4430
  }
4431
+ // If we're configured to reflect the current slide in the
4432
+ // URL without pushing to history.
4433
+ else if( config.hash ) {
4434
+ window.history.replaceState( null, null, '#' + locationHash() );
4435
+ }
4436
+ // If history and hash are both disabled, a hash may still
4437
+ // be added to the URL by clicking on a href with a hash
4438
+ // target. Counter this by always removing the hash.
4439
+ else {
4440
+ window.history.replaceState( null, null, window.location.pathname + window.location.search );
4441
+ }
4036
4442
  }
4037
4443
 
4038
4444
  }
@@ -4095,7 +4501,63 @@
4095
4501
  */
4096
4502
  function getSlides() {
4097
4503
 
4098
- return toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ));
4504
+ return toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ) );
4505
+
4506
+ }
4507
+
4508
+ /**
4509
+ * Returns a list of all horizontal slides in the deck. Each
4510
+ * vertical stack is included as one horizontal slide in the
4511
+ * resulting array.
4512
+ */
4513
+ function getHorizontalSlides() {
4514
+
4515
+ return toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
4516
+
4517
+ }
4518
+
4519
+ /**
4520
+ * Returns all vertical slides that exist within this deck.
4521
+ */
4522
+ function getVerticalSlides() {
4523
+
4524
+ return toArray( dom.wrapper.querySelectorAll( '.slides>section>section' ) );
4525
+
4526
+ }
4527
+
4528
+ /**
4529
+ * Returns true if there are at least two horizontal slides.
4530
+ */
4531
+ function hasHorizontalSlides() {
4532
+
4533
+ return getHorizontalSlides().length > 1;
4534
+ }
4535
+
4536
+ /**
4537
+ * Returns true if there are at least two vertical slides.
4538
+ */
4539
+ function hasVerticalSlides() {
4540
+
4541
+ return getVerticalSlides().length > 1;
4542
+
4543
+ }
4544
+
4545
+ /**
4546
+ * Returns an array of objects where each object represents the
4547
+ * attributes on its respective slide.
4548
+ */
4549
+ function getSlidesAttributes() {
4550
+
4551
+ return getSlides().map( function( slide ) {
4552
+
4553
+ var attributes = {};
4554
+ for( var i = 0; i < slide.attributes.length; i++ ) {
4555
+ var attribute = slide.attributes[ i ];
4556
+ attributes[ attribute.name ] = attribute.value;
4557
+ }
4558
+ return attributes;
4559
+
4560
+ } );
4099
4561
 
4100
4562
  }
4101
4563
 
@@ -4291,6 +4753,84 @@
4291
4753
 
4292
4754
  }
4293
4755
 
4756
+ /**
4757
+ * Refreshes the fragments on the current slide so that they
4758
+ * have the appropriate classes (.visible + .current-fragment).
4759
+ *
4760
+ * @param {number} [index] The index of the current fragment
4761
+ * @param {array} [fragments] Array containing all fragments
4762
+ * in the current slide
4763
+ *
4764
+ * @return {{shown: array, hidden: array}}
4765
+ */
4766
+ function updateFragments( index, fragments ) {
4767
+
4768
+ var changedFragments = {
4769
+ shown: [],
4770
+ hidden: []
4771
+ };
4772
+
4773
+ if( currentSlide && config.fragments ) {
4774
+
4775
+ fragments = fragments || sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
4776
+
4777
+ if( fragments.length ) {
4778
+
4779
+ var maxIndex = 0;
4780
+
4781
+ if( typeof index !== 'number' ) {
4782
+ var currentFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
4783
+ if( currentFragment ) {
4784
+ index = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
4785
+ }
4786
+ }
4787
+
4788
+ toArray( fragments ).forEach( function( el, i ) {
4789
+
4790
+ if( el.hasAttribute( 'data-fragment-index' ) ) {
4791
+ i = parseInt( el.getAttribute( 'data-fragment-index' ), 10 );
4792
+ }
4793
+
4794
+ maxIndex = Math.max( maxIndex, i );
4795
+
4796
+ // Visible fragments
4797
+ if( i <= index ) {
4798
+ if( !el.classList.contains( 'visible' ) ) changedFragments.shown.push( el );
4799
+ el.classList.add( 'visible' );
4800
+ el.classList.remove( 'current-fragment' );
4801
+
4802
+ // Announce the fragments one by one to the Screen Reader
4803
+ dom.statusDiv.textContent = getStatusText( el );
4804
+
4805
+ if( i === index ) {
4806
+ el.classList.add( 'current-fragment' );
4807
+ startEmbeddedContent( el );
4808
+ }
4809
+ }
4810
+ // Hidden fragments
4811
+ else {
4812
+ if( el.classList.contains( 'visible' ) ) changedFragments.hidden.push( el );
4813
+ el.classList.remove( 'visible' );
4814
+ el.classList.remove( 'current-fragment' );
4815
+ }
4816
+
4817
+ } );
4818
+
4819
+ // Write the current fragment index to the slide <section>.
4820
+ // This can be used by end users to apply styles based on
4821
+ // the current fragment index.
4822
+ index = typeof index === 'number' ? index : -1;
4823
+ index = Math.max( Math.min( index, maxIndex ), -1 );
4824
+ currentSlide.setAttribute( 'data-fragment', index );
4825
+
4826
+ }
4827
+
4828
+ }
4829
+
4830
+ return changedFragments;
4831
+
4832
+ }
4833
+
4294
4834
  /**
4295
4835
  * Navigate to the specified slide fragment.
4296
4836
  *
@@ -4326,53 +4866,24 @@
4326
4866
  index += offset;
4327
4867
  }
4328
4868
 
4329
- var fragmentsShown = [],
4330
- fragmentsHidden = [];
4331
-
4332
- toArray( fragments ).forEach( function( element, i ) {
4333
-
4334
- if( element.hasAttribute( 'data-fragment-index' ) ) {
4335
- i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 );
4336
- }
4337
-
4338
- // Visible fragments
4339
- if( i <= index ) {
4340
- if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element );
4341
- element.classList.add( 'visible' );
4342
- element.classList.remove( 'current-fragment' );
4343
-
4344
- // Announce the fragments one by one to the Screen Reader
4345
- dom.statusDiv.textContent = getStatusText( element );
4869
+ var changedFragments = updateFragments( index, fragments );
4346
4870
 
4347
- if( i === index ) {
4348
- element.classList.add( 'current-fragment' );
4349
- startEmbeddedContent( element );
4350
- }
4351
- }
4352
- // Hidden fragments
4353
- else {
4354
- if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element );
4355
- element.classList.remove( 'visible' );
4356
- element.classList.remove( 'current-fragment' );
4357
- }
4358
-
4359
- } );
4360
-
4361
- if( fragmentsHidden.length ) {
4362
- dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } );
4871
+ if( changedFragments.hidden.length ) {
4872
+ dispatchEvent( 'fragmenthidden', { fragment: changedFragments.hidden[0], fragments: changedFragments.hidden } );
4363
4873
  }
4364
4874
 
4365
- if( fragmentsShown.length ) {
4366
- dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } );
4875
+ if( changedFragments.shown.length ) {
4876
+ dispatchEvent( 'fragmentshown', { fragment: changedFragments.shown[0], fragments: changedFragments.shown } );
4367
4877
  }
4368
4878
 
4369
4879
  updateControls();
4370
4880
  updateProgress();
4881
+
4371
4882
  if( config.fragmentInURL ) {
4372
4883
  writeURL();
4373
4884
  }
4374
4885
 
4375
- return !!( fragmentsShown.length || fragmentsHidden.length );
4886
+ return !!( changedFragments.shown.length || changedFragments.hidden.length );
4376
4887
 
4377
4888
  }
4378
4889
 
@@ -4519,12 +5030,12 @@
4519
5030
  // Reverse for RTL
4520
5031
  if( config.rtl ) {
4521
5032
  if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
4522
- slide( indexh + 1 );
5033
+ slide( indexh + 1, config.navigationMode === 'grid' ? indexv : undefined );
4523
5034
  }
4524
5035
  }
4525
5036
  // Normal navigation
4526
5037
  else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
4527
- slide( indexh - 1 );
5038
+ slide( indexh - 1, config.navigationMode === 'grid' ? indexv : undefined );
4528
5039
  }
4529
5040
 
4530
5041
  }
@@ -4536,12 +5047,12 @@
4536
5047
  // Reverse for RTL
4537
5048
  if( config.rtl ) {
4538
5049
  if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
4539
- slide( indexh - 1 );
5050
+ slide( indexh - 1, config.navigationMode === 'grid' ? indexv : undefined );
4540
5051
  }
4541
5052
  }
4542
5053
  // Normal navigation
4543
5054
  else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
4544
- slide( indexh + 1 );
5055
+ slide( indexh + 1, config.navigationMode === 'grid' ? indexv : undefined );
4545
5056
  }
4546
5057
 
4547
5058
  }
@@ -4667,6 +5178,22 @@
4667
5178
 
4668
5179
  }
4669
5180
 
5181
+ /**
5182
+ * Called whenever there is mouse input at the document level
5183
+ * to determine if the cursor is active or not.
5184
+ *
5185
+ * @param {object} event
5186
+ */
5187
+ function onDocumentCursorActive( event ) {
5188
+
5189
+ showCursor();
5190
+
5191
+ clearTimeout( cursorInactiveTimeout );
5192
+
5193
+ cursorInactiveTimeout = setTimeout( hideCursor, config.hideCursorTime );
5194
+
5195
+ }
5196
+
4670
5197
  /**
4671
5198
  * Handler for the document level 'keypress' event.
4672
5199
  *
@@ -4694,20 +5221,31 @@
4694
5221
  return true;
4695
5222
  }
4696
5223
 
5224
+ // Shorthand
5225
+ var keyCode = event.keyCode;
5226
+
4697
5227
  // Remember if auto-sliding was paused so we can toggle it
4698
5228
  var autoSlideWasPaused = autoSlidePaused;
4699
5229
 
4700
5230
  onUserInput( event );
4701
5231
 
4702
- // Check if there's a focused element that could be using
4703
- // the keyboard
5232
+ // Is there a focused element that could be using the keyboard?
4704
5233
  var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
4705
5234
  var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
4706
5235
  var activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);
4707
5236
 
5237
+ // Whitelist specific modified + keycode combinations
5238
+ var prevSlideShortcut = event.shiftKey && event.keyCode === 32;
5239
+ var firstSlideShortcut = event.shiftKey && keyCode === 37;
5240
+ var lastSlideShortcut = event.shiftKey && keyCode === 39;
5241
+
5242
+ // Prevent all other events when a modifier is pressed
5243
+ var unusedModifier = !prevSlideShortcut && !firstSlideShortcut && !lastSlideShortcut &&
5244
+ ( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey );
5245
+
4708
5246
  // Disregard the event if there's a focused element or a
4709
5247
  // keyboard modifier key is present
4710
- if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
5248
+ if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return;
4711
5249
 
4712
5250
  // While paused only allow resume keyboard events; 'b', 'v', '.'
4713
5251
  var resumeKeyCodes = [66,86,190,191];
@@ -4722,10 +5260,14 @@
4722
5260
  }
4723
5261
  }
4724
5262
 
4725
- if( isPaused() && resumeKeyCodes.indexOf( event.keyCode ) === -1 ) {
5263
+ if( isPaused() && resumeKeyCodes.indexOf( keyCode ) === -1 ) {
4726
5264
  return false;
4727
5265
  }
4728
5266
 
5267
+ // Use linear navigation if we're configured to OR if
5268
+ // the presentation is one-dimensional
5269
+ var useLinearMode = config.navigationMode === 'linear' || !hasHorizontalSlides() || !hasVerticalSlides();
5270
+
4729
5271
  var triggered = false;
4730
5272
 
4731
5273
  // 1. User defined key bindings
@@ -4734,7 +5276,7 @@
4734
5276
  for( key in config.keyboard ) {
4735
5277
 
4736
5278
  // Check if this binding matches the pressed key
4737
- if( parseInt( key, 10 ) === event.keyCode ) {
5279
+ if( parseInt( key, 10 ) === keyCode ) {
4738
5280
 
4739
5281
  var value = config.keyboard[ key ];
4740
5282
 
@@ -4761,7 +5303,7 @@
4761
5303
  for( key in registeredKeyBindings ) {
4762
5304
 
4763
5305
  // Check if this binding matches the pressed key
4764
- if( parseInt( key, 10 ) === event.keyCode ) {
5306
+ if( parseInt( key, 10 ) === keyCode ) {
4765
5307
 
4766
5308
  var action = registeredKeyBindings[ key ].callback;
4767
5309
 
@@ -4785,35 +5327,92 @@
4785
5327
  // Assume true and try to prove false
4786
5328
  triggered = true;
4787
5329
 
4788
- switch( event.keyCode ) {
4789
- // p, page up
4790
- case 80: case 33: navigatePrev(); break;
4791
- // n, page down
4792
- case 78: case 34: navigateNext(); break;
4793
- // h, left
4794
- case 72: case 37: navigateLeft(); break;
4795
- // l, right
4796
- case 76: case 39: navigateRight(); break;
4797
- // k, up
4798
- case 75: case 38: navigateUp(); break;
4799
- // j, down
4800
- case 74: case 40: navigateDown(); break;
4801
- // home
4802
- case 36: slide( 0 ); break;
4803
- // end
4804
- case 35: slide( Number.MAX_VALUE ); break;
4805
- // space
4806
- case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
4807
- // return
4808
- case 13: isOverview() ? deactivateOverview() : triggered = false; break;
4809
- // two-spot, semicolon, b, v, period, Logitech presenter tools "black screen" button
4810
- case 58: case 59: case 66: case 86: case 190: case 191: togglePause(); break;
4811
- // f
4812
- case 70: enterFullscreen(); break;
4813
- // a
4814
- case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
4815
- default:
4816
- triggered = false;
5330
+ // P, PAGE UP
5331
+ if( keyCode === 80 || keyCode === 33 ) {
5332
+ navigatePrev();
5333
+ }
5334
+ // N, PAGE DOWN
5335
+ else if( keyCode === 78 || keyCode === 34 ) {
5336
+ navigateNext();
5337
+ }
5338
+ // H, LEFT
5339
+ else if( keyCode === 72 || keyCode === 37 ) {
5340
+ if( firstSlideShortcut ) {
5341
+ slide( 0 );
5342
+ }
5343
+ else if( !isOverview() && useLinearMode ) {
5344
+ navigatePrev();
5345
+ }
5346
+ else {
5347
+ navigateLeft();
5348
+ }
5349
+ }
5350
+ // L, RIGHT
5351
+ else if( keyCode === 76 || keyCode === 39 ) {
5352
+ if( lastSlideShortcut ) {
5353
+ slide( Number.MAX_VALUE );
5354
+ }
5355
+ else if( !isOverview() && useLinearMode ) {
5356
+ navigateNext();
5357
+ }
5358
+ else {
5359
+ navigateRight();
5360
+ }
5361
+ }
5362
+ // K, UP
5363
+ else if( keyCode === 75 || keyCode === 38 ) {
5364
+ if( !isOverview() && useLinearMode ) {
5365
+ navigatePrev();
5366
+ }
5367
+ else {
5368
+ navigateUp();
5369
+ }
5370
+ }
5371
+ // J, DOWN
5372
+ else if( keyCode === 74 || keyCode === 40 ) {
5373
+ if( !isOverview() && useLinearMode ) {
5374
+ navigateNext();
5375
+ }
5376
+ else {
5377
+ navigateDown();
5378
+ }
5379
+ }
5380
+ // HOME
5381
+ else if( keyCode === 36 ) {
5382
+ slide( 0 );
5383
+ }
5384
+ // END
5385
+ else if( keyCode === 35 ) {
5386
+ slide( Number.MAX_VALUE );
5387
+ }
5388
+ // SPACE
5389
+ else if( keyCode === 32 ) {
5390
+ if( isOverview() ) {
5391
+ deactivateOverview();
5392
+ }
5393
+ if( event.shiftKey ) {
5394
+ navigatePrev();
5395
+ }
5396
+ else {
5397
+ navigateNext();
5398
+ }
5399
+ }
5400
+ // TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON
5401
+ else if( keyCode === 58 || keyCode === 59 || keyCode === 66 || keyCode === 86 || keyCode === 190 || keyCode === 191 ) {
5402
+ togglePause();
5403
+ }
5404
+ // F
5405
+ else if( keyCode === 70 ) {
5406
+ enterFullscreen();
5407
+ }
5408
+ // A
5409
+ else if( keyCode === 65 ) {
5410
+ if ( config.autoSlideStoppable ) {
5411
+ toggleAutoSlide( autoSlideWasPaused );
5412
+ }
5413
+ }
5414
+ else {
5415
+ triggered = false;
4817
5416
  }
4818
5417
 
4819
5418
  }
@@ -4824,7 +5423,7 @@
4824
5423
  event.preventDefault && event.preventDefault();
4825
5424
  }
4826
5425
  // ESC or O key
4827
- else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
5426
+ else if ( ( keyCode === 27 || keyCode === 79 ) && features.transforms3d ) {
4828
5427
  if( dom.overlay ) {
4829
5428
  closeOverlay();
4830
5429
  }
@@ -4855,18 +5454,6 @@
4855
5454
  touch.startY = event.touches[0].clientY;
4856
5455
  touch.startCount = event.touches.length;
4857
5456
 
4858
- // If there's two touches we need to memorize the distance
4859
- // between those two points to detect pinching
4860
- if( event.touches.length === 2 && config.overview ) {
4861
- touch.startSpan = distanceBetween( {
4862
- x: event.touches[1].clientX,
4863
- y: event.touches[1].clientY
4864
- }, {
4865
- x: touch.startX,
4866
- y: touch.startY
4867
- } );
4868
- }
4869
-
4870
5457
  }
4871
5458
 
4872
5459
  /**
@@ -4885,56 +5472,57 @@
4885
5472
  var currentX = event.touches[0].clientX;
4886
5473
  var currentY = event.touches[0].clientY;
4887
5474
 
4888
- // If the touch started with two points and still has
4889
- // two active touches; test for the pinch gesture
4890
- if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
4891
-
4892
- // The current distance in pixels between the two touch points
4893
- var currentSpan = distanceBetween( {
4894
- x: event.touches[1].clientX,
4895
- y: event.touches[1].clientY
4896
- }, {
4897
- x: touch.startX,
4898
- y: touch.startY
4899
- } );
4900
-
4901
- // If the span is larger than the desire amount we've got
4902
- // ourselves a pinch
4903
- if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
4904
- touch.captured = true;
4905
-
4906
- if( currentSpan < touch.startSpan ) {
4907
- activateOverview();
4908
- }
4909
- else {
4910
- deactivateOverview();
4911
- }
4912
- }
4913
-
4914
- event.preventDefault();
4915
-
4916
- }
4917
5475
  // There was only one touch point, look for a swipe
4918
- else if( event.touches.length === 1 && touch.startCount !== 2 ) {
5476
+ if( event.touches.length === 1 && touch.startCount !== 2 ) {
4919
5477
 
4920
5478
  var deltaX = currentX - touch.startX,
4921
5479
  deltaY = currentY - touch.startY;
4922
5480
 
4923
5481
  if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
4924
5482
  touch.captured = true;
4925
- navigateLeft();
5483
+ if( config.navigationMode === 'linear' ) {
5484
+ if( config.rtl ) {
5485
+ navigateNext();
5486
+ }
5487
+ else {
5488
+ navigatePrev();
5489
+ }
5490
+ }
5491
+ else {
5492
+ navigateLeft();
5493
+ }
4926
5494
  }
4927
5495
  else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
4928
5496
  touch.captured = true;
4929
- navigateRight();
5497
+ if( config.navigationMode === 'linear' ) {
5498
+ if( config.rtl ) {
5499
+ navigatePrev();
5500
+ }
5501
+ else {
5502
+ navigateNext();
5503
+ }
5504
+ }
5505
+ else {
5506
+ navigateRight();
5507
+ }
4930
5508
  }
4931
5509
  else if( deltaY > touch.threshold ) {
4932
5510
  touch.captured = true;
4933
- navigateUp();
5511
+ if( config.navigationMode === 'linear' ) {
5512
+ navigatePrev();
5513
+ }
5514
+ else {
5515
+ navigateUp();
5516
+ }
4934
5517
  }
4935
5518
  else if( deltaY < -touch.threshold ) {
4936
5519
  touch.captured = true;
4937
- navigateDown();
5520
+ if( config.navigationMode === 'linear' ) {
5521
+ navigateNext();
5522
+ }
5523
+ else {
5524
+ navigateDown();
5525
+ }
4938
5526
  }
4939
5527
 
4940
5528
  // If we're embedded, only block touch events if they have
@@ -5065,8 +5653,8 @@
5065
5653
  /**
5066
5654
  * Event handler for navigation control buttons.
5067
5655
  */
5068
- function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }
5069
- function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }
5656
+ function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); config.navigationMode === 'linear' ? navigatePrev() : navigateLeft(); }
5657
+ function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); config.navigationMode === 'linear' ? navigateNext() : navigateRight(); }
5070
5658
  function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }
5071
5659
  function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }
5072
5660
  function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }
@@ -5455,6 +6043,10 @@
5455
6043
  // Returns an Array of all slides
5456
6044
  getSlides: getSlides,
5457
6045
 
6046
+ // Returns an Array of objects representing the attributes on
6047
+ // the slides
6048
+ getSlidesAttributes: getSlidesAttributes,
6049
+
5458
6050
  // Returns the total number of slides
5459
6051
  getTotalSlides: getTotalSlides,
5460
6052
 
@@ -5467,6 +6059,15 @@
5467
6059
  // Returns the speaker notes string for a slide, or null
5468
6060
  getSlideNotes: getSlideNotes,
5469
6061
 
6062
+ // Returns an array with all horizontal/vertical slides in the deck
6063
+ getHorizontalSlides: getHorizontalSlides,
6064
+ getVerticalSlides: getVerticalSlides,
6065
+
6066
+ // Checks if the presentation contains two or more
6067
+ // horizontal/vertical slides
6068
+ hasHorizontalSlides: hasHorizontalSlides,
6069
+ hasVerticalSlides: hasVerticalSlides,
6070
+
5470
6071
  // Returns the previous slide element, may be null
5471
6072
  getPreviousSlide: function() {
5472
6073
  return previousSlide;
@@ -5505,6 +6106,16 @@
5505
6106
  return query;
5506
6107
  },
5507
6108
 
6109
+ // Returns the top-level DOM element
6110
+ getRevealElement: function() {
6111
+ return dom.wrapper || document.querySelector( '.reveal' );
6112
+ },
6113
+
6114
+ // Returns a hash with all registered plugins
6115
+ getPlugins: function() {
6116
+ return plugins;
6117
+ },
6118
+
5508
6119
  // Returns true if we're currently on the first slide
5509
6120
  isFirstSlide: function() {
5510
6121
  return ( indexh === 0 && indexv === 0 );
@@ -5546,22 +6157,25 @@
5546
6157
  // Forward event binding to the reveal DOM element
5547
6158
  addEventListener: function( type, listener, useCapture ) {
5548
6159
  if( 'addEventListener' in window ) {
5549
- ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
6160
+ Reveal.getRevealElement().addEventListener( type, listener, useCapture );
5550
6161
  }
5551
6162
  },
5552
6163
  removeEventListener: function( type, listener, useCapture ) {
5553
6164
  if( 'addEventListener' in window ) {
5554
- ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
6165
+ Reveal.getRevealElement().removeEventListener( type, listener, useCapture );
5555
6166
  }
5556
6167
  },
5557
6168
 
5558
- // Adds a custom key binding
6169
+ // Adds/removes a custom key binding
5559
6170
  addKeyBinding: addKeyBinding,
5560
-
5561
- // Removes a custom key binding
5562
6171
  removeKeyBinding: removeKeyBinding,
5563
6172
 
5564
- // Programatically triggers a keyboard event
6173
+ // API for registering and retrieving plugins
6174
+ registerPlugin: registerPlugin,
6175
+ hasPlugin: hasPlugin,
6176
+ getPlugin: getPlugin,
6177
+
6178
+ // Programmatically triggers a keyboard event
5565
6179
  triggerKey: function( keyCode ) {
5566
6180
  onDocumentKeyDown( { keyCode: keyCode } );
5567
6181
  },