Sfoglia il codice sorgente

Merge branch 'dev' into hash-problem

Hakim El Hattab 5 anni fa
parent
commit
b5fe0f8126
61 ha cambiato i file con 2049 aggiunte e 819 eliminazioni
  1. 1 1
      .travis.yml
  2. 11 20
      Gruntfile.js
  3. 1 1
      LICENSE
  4. 68 16
      README.md
  5. 30 0
      css/reset.css
  6. 92 85
      css/reveal.css
  7. 91 87
      css/reveal.scss
  8. 1 1
      css/theme/beige.css
  9. 4 4
      css/theme/black.css
  10. 1 1
      css/theme/blood.css
  11. 1 1
      css/theme/league.css
  12. 1 1
      css/theme/moon.css
  13. 1 1
      css/theme/night.css
  14. 1 1
      css/theme/serif.css
  15. 1 1
      css/theme/simple.css
  16. 1 1
      css/theme/sky.css
  17. 1 1
      css/theme/solarized.css
  18. 1 1
      css/theme/source/black.scss
  19. 3 1
      css/theme/template/settings.scss
  20. 3 3
      css/theme/template/theme.scss
  21. 1 1
      css/theme/white.css
  22. 35 26
      demo.html
  23. 3 3
      index.html
  24. 664 285
      js/reveal.js
  25. 71 0
      lib/css/monokai.css
  26. 0 2
      lib/js/classList.js
  27. 0 6
      lib/js/head.min.js
  28. 2 0
      lib/js/promise.js
  29. 13 11
      package.json
  30. 111 21
      plugin/highlight/highlight.js
  31. 1 3
      plugin/markdown/example.html
  32. 107 73
      plugin/markdown/markdown.js
  33. 3 3
      plugin/markdown/marked.js
  34. 51 26
      plugin/math/math.js
  35. 118 76
      plugin/notes/notes.html
  36. 46 15
      plugin/notes/notes.js
  37. 1 1
      plugin/search/search.js
  38. 22 17
      plugin/zoom-js/zoom.js
  39. 1 0
      test/assets/external-script-a.js
  40. 1 0
      test/assets/external-script-b.js
  41. 1 0
      test/assets/external-script-c.js
  42. 1 0
      test/assets/external-script-d.js
  43. 2 0
      test/examples/assets/beeping.txt
  44. BIN
      test/examples/assets/beeping.wav
  45. 5 1
      test/examples/embedded-media.html
  46. 23 3
      test/examples/math.html
  47. 0 1
      test/examples/slide-backgrounds.html
  48. 0 1
      test/examples/slide-transitions.html
  49. 78 0
      test/test-dependencies-async.html
  50. 54 0
      test/test-dependencies.html
  51. 74 0
      test/test-grid-navigation.html
  52. 1 3
      test/test-markdown-element-attributes.html
  53. 5 4
      test/test-markdown-external.html
  54. 0 1
      test/test-markdown-options.html
  55. 0 1
      test/test-markdown-slide-attributes.html
  56. 0 1
      test/test-markdown.html
  57. 1 2
      test/test-pdf.html
  58. 92 0
      test/test-plugins.html
  59. 139 0
      test/test-state.html
  60. 3 4
      test/test.html
  61. 5 0
      test/test.js

+ 1 - 1
.travis.yml

@@ -1,5 +1,5 @@
 language: node_js
 node_js:
-  - 4
+  - 11
 after_script:
   - npm run build -- retire

+ 11 - 20
Gruntfile.js

@@ -1,7 +1,11 @@
 /* global module:false */
 module.exports = function(grunt) {
-	var port = grunt.option('port') || 8000;
-	var root = grunt.option('root') || '.';
+	const sass = require('node-sass');
+
+	require('load-grunt-tasks')(grunt);
+
+	let port = grunt.option('port') || 8000;
+	let root = grunt.option('root') || '.';
 
 	if (!Array.isArray(root)) root = [root];
 
@@ -15,7 +19,7 @@ module.exports = function(grunt) {
 				' * http://revealjs.com\n' +
 				' * MIT licensed\n' +
 				' *\n' +
-				' * Copyright (C) 2018 Hakim El Hattab, http://hakim.se\n' +
+				' * Copyright (C) 2019 Hakim El Hattab, http://hakim.se\n' +
 				' */'
 		},
 
@@ -35,6 +39,10 @@ module.exports = function(grunt) {
 		},
 
 		sass: {
+			options: {
+				implementation: sass,
+				sourceMap: false
+			},
 			core: {
 				src: 'css/reveal.scss',
 				dest: 'css/reveal.css'
@@ -145,27 +153,10 @@ module.exports = function(grunt) {
 			options: {
 				livereload: true
 			}
-		},
-
-		retire: {
-			js: [ 'js/reveal.js', 'lib/js/*.js', 'plugin/**/*.js' ],
-			node: [ '.' ]
 		}
 
 	});
 
-	// Dependencies
-	grunt.loadNpmTasks( 'grunt-contrib-connect' );
-	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
-	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
-	grunt.loadNpmTasks( 'grunt-contrib-qunit' );
-	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
-	grunt.loadNpmTasks( 'grunt-contrib-watch' );
-	grunt.loadNpmTasks( 'grunt-autoprefixer' );
-	grunt.loadNpmTasks( 'grunt-retire' );
-	grunt.loadNpmTasks( 'grunt-sass' );
-	grunt.loadNpmTasks( 'grunt-zip' );
-
 	// Default task
 	grunt.registerTask( 'default', [ 'css', 'js' ] );
 

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (C) 2018 Hakim El Hattab, http://hakim.se, and reveal.js contributors
+Copyright (C) 2019 Hakim El Hattab, http://hakim.se, and reveal.js contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

File diff suppressed because it is too large
+ 68 - 16
README.md


+ 30 - 0
css/reset.css

@@ -0,0 +1,30 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+   v4.0 | 20180602
+   License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+main, menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-size: 100%;
+  font: inherit;
+  vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, main, menu, nav, section {
+  display: block;
+}

File diff suppressed because it is too large
+ 92 - 85
css/reveal.css


File diff suppressed because it is too large
+ 91 - 87
css/reveal.scss


+ 1 - 1
css/theme/beige.css

@@ -153,7 +153,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 4 - 4
css/theme/black.css

@@ -11,8 +11,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
  * GLOBAL STYLES
  *********************************************/
 body {
-  background: #222;
-  background-color: #222; }
+  background: #191919;
+  background-color: #191919; }
 
 .reveal {
   font-family: "Source Sans Pro", Helvetica, sans-serif;
@@ -149,7 +149,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;
@@ -270,4 +270,4 @@ body {
  *********************************************/
 @media print {
   .backgrounds {
-    background-color: #222; } }
+    background-color: #191919; } }

+ 1 - 1
css/theme/blood.css

@@ -152,7 +152,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/league.css

@@ -155,7 +155,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/moon.css

@@ -153,7 +153,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/night.css

@@ -147,7 +147,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/serif.css

@@ -149,7 +149,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/simple.css

@@ -152,7 +152,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/sky.css

@@ -156,7 +156,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/solarized.css

@@ -153,7 +153,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 1 - 1
css/theme/source/black.scss

@@ -16,7 +16,7 @@
 
 
 // Override theme settings (see ../template/settings.scss)
-$backgroundColor: #222;
+$backgroundColor: #191919;
 
 $mainColor: #fff;
 $headingColor: #fff;

+ 3 - 1
css/theme/template/settings.scss

@@ -28,6 +28,8 @@ $heading2Size: 2.11em;
 $heading3Size: 1.55em;
 $heading4Size: 1.00em;
 
+$codeFont: monospace;
+
 // Links and actions
 $linkColor: #13DAEC;
 $linkColorHover: lighten( $linkColor, 20% );
@@ -40,4 +42,4 @@ $selectionColor: #fff;
 // to return a background image or gradient
 @mixin bodyBackground() {
 	background: $backgroundColor;
-}
+}

+ 3 - 3
css/theme/template/theme.scss

@@ -162,16 +162,16 @@ body {
 
 	text-align: left;
 	font-size: 0.55em;
-	font-family: monospace;
+	font-family: $codeFont;
 	line-height: 1.2em;
 
 	word-wrap: break-word;
 
-	box-shadow: 0px 0px 6px rgba(0,0,0,0.3);
+	box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
 }
 
 .reveal code {
-	font-family: monospace;
+	font-family: $codeFont;
 	text-transform: none;
 }
 

+ 1 - 1
css/theme/white.css

@@ -149,7 +149,7 @@ body {
   font-family: monospace;
   line-height: 1.2em;
   word-wrap: break-word;
-  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); }
 
 .reveal code {
   font-family: monospace;

+ 35 - 26
demo.html

@@ -12,13 +12,14 @@
 		<meta name="apple-mobile-web-app-capable" content="yes">
 		<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
 
-		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
+		<link rel="stylesheet" href="css/reset.css">
 		<link rel="stylesheet" href="css/reveal.css">
 		<link rel="stylesheet" href="css/theme/black.css" id="theme">
 
 		<!-- Theme used for syntax highlighting of code -->
-		<link rel="stylesheet" href="lib/css/zenburn.css">
+		<link rel="stylesheet" href="lib/css/monokai.css">
 
 		<!-- Printing and PDF exports -->
 		<script>
@@ -93,7 +94,10 @@
 						Press <strong>ESC</strong> to enter the slide overview.
 					</p>
 					<p>
-						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.
+						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.
+					</p>
+					<p>
+						(NOTE: Use ctrl + click in Linux.)
 					</p>
 				</section>
 
@@ -195,16 +199,16 @@
 					</section>
 					<section data-background="https://s3.amazonaws.com/hakim-static/reveal-js/image-placeholder.png">
 						<h2>Image Backgrounds</h2>
-						<pre><code class="hljs">&lt;section data-background="image.png"&gt;</code></pre>
+						<pre><code class="hljs html">&lt;section data-background="image.png"&gt;</code></pre>
 					</section>
 					<section data-background="https://s3.amazonaws.com/hakim-static/reveal-js/image-placeholder.png" data-background-repeat="repeat" data-background-size="100px">
 						<h2>Tiled Backgrounds</h2>
-						<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>
+						<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>
 					</section>
 					<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">
 						<div style="background-color: rgba(0, 0, 0, 0.9); color: #fff; padding: 20px;">
 							<h2>Video Backgrounds</h2>
-							<pre><code class="hljs" style="word-wrap: break-word;">&lt;section data-background-video="video.mp4,video.webm"&gt;</code></pre>
+							<pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background-video="video.mp4,video.webm"&gt;</code></pre>
 						</div>
 					</section>
 					<section data-background="http://i.giphy.com/90F8aUepslB84.gif">
@@ -217,7 +221,7 @@
 					<p>
 						Different background transitions are available via the backgroundTransition option. This one's called "zoom".
 					</p>
-					<pre><code class="hljs">Reveal.configure({ backgroundTransition: 'zoom' })</code></pre>
+					<pre><code class="hljs javascript">Reveal.configure({ backgroundTransition: 'zoom' })</code></pre>
 				</section>
 
 				<section data-transition="slide" data-background="#b5533c" data-background-transition="zoom">
@@ -225,25 +229,32 @@
 					<p>
 						You can override background transitions per-slide.
 					</p>
-					<pre><code class="hljs" style="word-wrap: break-word;">&lt;section data-background-transition="zoom"&gt;</code></pre>
+					<pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background-transition="zoom"&gt;</code></pre>
+				</section>
+
+				<section data-background-iframe="https://hakim.se" data-background-interactive>
+					<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;">
+						<h2>Iframe Backgrounds</h2>
+						<p>Since reveal.js runs on the web, you can easily embed other web content. Try interacting with the page in the background.</p>
+					</div>
 				</section>
 
 				<section>
 					<h2>Pretty Code</h2>
-					<pre><code class="hljs" data-trim contenteditable>
-function linkify( selector ) {
-  if( supports3DTransforms ) {
-
-    var nodes = document.querySelectorAll( selector );
-
-    for( var i = 0, len = nodes.length; i &lt; len; i++ ) {
-      var node = nodes[i];
-
-      if( !node.className ) {
-        node.className += ' roll';
-      }
-    }
-  }
+					<pre><code class="hljs" data-trim data-line-numbers="4,8-9">
+import React, { useState } from 'react';
+
+function Example() {
+  const [count, setCount] = useState(0);
+
+  return (
+    &lt;div&gt;
+      &lt;p&gt;You clicked {count} times&lt;/p&gt;
+      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;
+        Click me
+      &lt;/button&gt;
+    &lt;/div&gt;
+  );
 }
 					</code></pre>
 					<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() {
 
 		</div>
 
-		<script src="lib/js/head.min.js"></script>
 		<script src="js/reveal.js"></script>
 
 		<script>
@@ -393,17 +403,16 @@ Reveal.addEventListener( 'customevent', function() {
 			Reveal.initialize({
 				controls: true,
 				progress: true,
-				history: true,
 				center: true,
+				hash: true,
 
 				transition: 'slide', // none/fade/slide/convex/concave/zoom
 
 				// More info https://github.com/hakimel/reveal.js#dependencies
 				dependencies: [
-					{ src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
 					{ src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
 					{ src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
-					{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
+					{ src: 'plugin/highlight/highlight.js', async: true },
 					{ src: 'plugin/search/search.js', async: true },
 					{ src: 'plugin/zoom-js/zoom.js', async: true },
 					{ src: 'plugin/notes/notes.js', async: true }

+ 3 - 3
index.html

@@ -6,11 +6,12 @@
 
 		<title>reveal.js</title>
 
+		<link rel="stylesheet" href="css/reset.css">
 		<link rel="stylesheet" href="css/reveal.css">
 		<link rel="stylesheet" href="css/theme/black.css">
 
 		<!-- Theme used for syntax highlighting of code -->
-		<link rel="stylesheet" href="lib/css/zenburn.css">
+		<link rel="stylesheet" href="lib/css/monokai.css">
 
 		<!-- Printing and PDF exports -->
 		<script>
@@ -29,7 +30,6 @@
 			</div>
 		</div>
 
-		<script src="lib/js/head.min.js"></script>
 		<script src="js/reveal.js"></script>
 
 		<script>
@@ -41,7 +41,7 @@
 					{ src: 'plugin/markdown/marked.js' },
 					{ src: 'plugin/markdown/markdown.js' },
 					{ src: 'plugin/notes/notes.js', async: true },
-					{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }
+					{ src: 'plugin/highlight/highlight.js', async: true }
 				]
 			});
 		</script>

File diff suppressed because it is too large
+ 664 - 285
js/reveal.js


+ 71 - 0
lib/css/monokai.css

@@ -0,0 +1,71 @@
+/*
+Monokai style - ported by Luigi Maselli - http://grigio.org
+*/
+
+.hljs {
+  display: block;
+  overflow-x: auto;
+  padding: 0.5em;
+  background: #272822;
+  color: #ddd;
+}
+
+.hljs-tag,
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-literal,
+.hljs-strong,
+.hljs-name {
+  color: #f92672;
+}
+
+.hljs-code {
+  color: #66d9ef;
+}
+
+.hljs-class .hljs-title {
+  color: white;
+}
+
+.hljs-attribute,
+.hljs-symbol,
+.hljs-regexp,
+.hljs-link {
+  color: #bf79db;
+}
+
+.hljs-string,
+.hljs-bullet,
+.hljs-subst,
+.hljs-title,
+.hljs-section,
+.hljs-emphasis,
+.hljs-type,
+.hljs-built_in,
+.hljs-builtin-name,
+.hljs-selector-attr,
+.hljs-selector-pseudo,
+.hljs-addition,
+.hljs-variable,
+.hljs-template-tag,
+.hljs-template-variable {
+  color: #a6e22e;
+}
+
+.hljs-comment,
+.hljs-quote,
+.hljs-deletion,
+.hljs-meta {
+  color: #75715e;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-literal,
+.hljs-doctag,
+.hljs-title,
+.hljs-section,
+.hljs-type,
+.hljs-selector-id {
+  font-weight: bold;
+}

File diff suppressed because it is too large
+ 0 - 2
lib/js/classList.js


File diff suppressed because it is too large
+ 0 - 6
lib/js/head.min.js


File diff suppressed because it is too large
+ 2 - 0
lib/js/promise.js


+ 13 - 11
package.json

@@ -20,24 +20,26 @@
     "url": "git://github.com/hakimel/reveal.js.git"
   },
   "engines": {
-    "node": ">=4.0.0"
+    "node": ">=9.0.0"
   },
   "devDependencies": {
     "express": "^4.16.2",
-    "grunt": "^1.0.1",
+    "grunt": "^1.0.3",
+    "grunt-cli": "^1.3.2",
     "grunt-autoprefixer": "^3.0.4",
-    "grunt-cli": "^1.2.0",
-    "grunt-contrib-connect": "^1.0.2",
-    "grunt-contrib-cssmin": "^2.2.1",
-    "grunt-contrib-jshint": "^1.1.0",
-    "grunt-contrib-qunit": "^2.0.0",
+    "grunt-contrib-connect": "^2.0.0",
+    "grunt-contrib-cssmin": "^3.0.0",
+    "grunt-contrib-jshint": "^2.0.0",
+    "grunt-contrib-qunit": "^3.1.0",
     "grunt-contrib-uglify": "^3.3.0",
-    "grunt-contrib-watch": "^1.0.0",
-    "grunt-sass": "^2.0.0",
-    "grunt-retire": "^1.0.7",
+    "grunt-contrib-watch": "^1.1.0",
+    "grunt-sass": "^3.0.2",
     "grunt-zip": "~0.17.1",
+    "load-grunt-tasks": "^4.0.0",
+    "node-sass": "4.11.0",
     "mustache": "^2.3.0",
-    "socket.io": "^1.7.3"
+    "socket.io": "^2.2.0",
+    "typescript": "^3.3.3333"
   },
   "license": "MIT"
 }

File diff suppressed because it is too large
+ 111 - 21
plugin/highlight/highlight.js


+ 1 - 3
plugin/markdown/example.html

@@ -9,7 +9,7 @@
 		<link rel="stylesheet" href="../../css/reveal.css">
 		<link rel="stylesheet" href="../../css/theme/white.css" id="theme">
 
-        <link rel="stylesheet" href="../../lib/css/zenburn.css">
+        <link rel="stylesheet" href="../../lib/css/monokai.css">
 	</head>
 
 	<body>
@@ -109,7 +109,6 @@
             </div>
 		</div>
 
-		<script src="../../lib/js/head.min.js"></script>
 		<script src="../../js/reveal.js"></script>
 
 		<script>
@@ -122,7 +121,6 @@
 
 				// Optional libraries used to extend on reveal.js
 				dependencies: [
-					{ src: '../../lib/js/classList.js', condition: function() { return !document.body.classList; } },
 					{ src: 'marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
                     { src: 'markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
                     { src: '../highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },

+ 107 - 73
plugin/markdown/markdown.js

@@ -7,13 +7,11 @@
 	if (typeof define === 'function' && define.amd) {
 		root.marked = require( './marked' );
 		root.RevealMarkdown = factory( root.marked );
-		root.RevealMarkdown.initialize();
 	} else if( typeof exports === 'object' ) {
 		module.exports = factory( require( './marked' ) );
 	} else {
 		// Browser globals (root is window)
 		root.RevealMarkdown = factory( root.marked );
-		root.RevealMarkdown.initialize();
 	}
 }( this, function( marked ) {
 
@@ -199,81 +197,108 @@
 	 */
 	function processSlides() {
 
-		var sections = document.querySelectorAll( '[data-markdown]'),
-			section;
+		return new Promise( function( resolve ) {
 
-		for( var i = 0, len = sections.length; i < len; i++ ) {
+			var externalPromises = [];
 
-			section = sections[i];
+			[].slice.call( document.querySelectorAll( '[data-markdown]') ).forEach( function( section, i ) {
 
-			if( section.getAttribute( 'data-markdown' ).length ) {
+				if( section.getAttribute( 'data-markdown' ).length ) {
 
-				var xhr = new XMLHttpRequest(),
-					url = section.getAttribute( 'data-markdown' );
-
-				datacharset = section.getAttribute( 'data-charset' );
-
-				// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
-				if( datacharset != null && datacharset != '' ) {
-					xhr.overrideMimeType( 'text/html; charset=' + datacharset );
-				}
-
-				xhr.onreadystatechange = function() {
-					if( xhr.readyState === 4 ) {
-						// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
-						if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
+					externalPromises.push( loadExternalMarkdown( section ).then(
 
+						// Finished loading external file
+						function( xhr, url ) {
 							section.outerHTML = slidify( xhr.responseText, {
 								separator: section.getAttribute( 'data-separator' ),
 								verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
 								notesSeparator: section.getAttribute( 'data-separator-notes' ),
 								attributes: getForwardedAttributes( section )
 							});
+						},
 
-						}
-						else {
-
+						// Failed to load markdown
+						function( xhr, url ) {
 							section.outerHTML = '<section data-state="alert">' +
 								'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
 								'Check your browser\'s JavaScript console for more details.' +
 								'<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
 								'</section>';
-
 						}
-					}
-				};
 
-				xhr.open( 'GET', url, false );
+					) );
+
+				}
+				else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
+
+					section.outerHTML = slidify( getMarkdownFromSlide( section ), {
+						separator: section.getAttribute( 'data-separator' ),
+						verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
+						notesSeparator: section.getAttribute( 'data-separator-notes' ),
+						attributes: getForwardedAttributes( section )
+					});
 
-				try {
-					xhr.send();
 				}
-				catch ( e ) {
-					alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
+				else {
+					section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
 				}
 
+			});
+
+			Promise.all( externalPromises ).then( resolve );
+
+		} );
+
+	}
+
+	function loadExternalMarkdown( section ) {
+
+		return new Promise( function( resolve, reject ) {
+
+			var xhr = new XMLHttpRequest(),
+				url = section.getAttribute( 'data-markdown' );
+
+			datacharset = section.getAttribute( 'data-charset' );
+
+			// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
+			if( datacharset != null && datacharset != '' ) {
+				xhr.overrideMimeType( 'text/html; charset=' + datacharset );
 			}
-			else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
 
-				section.outerHTML = slidify( getMarkdownFromSlide( section ), {
-					separator: section.getAttribute( 'data-separator' ),
-					verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
-					notesSeparator: section.getAttribute( 'data-separator-notes' ),
-					attributes: getForwardedAttributes( section )
-				});
+			xhr.onreadystatechange = function( section, xhr ) {
+				if( xhr.readyState === 4 ) {
+					// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
+					if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
+
+						resolve( xhr, url );
 
+					}
+					else {
+
+						reject( xhr, url );
+
+					}
+				}
+			}.bind( this, section, xhr );
+
+			xhr.open( 'GET', url, true );
+
+			try {
+				xhr.send();
 			}
-			else {
-				section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
+			catch ( e ) {
+				alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
+				resolve( xhr, url );
 			}
-		}
+
+		} );
 
 	}
 
 	/**
 	 * Check if a node value has the attributes pattern.
 	 * If yes, extract it and add that value as one or several attributes
-	 * the the terget element.
+	 * to the target element.
 	 *
 	 * You need Cache Killer on Chrome to see the effect on any FOM transformation
 	 * directly on refresh (F5)
@@ -342,44 +367,47 @@
 	 */
 	function convertSlides() {
 
-		var sections = document.querySelectorAll( '[data-markdown]');
-
-		for( var i = 0, len = sections.length; i < len; i++ ) {
+		var sections = document.querySelectorAll( '[data-markdown]:not([data-markdown-parsed])');
 
-			var section = sections[i];
+		[].slice.call( sections ).forEach( function( section ) {
 
-			// Only parse the same slide once
-			if( !section.getAttribute( 'data-markdown-parsed' ) ) {
+			section.setAttribute( 'data-markdown-parsed', true )
 
-				section.setAttribute( 'data-markdown-parsed', true )
+			var notes = section.querySelector( 'aside.notes' );
+			var markdown = getMarkdownFromSlide( section );
 
-				var notes = section.querySelector( 'aside.notes' );
-				var markdown = getMarkdownFromSlide( section );
-
-				section.innerHTML = marked( markdown );
-				addAttributes( 	section, section, null, section.getAttribute( 'data-element-attributes' ) ||
-								section.parentNode.getAttribute( 'data-element-attributes' ) ||
-								DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
-								section.getAttribute( 'data-attributes' ) ||
-								section.parentNode.getAttribute( 'data-attributes' ) ||
-								DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
-
-				// If there were notes, we need to re-add them after
-				// having overwritten the section's HTML
-				if( notes ) {
-					section.appendChild( notes );
-				}
+			section.innerHTML = marked( markdown );
+			addAttributes( 	section, section, null, section.getAttribute( 'data-element-attributes' ) ||
+							section.parentNode.getAttribute( 'data-element-attributes' ) ||
+							DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
+							section.getAttribute( 'data-attributes' ) ||
+							section.parentNode.getAttribute( 'data-attributes' ) ||
+							DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
 
+			// If there were notes, we need to re-add them after
+			// having overwritten the section's HTML
+			if( notes ) {
+				section.appendChild( notes );
 			}
 
-		}
+		} );
+
+		return Promise.resolve();
 
 	}
 
 	// API
-	return {
+	var RevealMarkdown = {
+
+		/**
+		 * Starts processing and converting Markdown within the
+		 * current reveal.js deck.
+		 *
+		 * @param {function} callback function to invoke once
+		 * we've finished loading and parsing Markdown
+		 */
+		init: function( callback ) {
 
-		initialize: function() {
 			if( typeof marked === 'undefined' ) {
 				throw 'The reveal.js Markdown plugin requires marked to be loaded';
 			}
@@ -392,14 +420,14 @@
 				});
 			}
 
+			// marked can be configured via reveal.js config options
 			var options = Reveal.getConfig().markdown;
-
-			if ( options ) {
+			if( options ) {
 				marked.setOptions( options );
 			}
 
-			processSlides();
-			convertSlides();
+			return processSlides().then( convertSlides );
+
 		},
 
 		// TODO: Do these belong in the API?
@@ -409,4 +437,10 @@
 
 	};
 
+	// Register our plugin so that reveal.js will call our
+	// plugin 'init' method as part of the initialization
+	Reveal.registerPlugin( 'markdown', RevealMarkdown );
+
+	return RevealMarkdown;
+
 }));

File diff suppressed because it is too large
+ 3 - 3
plugin/markdown/marked.js


+ 51 - 26
plugin/math/math.js

@@ -7,33 +7,28 @@
 var RevealMath = window.RevealMath || (function(){
 
 	var options = Reveal.getConfig().math || {};
-	options.mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js';
-	options.config = options.config || 'TeX-AMS_HTML-full';
-	options.tex2jax = options.tex2jax || {
-				inlineMath: [['$','$'],['\\(','\\)']] ,
-				skipTags: ['script','noscript','style','textarea','pre'] };
-
-	loadScript( options.mathjax + '?config=' + options.config, function() {
-
-		MathJax.Hub.Config({
-			messageStyle: 'none',
-			tex2jax: options.tex2jax,
-			skipStartupTypeset: true
-		});
-
-		// Typeset followed by an immediate reveal.js layout since
-		// the typesetting process could affect slide height
-		MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] );
-		MathJax.Hub.Queue( Reveal.layout );
-
-		// Reprocess equations in slides when they turn visible
-		Reveal.addEventListener( 'slidechanged', function( event ) {
-
-			MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] );
-
-		} );
+	var mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js';
+	var config = options.config || 'TeX-AMS_HTML-full';
+	var url = mathjax + '?config=' + config;
+
+	var defaultOptions = {
+		messageStyle: 'none',
+		tex2jax: {
+			inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ],
+			skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ]
+		},
+		skipStartupTypeset: true
+	};
+
+	function defaults( options, defaultOptions ) {
+
+		for ( var i in defaultOptions ) {
+			if ( !options.hasOwnProperty( i ) ) {
+				options[i] = defaultOptions[i];
+			}
+		}
 
-	} );
+	}
 
 	function loadScript( url, callback ) {
 
@@ -64,4 +59,34 @@ var RevealMath = window.RevealMath || (function(){
 
 	}
 
+	return {
+		init: function() {
+
+			defaults( options, defaultOptions );
+			defaults( options.tex2jax, defaultOptions.tex2jax );
+			options.mathjax = options.config = null;
+
+			loadScript( url, function() {
+
+				MathJax.Hub.Config( options );
+
+				// Typeset followed by an immediate reveal.js layout since
+				// the typesetting process could affect slide height
+				MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] );
+				MathJax.Hub.Queue( Reveal.layout );
+
+				// Reprocess equations in slides when they turn visible
+				Reveal.addEventListener( 'slidechanged', function( event ) {
+
+					MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] );
+
+				} );
+
+			} );
+
+		}
+	}
+
 })();
+
+Reveal.registerPlugin( 'math', RevealMath );

+ 118 - 76
plugin/notes/notes.html

@@ -347,6 +347,8 @@
 					upcomingSlide,
 					layoutLabel,
 					layoutDropdown,
+					pendingCalls = {},
+					lastRevealApiCallId = 0,
 					connected = false;
 
 				var SPEAKER_LAYOUTS = {
@@ -382,6 +384,10 @@
 						else if( data.type === 'state' ) {
 							handleStateMessage( data );
 						}
+						else if( data.type === 'return' ) {
+							pendingCalls[data.callId](data.result);
+							delete pendingCalls[data.callId];
+						}
 					}
 					// Messages sent by the reveal.js inside of the current slide preview
 					else if( data && data.namespace === 'reveal' ) {
@@ -398,6 +404,23 @@
 
 				} );
 
+				/**
+				 * Asynchronously calls the Reveal.js API of the main frame.
+				 */
+				function callRevealApi( methodName, methodArguments, callback ) {
+
+					var callId = ++lastRevealApiCallId;
+					pendingCalls[callId] = callback;
+					window.opener.postMessage( JSON.stringify( {
+						namespace: 'reveal-notes',
+						type: 'call',
+						callId: callId,
+						methodName: methodName,
+						arguments: methodArguments
+					} ), '*' );
+
+				}
+
 				/**
 				 * Called when the main window is trying to establish a
 				 * connection.
@@ -512,28 +535,34 @@
 
 				}
 
-				function getTimings() {
+				function getTimings( callback ) {
 
-					var slides = Reveal.getSlides();
-					var defaultTiming = Reveal.getConfig().defaultTiming;
-					if (defaultTiming == null) {
-						return null;
-					}
-					var timings = [];
-					for ( var i in slides ) {
-						var slide = slides[i];
-						var timing = defaultTiming;
-						if( slide.hasAttribute( 'data-timing' )) {
-							var t = slide.getAttribute( 'data-timing' );
-							timing = parseInt(t);
-							if( isNaN(timing) ) {
-								console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
-								timing = defaultTiming;
+					callRevealApi( 'getSlidesAttributes', [], function ( slideAttributes ) {
+						callRevealApi( 'getConfig', [], function ( config ) {
+							var defaultTiming = config.defaultTiming;
+							if (defaultTiming == null) {
+								callback(null);
+								return;
+							}
+
+							var timings = [];
+							for ( var i in slideAttributes ) {
+								var slide = slideAttributes[ i ];
+								var timing = defaultTiming;
+								if( slide.hasOwnProperty( 'data-timing' )) {
+									var t = slide[ 'data-timing' ];
+									timing = parseInt(t);
+									if( isNaN(timing) ) {
+										console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
+										timing = defaultTiming;
+									}
+								}
+								timings.push(timing);
 							}
-						}
-						timings.push(timing);
-					}
-					return timings;
+
+							callback( timings );
+						} );
+					} );
 
 				}
 
@@ -541,15 +570,15 @@
 				 * Return the number of seconds allocated for presenting
 				 * all slides up to and including this one.
 				 */
-				function getTimeAllocated(timings) {
+				function getTimeAllocated( timings, callback ) {
 
-					var slides = Reveal.getSlides();
-					var allocated = 0;
-					var currentSlide = Reveal.getSlidePastCount();
-					for (var i in slides.slice(0, currentSlide + 1)) {
-						allocated += timings[i];
-					}
-					return allocated;
+					callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
+						var allocated = 0;
+						for (var i in timings.slice(0, currentSlide + 1)) {
+							allocated += timings[i];
+						}
+						callback( allocated );
+					} );
 
 				}
 
@@ -571,12 +600,51 @@
 					pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
 					pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
 
-					var timings = getTimings();
-					if (timings !== null) {
-						pacingTitleEl.style.removeProperty('display');
-						pacingEl.style.removeProperty('display');
+					var timings = null;
+					getTimings( function ( _timings ) {
+
+						timings = _timings;
+						if (_timings !== null) {
+							pacingTitleEl.style.removeProperty('display');
+							pacingEl.style.removeProperty('display');
+						}
+
+						// Update once directly
+						_updateTimer();
+
+						// Then update every second
+						setInterval( _updateTimer, 1000 );
+
+					} );
+
+
+					function _resetTimer() {
+
+						if (timings == null) {
+							start = new Date();
+							_updateTimer();
+						}
+						else {
+							// Reset timer to beginning of current slide
+							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
+								var slideEndTiming = slideEndTimingSeconds * 1000;
+								callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
+									var currentSlideTiming = timings[currentSlide] * 1000;
+									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
+									var now = new Date();
+									start = new Date(now.getTime() - previousSlidesTiming);
+									_updateTimer();
+								} );
+							} );
+						}
+
 					}
 
+					timeEl.addEventListener( 'click', function() {
+						_resetTimer();
+						return false;
+					} );
+
 					function _displayTime( hrEl, minEl, secEl, time) {
 
 						var sign = Math.sign(time) == -1 ? "-" : "";
@@ -618,52 +686,26 @@
 
 					function _updatePacing(diff) {
 
-						var slideEndTiming = getTimeAllocated(timings) * 1000;
-						var currentSlide = Reveal.getSlidePastCount();
-						var currentSlideTiming = timings[currentSlide] * 1000;
-						var timeLeftCurrentSlide = slideEndTiming - diff;
-						if (timeLeftCurrentSlide < 0) {
-							pacingEl.className = 'pacing behind';
-						}
-						else if (timeLeftCurrentSlide < currentSlideTiming) {
-							pacingEl.className = 'pacing on-track';
-						}
-						else {
-							pacingEl.className = 'pacing ahead';
-						}
-						_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
-
-					}
-
-					// Update once directly
-					_updateTimer();
-
-					// Then update every second
-					setInterval( _updateTimer, 1000 );
-
-					function _resetTimer() {
-
-						if (timings == null) {
-							start = new Date();
-						}
-						else {
-							// Reset timer to beginning of current slide
-							var slideEndTiming = getTimeAllocated(timings) * 1000;
-							var currentSlide = Reveal.getSlidePastCount();
-							var currentSlideTiming = timings[currentSlide] * 1000;
-							var previousSlidesTiming = slideEndTiming - currentSlideTiming;
-							var now = new Date();
-							start = new Date(now.getTime() - previousSlidesTiming);
-						}
-						_updateTimer();
-
+						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
+							var slideEndTiming = slideEndTimingSeconds * 1000;
+
+							callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
+								var currentSlideTiming = timings[currentSlide] * 1000;
+								var timeLeftCurrentSlide = slideEndTiming - diff;
+								if (timeLeftCurrentSlide < 0) {
+									pacingEl.className = 'pacing behind';
+								}
+								else if (timeLeftCurrentSlide < currentSlideTiming) {
+									pacingEl.className = 'pacing on-track';
+								}
+								else {
+									pacingEl.className = 'pacing ahead';
+								}
+								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
+							} );
+						} );
 					}
 
-					timeEl.addEventListener( 'click', function() {
-						_resetTimer();
-						return false;
-					} );
-
 				}
 
 				/**

+ 46 - 15
plugin/notes/notes.js

@@ -11,24 +11,28 @@
  */
 var RevealNotes = (function() {
 
+    var notesPopup = null;
+
 	function openNotes( notesFilePath ) {
 
+        if (notesPopup && !notesPopup.closed) {
+            notesPopup.focus();
+            return;
+        }
+
 		if( !notesFilePath ) {
 			var jsFileLocation = document.querySelector('script[src$="notes.js"]').src;  // this js file path
 			jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, '');   // the js folder path
 			notesFilePath = jsFileLocation + 'notes.html';
 		}
 
-		var notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' );
+		notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' );
 
 		if( !notesPopup ) {
 			alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' );
 			return;
 		}
 
-		// Allow popup window access to Reveal API
-		notesPopup.Reveal = window.Reveal;
-
 		/**
 		 * Connect to the notes window through a postmessage handshake.
 		 * Using postmessage enables us to work in situations where the
@@ -52,9 +56,28 @@ var RevealNotes = (function() {
 					clearInterval( connectInterval );
 					onConnected();
 				}
+				if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
+					callRevealApi( data.methodName, data.arguments, data.callId );
+				}
 			} );
 		}
 
+		/**
+		 * Calls the specified Reveal.js method with the provided argument
+		 * and then pushes the result to the notes frame.
+		 */
+		function callRevealApi( methodName, methodArguments, callId ) {
+
+			var result = Reveal[methodName].apply( Reveal, methodArguments );
+			notesPopup.postMessage( JSON.stringify( {
+				namespace: 'reveal-notes',
+				type: 'return',
+				result: result,
+				callId: callId
+			} ), '*' );
+
+		}
+
 		/**
 		 * Posts the current slide data to the notes window
 		 */
@@ -128,20 +151,28 @@ var RevealNotes = (function() {
 
 	}
 
-	if( !/receiver/i.test( window.location.search ) ) {
+	return {
+		init: function() {
 
-		// If the there's a 'notes' query set, open directly
-		if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
-			openNotes();
-		}
+			if( !/receiver/i.test( window.location.search ) ) {
 
-		// Open the notes when the 's' key is hit
-		Reveal.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() {
-			openNotes();
-		} );
+				// If the there's a 'notes' query set, open directly
+				if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
+					openNotes();
+				}
 
-	}
+				// Open the notes when the 's' key is hit
+				Reveal.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() {
+					openNotes();
+				} );
 
-	return { open: openNotes };
+			}
+
+		},
+
+		open: openNotes
+	};
 
 })();
+
+Reveal.registerPlugin( 'notes', RevealNotes );

+ 1 - 1
plugin/search/search.js

@@ -200,7 +200,7 @@ function Hilitor(id, tag)
 			toggleSearch();
 		}
 	}, false );
-	if( window.Reveal ) Reveal.registerKeyboardShortcut( 'Ctrl-Shift-F', 'Search' );
+	if( window.Reveal ) Reveal.registerKeyboardShortcut( 'CTRL + Shift + F', 'Search' );
 	closeSearch();
 	return { open: openSearch };
 })();

+ 22 - 17
plugin/zoom-js/zoom.js

@@ -1,29 +1,34 @@
 // Custom reveal.js integration
-(function(){
-	var revealElement = document.querySelector( '.reveal' );
-	if( revealElement ) {
+var RevealZoom = (function(){
 
-		revealElement.addEventListener( 'mousedown', function( event ) {
-			var defaultModifier = /Linux/.test( window.navigator.platform ) ? 'ctrl' : 'alt';
+	return {
+		init: function() {
 
-			var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : defaultModifier ) + 'Key';
-			var zoomLevel = ( Reveal.getConfig().zoomLevel ? Reveal.getConfig().zoomLevel : 2 );
+			Reveal.getRevealElement().addEventListener( 'mousedown', function( event ) {
+				var defaultModifier = /Linux/.test( window.navigator.platform ) ? 'ctrl' : 'alt';
 
-			if( event[ modifier ] && !Reveal.isOverview() ) {
-				event.preventDefault();
+				var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : defaultModifier ) + 'Key';
+				var zoomLevel = ( Reveal.getConfig().zoomLevel ? Reveal.getConfig().zoomLevel : 2 );
 
-				zoom.to({
-					x: event.clientX,
-					y: event.clientY,
-					scale: zoomLevel,
-					pan: false
-				});
-			}
-		} );
+				if( event[ modifier ] && !Reveal.isOverview() ) {
+					event.preventDefault();
+
+					zoom.to({
+						x: event.clientX,
+						y: event.clientY,
+						scale: zoomLevel,
+						pan: false
+					});
+				}
+			} );
 
+		}
 	}
+
 })();
 
+Reveal.registerPlugin( 'zoom', RevealZoom );
+
 /*!
  * zoom.js 0.3 (modified for use with reveal.js)
  * http://lab.hakim.se/zoom-js

+ 1 - 0
test/assets/external-script-a.js

@@ -0,0 +1 @@
+window.externalScriptSequence += 'A';

+ 1 - 0
test/assets/external-script-b.js

@@ -0,0 +1 @@
+window.externalScriptSequence += 'B';

+ 1 - 0
test/assets/external-script-c.js

@@ -0,0 +1 @@
+window.externalScriptSequence += 'C';

+ 1 - 0
test/assets/external-script-d.js

@@ -0,0 +1 @@
+window.externalScriptSequence += 'D';

+ 2 - 0
test/examples/assets/beeping.txt

@@ -0,0 +1,2 @@
+Source: https://freesound.org/people/fennelliott/sounds/379419/
+License: CC0 (public domain)

BIN
test/examples/assets/beeping.wav


+ 5 - 1
test/examples/embedded-media.html

@@ -30,11 +30,15 @@
 					<h2>Empty Slide</h2>
 				</section>
 
+				<section>
+					<h2>Auto-playing audio</h2>
+					<audio src="assets/beeping.wav" data-autoplay></audio>
+				</section>
+
 			</div>
 
 		</div>
 
-		<script src="../../lib/js/head.min.js"></script>
 		<script src="../../js/reveal.js"></script>
 
 		<script>

+ 23 - 3
test/examples/math.html

@@ -82,6 +82,14 @@
 					\]
 				</section>
 
+				<section>
+					<h3>TeX Macros</h3>
+
+					Here is a common vector space:
+					\[L^2(\R) = \set{u : \R \to \R}{\int_\R |u|^2 &lt; +\infty}\]
+					used in functional analysis.
+				</section>
+
 				<section>
 					<section>
 						<h3>The Lorenz Equations</h3>
@@ -153,13 +161,20 @@
 							\]
 						</div>
 					</section>
+
+					<section>
+						<h3>TeX Macros</h3>
+
+						Here is a common vector space:
+						\[L^2(\R) = \set{u : \R \to \R}{\int_\R |u|^2 &lt; +\infty}\]
+						used in functional analysis.
+					</section>
 				</section>
 
 			</div>
 
 		</div>
 
-		<script src="../../lib/js/head.min.js"></script>
 		<script src="../../js/reveal.js"></script>
 
 		<script>
@@ -170,11 +185,16 @@
 
 				math: {
 					// mathjax: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js',
-					config: 'TeX-AMS_HTML-full'
+					config: 'TeX-AMS_HTML-full',
+					TeX: {
+						Macros: {
+							R: '\\mathbb{R}',
+							set: [ '\\left\\{#1 \\; ; \\; #2\\right\\}', 2 ]
+						}
+					}
 				},
 
 				dependencies: [
-					{ src: '../../lib/js/classList.js' },
 					{ src: '../../plugin/math/math.js', async: true }
 				]
 			});

+ 0 - 1
test/examples/slide-backgrounds.html

@@ -122,7 +122,6 @@
 
 		</div>
 
-		<script src="../../lib/js/head.min.js"></script>
 		<script src="../../js/reveal.js"></script>
 
 		<script>

+ 0 - 1
test/examples/slide-transitions.html

@@ -81,7 +81,6 @@
 
 		</div>
 
-		<script src="../../lib/js/head.min.js"></script>
 		<script src="../../js/reveal.js"></script>
 
 		<script>

+ 78 - 0
test/test-dependencies-async.html

@@ -0,0 +1,78 @@
+<!doctype html>
+<html lang="en">
+
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Test Async Dependencies</title>
+
+		<link rel="stylesheet" href="../css/reveal.css">
+		<link rel="stylesheet" href="qunit-2.5.0.css">
+	</head>
+
+	<body style="overflow: auto;">
+
+		<div id="qunit"></div>
+		<div id="qunit-fixture"></div>
+
+		<div class="reveal" style="display: none;">
+
+			<div class="slides">
+
+				<section>Slide content</section>
+
+			</div>
+
+		</div>
+
+		<script src="../js/reveal.js"></script>
+		<script src="qunit-2.5.0.js"></script>
+
+		<script>
+			var	externalScriptSequence = '';
+			var scriptCount = 0;
+
+			QUnit.config.autostart = false;
+			QUnit.module( 'Async Dependencies' );
+
+			QUnit.test( 'Async scripts are loaded', function( assert ) {
+				assert.expect( 5 );
+				var done = assert.async( 5 );
+
+				function callback( event ) {
+					if( externalScriptSequence.length === 1 ) {
+						assert.ok( externalScriptSequence === 'A', 'first callback was sync script' );
+						done();
+					}
+					else {
+						assert.ok( true, 'async script loaded' );
+						done();
+					}
+
+					if( externalScriptSequence.length === 4 ) {
+						assert.ok( 	externalScriptSequence.indexOf( 'A' ) !== -1 &&
+									externalScriptSequence.indexOf( 'B' ) !== -1 &&
+									externalScriptSequence.indexOf( 'C' ) !== -1 &&
+									externalScriptSequence.indexOf( 'D' ) !== -1, 'four unique scripts were loaded' );
+						done();
+					}
+
+					scriptCount ++;
+				}
+
+				Reveal.initialize({
+					dependencies: [
+						{ src: 'assets/external-script-a.js', async: false, callback: callback },
+						{ src: 'assets/external-script-b.js', async: true, callback: callback },
+						{ src: 'assets/external-script-c.js', async: true, callback: callback },
+						{ src: 'assets/external-script-d.js', async: true, callback: callback }
+					]
+				});
+			});
+
+			QUnit.start();
+
+		</script>
+
+	</body>
+</html>

+ 54 - 0
test/test-dependencies.html

@@ -0,0 +1,54 @@
+<!doctype html>
+<html lang="en">
+
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Test Dependencies</title>
+
+		<link rel="stylesheet" href="../css/reveal.css">
+		<link rel="stylesheet" href="qunit-2.5.0.css">
+	</head>
+
+	<body style="overflow: auto;">
+
+		<div id="qunit"></div>
+		<div id="qunit-fixture"></div>
+
+		<div class="reveal" style="display: none;">
+
+			<div class="slides">
+
+				<section>Slide content</section>
+
+			</div>
+
+		</div>
+
+		<script src="../js/reveal.js"></script>
+		<script src="qunit-2.5.0.js"></script>
+
+		<script>
+			window.externalScriptSequence = '';
+
+			Reveal.addEventListener( 'ready', function() {
+
+				QUnit.module( 'Dependencies' );
+
+				QUnit.test( 'Load synchronous scripts', function( assert ) {
+					assert.strictEqual( window.externalScriptSequence, 'ABC', 'Loaded and executed in order' );
+				});
+
+			} );
+
+			Reveal.initialize({
+				dependencies: [
+					{ src: 'assets/external-script-a.js' },
+					{ src: 'assets/external-script-b.js' },
+					{ src: 'assets/external-script-c.js' }
+				]
+			});
+		</script>
+
+	</body>
+</html>

+ 74 - 0
test/test-grid-navigation.html

@@ -0,0 +1,74 @@
+<!doctype html>
+<html lang="en">
+
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Test Grid</title>
+
+		<link rel="stylesheet" href="../css/reveal.css">
+		<link rel="stylesheet" href="qunit-2.5.0.css">
+	</head>
+
+	<body style="overflow: auto;">
+
+		<div id="qunit"></div>
+		<div id="qunit-fixture"></div>
+
+		<div class="reveal" style="display: none;">
+
+			<div class="slides">
+
+				<section>0</section>
+				<section>
+					<section>1.1</section>
+					<section>1.2</section>
+					<section>1.3</section>
+					<section>1.4</section>
+				</section>
+				<section>
+					<section>2.1</section>
+					<section>2.2</section>
+					<section>2.3</section>
+					<section>2.4</section>
+				</section>
+
+			</div>
+
+		</div>
+
+		<script src="../js/reveal.js"></script>
+		<script src="qunit-2.5.0.js"></script>
+
+		<script>
+			Reveal.addEventListener( 'ready', function() {
+
+				QUnit.module( 'Grid Navigation' );
+
+				QUnit.test( 'Disabled', function( assert ) {
+					Reveal.right();
+					Reveal.down();
+					Reveal.down();
+					assert.deepEqual( Reveal.getIndices(), { h: 1, v: 2, f: undefined }, 'Correct starting point' );
+					Reveal.right();
+					assert.deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: undefined }, 'Moves to top when going to adjacent stack' );
+				});
+
+				QUnit.test( 'Enabled', function( assert ) {
+					Reveal.configure({ navigationMode: 'grid' });
+					Reveal.slide( 0, 0 );
+					Reveal.right();
+					Reveal.down();
+					Reveal.down();
+					assert.deepEqual( Reveal.getIndices(), { h: 1, v: 2, f: undefined }, 'Correct starting point' );
+					Reveal.right();
+					assert.deepEqual( Reveal.getIndices(), { h: 2, v: 2, f: undefined }, 'Remains at same vertical index when going to adjacent stack' );
+				});
+
+			} );
+
+			Reveal.initialize();
+		</script>
+
+	</body>
+</html>

+ 1 - 3
test/test-markdown-element-attributes.html

@@ -66,8 +66,7 @@
 
 						Test
 
-						![Example Picture](examples/assets/image2.png)
-						<!-- {_class="reveal stretch"} -->
+						![Example Picture](examples/assets/image2.png) <!-- {_class="reveal stretch"} -->
 
 					</script>
 				</section>
@@ -122,7 +121,6 @@
 
 		</div>
 
-		<script src="../lib/js/head.min.js"></script>
 		<script src="../js/reveal.js"></script>
 		<script src="../plugin/markdown/marked.js"></script>
 		<script src="../plugin/markdown/markdown.js"></script>

+ 5 - 4
test/test-markdown-external.html

@@ -13,24 +13,25 @@
 	<body style="overflow: auto;">
 
 		<div id="qunit"></div>
-  		<div id="qunit-fixture"></div>
+		<div id="qunit-fixture"></div>
 
 		<div class="reveal" style="display: none;">
 
 			<div class="slides">
-				<section data-markdown="simple.md" data-separator="^\r?\n\r?\n\r?\n" data-separator-vertical="^\r?\n\r?\n"></section>
+				<!-- <section data-markdown="simple.md" data-separator="^\r?\n\r?\n\r?\n" data-separator-vertical="^\r?\n\r?\n"></section> -->
 			</div>
 
 		</div>
 
-		<script src="../lib/js/head.min.js"></script>
 		<script src="../js/reveal.js"></script>
 		<script src="../plugin/highlight/highlight.js"></script>
 		<script src="../plugin/markdown/marked.js"></script>
 		<script src="../plugin/markdown/markdown.js"></script>
 		<script src="qunit-2.5.0.js"></script>
 
-		<script src="test-markdown-external.js"></script>
+		<!-- <script src="test-markdown-external.js"></script> -->
+
+		<!-- Test disabled 28/2/2019 by Hakim – Markdown plugin needs to be updated to load extenal files asycnhronously -->
 
 	</body>
 </html>

+ 0 - 1
test/test-markdown-options.html

@@ -31,7 +31,6 @@
 
 		</div>
 
-		<script src="../lib/js/head.min.js"></script>
 		<script src="../js/reveal.js"></script>
 		<script src="qunit-2.5.0.js"></script>
 

+ 0 - 1
test/test-markdown-slide-attributes.html

@@ -116,7 +116,6 @@
 
 		</div>
 
-		<script src="../lib/js/head.min.js"></script>
 		<script src="../js/reveal.js"></script>
 		<script src="../plugin/markdown/marked.js"></script>
 		<script src="../plugin/markdown/markdown.js"></script>

+ 0 - 1
test/test-markdown.html

@@ -40,7 +40,6 @@
 
 		</div>
 
-		<script src="../lib/js/head.min.js"></script>
 		<script src="../js/reveal.js"></script>
 		<script src="../plugin/markdown/marked.js"></script>
 		<script src="../plugin/markdown/markdown.js"></script>

+ 1 - 2
test/test-pdf.html

@@ -22,7 +22,7 @@
 
 				<section>
 					<h1>1</h1>
-					<img data-src="fake-url.png">
+					<img data-src="">
 				</section>
 
 				<section>
@@ -73,7 +73,6 @@
 
 		</div>
 
-		<script src="../lib/js/head.min.js"></script>
 		<script src="../js/reveal.js"></script>
 		<script src="qunit-2.5.0.js"></script>
 

+ 92 - 0
test/test-plugins.html

@@ -0,0 +1,92 @@
+<!doctype html>
+<html lang="en">
+
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Test Plugins</title>
+
+		<link rel="stylesheet" href="../css/reveal.css">
+		<link rel="stylesheet" href="qunit-2.5.0.css">
+	</head>
+
+	<body style="overflow: auto;">
+
+		<div id="qunit"></div>
+		<div id="qunit-fixture"></div>
+
+		<div class="reveal" style="display: none;">
+
+			<div class="slides">
+
+				<section>Slide content</section>
+
+			</div>
+
+		</div>
+
+		<script src="../js/reveal.js"></script>
+		<script src="qunit-2.5.0.js"></script>
+
+		<script>
+
+			QUnit.module( 'Plugins' );
+
+			var initCounter = { PluginB: 0, PluginC: 0, PluginD: 0 };
+
+			// Plugin with no init method
+			var PluginA = {};
+
+			// Plugin with init method
+			var PluginB = { init: function() {
+				initCounter['PluginB'] += 1;
+			} };
+
+			// Async plugin with init method
+			var PluginC = { init: function() {
+				return new Promise(function( resolve ) {
+					setTimeout( () => {
+						initCounter['PluginC'] += 1;
+						resolve();
+					}, 1000 );
+				});
+			} };
+
+			// Plugin initialized after reveal.js is ready
+			var PluginD = { init: function() {
+				initCounter['PluginD'] += 1;
+			} };
+
+			Reveal.registerPlugin( 'PluginA', PluginA );
+			Reveal.registerPlugin( 'PluginB', PluginB );
+			Reveal.registerPlugin( 'PluginC', PluginC );
+
+			Reveal.initialize();
+
+			QUnit.test( 'Can initialize synchronously', function( assert ) {
+				assert.strictEqual( initCounter['PluginB'], 1 );
+
+				Reveal.registerPlugin( 'PluginB', PluginB );
+
+				assert.strictEqual( initCounter['PluginB'], 1, 'prevents duplicate registration' );
+			});
+
+			QUnit.test( 'Can initialie asynchronously', function( assert ) {
+				assert.expect( 3 );
+				var done = assert.async( 2 );
+
+				assert.strictEqual( initCounter['PluginC'], 0, 'async plugin not immediately initialized' );
+
+				Reveal.addEventListener( 'ready', function() {
+					assert.strictEqual( initCounter['PluginC'], 1, 'finsihed initializing when reveal.js dispatches "ready"' );
+					done();
+
+					Reveal.registerPlugin( 'PluginD', PluginD );
+					assert.strictEqual( initCounter['PluginD'], 1, 'plugin registered after reveal.js is ready still initiailizes' );
+					done();
+				});
+			} );
+		</script>
+
+	</body>
+</html>

+ 139 - 0
test/test-state.html

@@ -0,0 +1,139 @@
+<!doctype html>
+<html lang="en">
+
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Test State</title>
+
+		<link rel="stylesheet" href="../css/reveal.css">
+		<link rel="stylesheet" href="qunit-2.5.0.css">
+	</head>
+
+	<body style="overflow: auto;">
+
+		<div id="qunit"></div>
+		<div id="qunit-fixture"></div>
+
+		<div class="reveal" style="display: none;">
+
+			<div class="slides">
+
+				<section>No state</section>
+				<section id="slide2" data-state="state1">State 1</section>
+				<section data-state="state1">State 1</section>
+				<section data-state="state2">State 2</section>
+				<section>
+					<section>No state</section>
+					<section data-state="state1">State 1</section>
+					<section data-state="state3">State 3</section>
+					<section>No state</section>
+				</section>
+				<section>No state</section>
+
+			</div>
+
+		</div>
+
+		<script src="../js/reveal.js"></script>
+		<script src="qunit-2.5.0.js"></script>
+
+		<script>
+
+
+			Reveal.addEventListener( 'ready', function() {
+
+				QUnit.module( 'State' );
+
+				QUnit.test( 'Fire events when changing slide', function( assert ) {
+					assert.expect( 2 );
+					var state1 = assert.async();
+					var state2 = assert.async();
+
+					var _onState1 = function( event ) {
+						assert.ok( true, 'state1 fired' );
+						state1();
+					}
+
+					var _onState2 = function( event ) {
+						assert.ok( true, 'state2 fired' );
+						state2();
+					}
+
+					Reveal.addEventListener( 'state1', _onState1 );
+					Reveal.addEventListener( 'state2', _onState2 );
+
+					Reveal.slide( 1 );
+					Reveal.slide( 3 );
+
+					Reveal.removeEventListener( 'state1', _onState1 );
+					Reveal.removeEventListener( 'state2', _onState2 );
+				});
+
+				QUnit.test( 'Fire state events for vertical slides', function( assert ) {
+					assert.expect( 2 );
+					var done = assert.async( 2 );
+
+					var _onState1 = function( event ) {
+						assert.ok( true, 'state1 fired' );
+						done();
+					}
+
+					var _onState3 = function( event ) {
+						assert.ok( true, 'state3 fired' );
+						done();
+					}
+
+					Reveal.addEventListener( 'state1', _onState1 );
+					Reveal.addEventListener( 'state3', _onState3 );
+
+					Reveal.slide( 0 );
+					Reveal.slide( 4, 1 );
+					Reveal.slide( 4, 2 );
+
+					Reveal.removeEventListener( 'state1', _onState1 );
+					Reveal.removeEventListener( 'state3', _onState3 );
+				});
+
+				QUnit.test( 'No events if state remains unchanged', function( assert ) {
+					var stateChanges = 0;
+
+					var _onEvent = function( event ) {
+						stateChanges += 1;
+					}
+
+					Reveal.addEventListener( 'state1', _onEvent );
+
+					Reveal.slide( 0 );      // no state
+					Reveal.slide( 1 );      // state1
+					Reveal.slide( 2 );      // state1
+					Reveal.prev();          // state1
+					Reveal.next();          // state1
+					Reveal.slide( 4, 1 );   // state1
+					Reveal.slide( 0 );      // no state
+
+					Reveal.removeEventListener( 'state1', _onEvent );
+
+					assert.strictEqual( stateChanges, 1, 'no event was fired when going to slide with same state' );
+				});
+
+				QUnit.test( 'Event order', function( assert ) {
+					var _onEvent = function( event ) {
+						assert.ok( Reveal.getCurrentSlide() == document.querySelector( '#slide2' ), 'correct current slide immediately after state event' );
+					}
+
+					Reveal.addEventListener( 'state1', _onEvent );
+
+					Reveal.slide( 0 );
+					Reveal.slide( 1 );
+
+					Reveal.removeEventListener( 'state1', _onEvent );
+				});
+
+			} );
+
+			Reveal.initialize();
+		</script>
+
+	</body>
+</html>

+ 3 - 4
test/test.html

@@ -21,9 +21,9 @@
 
 				<section data-background-image="examples/assets/image1.png">
 					<h1>1</h1>
-					<img data-src="fake-url.png">
-					<video data-src="fake-url.mp4"></video>
-					<audio data-src="fake-url.mp3"></audio>
+					<img data-src="">
+					<video data-src=""></video>
+					<audio data-src=""></audio>
 					<aside class="notes">speaker notes 1</aside>
 				</section>
 
@@ -76,7 +76,6 @@
 
 		</div>
 
-		<script src="../lib/js/head.min.js"></script>
 		<script src="../js/reveal.js"></script>
 		<script src="qunit-2.5.0.js"></script>
 

+ 5 - 0
test/test.js

@@ -262,6 +262,7 @@ Reveal.addEventListener( 'ready', function() {
 
 	QUnit.test( 'Current fragment', function( assert ) {
 		var fragmentSlide = document.querySelector( '#fragment-slides>section:nth-child(1)' );
+		var lastFragmentIndex = [].slice.call( fragmentSlide.querySelectorAll( '.fragment' ) ).pop().getAttribute( 'data-fragment-index' );
 
 		Reveal.slide( 2, 0 );
 		assert.strictEqual( fragmentSlide.querySelectorAll( '.fragment.current-fragment' ).length, 0, 'no current fragment at index -1' );
@@ -274,6 +275,10 @@ Reveal.addEventListener( 'ready', function() {
 
 		Reveal.slide( 3, 0, 0 );
 		assert.strictEqual( fragmentSlide.querySelectorAll( '.fragment.current-fragment' ).length, 0, 'no current fragment when navigating to next slide' );
+
+		Reveal.slide( 2, 1, -1 );
+		Reveal.prev();
+		assert.strictEqual( fragmentSlide.querySelector( '.fragment.current-fragment' ).getAttribute( 'data-fragment-index' ), lastFragmentIndex, 'last fragment is current fragment when returning from future slide' );
 	});
 
 	QUnit.test( 'Stepping through fragments', function( assert ) {