reveal-ck 3.9.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
  },