reveal-ck 3.6.0 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/files/reveal.js/CONTRIBUTING.md +4 -0
  3. data/files/reveal.js/Gruntfile.js +44 -26
  4. data/files/reveal.js/LICENSE +1 -1
  5. data/files/reveal.js/README.md +375 -161
  6. data/files/reveal.js/bower.json +27 -0
  7. data/files/reveal.js/css/print/paper.css +4 -3
  8. data/files/reveal.js/css/print/pdf.css +53 -38
  9. data/files/reveal.js/css/reveal.css +452 -206
  10. data/files/reveal.js/css/reveal.scss +328 -175
  11. data/files/reveal.js/css/theme/README.md +2 -6
  12. data/files/reveal.js/css/theme/beige.css +81 -50
  13. data/files/reveal.js/css/theme/black.css +70 -39
  14. data/files/reveal.js/css/theme/blood.css +81 -57
  15. data/files/reveal.js/css/theme/league.css +75 -44
  16. data/files/reveal.js/css/theme/moon.css +75 -44
  17. data/files/reveal.js/css/theme/night.css +70 -39
  18. data/files/reveal.js/css/theme/serif.css +72 -41
  19. data/files/reveal.js/css/theme/simple.css +72 -38
  20. data/files/reveal.js/css/theme/sky.css +75 -44
  21. data/files/reveal.js/css/theme/solarized.css +75 -44
  22. data/files/reveal.js/css/theme/source/black.scss +2 -2
  23. data/files/reveal.js/css/theme/source/blood.scss +3 -16
  24. data/files/reveal.js/css/theme/source/night.scss +0 -1
  25. data/files/reveal.js/css/theme/source/simple.scss +5 -0
  26. data/files/reveal.js/css/theme/source/white.scss +2 -2
  27. data/files/reveal.js/css/theme/template/settings.scss +1 -1
  28. data/files/reveal.js/css/theme/template/theme.scss +36 -23
  29. data/files/reveal.js/css/theme/white.css +75 -44
  30. data/files/reveal.js/demo.html +410 -0
  31. data/files/reveal.js/index.html +14 -373
  32. data/files/reveal.js/js/reveal.js +1186 -350
  33. data/files/reveal.js/lib/css/zenburn.css +41 -78
  34. data/files/reveal.js/lib/js/head.min.js +9 -8
  35. data/files/reveal.js/package.json +22 -26
  36. data/files/reveal.js/plugin/highlight/highlight.js +52 -4
  37. data/files/reveal.js/plugin/markdown/example.html +1 -1
  38. data/files/reveal.js/plugin/markdown/markdown.js +40 -21
  39. data/files/reveal.js/plugin/markdown/marked.js +2 -33
  40. data/files/reveal.js/plugin/math/math.js +5 -2
  41. data/files/reveal.js/plugin/multiplex/client.js +1 -1
  42. data/files/reveal.js/plugin/multiplex/index.js +24 -16
  43. data/files/reveal.js/plugin/multiplex/master.js +22 -42
  44. data/files/reveal.js/plugin/multiplex/package.json +19 -0
  45. data/files/reveal.js/plugin/notes-server/client.js +6 -1
  46. data/files/reveal.js/plugin/notes-server/index.js +17 -14
  47. data/files/reveal.js/plugin/notes-server/notes.html +215 -26
  48. data/files/reveal.js/plugin/notes/notes.html +372 -32
  49. data/files/reveal.js/plugin/notes/notes.js +40 -7
  50. data/files/reveal.js/plugin/print-pdf/print-pdf.js +47 -26
  51. data/files/reveal.js/plugin/zoom-js/zoom.js +12 -2
  52. data/files/reveal.js/test/examples/math.html +1 -1
  53. data/files/reveal.js/test/examples/slide-backgrounds.html +1 -1
  54. data/files/reveal.js/test/examples/slide-transitions.html +101 -0
  55. data/files/reveal.js/test/simple.md +12 -0
  56. data/files/reveal.js/test/test-markdown-element-attributes.html +3 -3
  57. data/files/reveal.js/test/test-markdown-element-attributes.js +1 -1
  58. data/files/reveal.js/test/test-markdown-external.html +36 -0
  59. data/files/reveal.js/test/test-markdown-external.js +24 -0
  60. data/files/reveal.js/test/test-markdown-options.html +41 -0
  61. data/files/reveal.js/test/test-markdown-options.js +26 -0
  62. data/files/reveal.js/test/test-markdown.html +1 -1
  63. data/files/reveal.js/test/test.html +5 -1
  64. data/files/reveal.js/test/test.js +26 -1
  65. data/lib/reveal-ck/version.rb +1 -1
  66. metadata +11 -4
  67. data/files/reveal.js/plugin/leap/leap.js +0 -159
  68. data/files/reveal.js/plugin/remotes/remotes.js +0 -39
@@ -7,14 +7,17 @@
7
7
  var RevealMath = window.RevealMath || (function(){
8
8
 
9
9
  var options = Reveal.getConfig().math || {};
10
- options.mathjax = options.mathjax || 'http://cdn.mathjax.org/mathjax/latest/MathJax.js';
10
+ options.mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js';
11
11
  options.config = options.config || 'TeX-AMS_HTML-full';
12
12
 
13
13
  loadScript( options.mathjax + '?config=' + options.config, function() {
14
14
 
15
15
  MathJax.Hub.Config({
16
16
  messageStyle: 'none',
17
- tex2jax: { inlineMath: [['$','$'],['\\(','\\)']] },
17
+ tex2jax: {
18
+ inlineMath: [['$','$'],['\\(','\\)']] ,
19
+ skipTags: ['script','noscript','style','textarea','pre']
20
+ },
18
21
  skipStartupTypeset: true
19
22
  });
20
23
 
@@ -8,6 +8,6 @@
8
8
  if (data.socketId !== socketId) { return; }
9
9
  if( window.location.host === 'localhost:1947' ) return;
10
10
 
11
- Reveal.slide(data.indexh, data.indexv, data.indexf, 'remote');
11
+ Reveal.setState(data.state);
12
12
  });
13
13
  }());
@@ -1,37 +1,45 @@
1
+ var http = require('http');
1
2
  var express = require('express');
2
3
  var fs = require('fs');
3
4
  var io = require('socket.io');
4
5
  var crypto = require('crypto');
5
6
 
6
- var app = express.createServer();
7
- var staticDir = express.static;
7
+ var app = express();
8
+ var staticDir = express.static;
9
+ var server = http.createServer(app);
8
10
 
9
- io = io.listen(app);
11
+ io = io(server);
10
12
 
11
13
  var opts = {
12
- port: 1948,
14
+ port: process.env.PORT || 1948,
13
15
  baseDir : __dirname + '/../../'
14
16
  };
15
17
 
16
- io.sockets.on('connection', function(socket) {
17
- socket.on('slidechanged', function(slideData) {
18
- if (typeof slideData.secret == 'undefined' || slideData.secret == null || slideData.secret === '') return;
19
- if (createHash(slideData.secret) === slideData.socketId) {
20
- slideData.secret = null;
21
- socket.broadcast.emit(slideData.socketId, slideData);
18
+ io.on( 'connection', function( socket ) {
19
+ socket.on('multiplex-statechanged', function(data) {
20
+ if (typeof data.secret == 'undefined' || data.secret == null || data.secret === '') return;
21
+ if (createHash(data.secret) === data.socketId) {
22
+ data.secret = null;
23
+ socket.broadcast.emit(data.socketId, data);
22
24
  };
23
25
  });
24
26
  });
25
27
 
26
- app.configure(function() {
27
- [ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) {
28
- app.use('/' + dir, staticDir(opts.baseDir + dir));
29
- });
28
+ [ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) {
29
+ app.use('/' + dir, staticDir(opts.baseDir + dir));
30
30
  });
31
31
 
32
32
  app.get("/", function(req, res) {
33
33
  res.writeHead(200, {'Content-Type': 'text/html'});
34
- fs.createReadStream(opts.baseDir + '/index.html').pipe(res);
34
+
35
+ var stream = fs.createReadStream(opts.baseDir + '/index.html');
36
+ stream.on('error', function( error ) {
37
+ res.write('<style>body{font-family: sans-serif;}</style><h2>reveal.js multiplex server.</h2><a href="/token">Generate token</a>');
38
+ res.end();
39
+ });
40
+ stream.on('readable', function() {
41
+ stream.pipe(res);
42
+ });
35
43
  });
36
44
 
37
45
  app.get("/token", function(req,res) {
@@ -47,7 +55,7 @@ var createHash = function(secret) {
47
55
  };
48
56
 
49
57
  // Actually listen
50
- app.listen(opts.port || null);
58
+ server.listen( opts.port || null );
51
59
 
52
60
  var brown = '\033[33m',
53
61
  green = '\033[32m',
@@ -1,51 +1,31 @@
1
1
  (function() {
2
+
2
3
  // Don't emit events from inside of notes windows
3
4
  if ( window.location.search.match( /receiver/gi ) ) { return; }
4
5
 
5
6
  var multiplex = Reveal.getConfig().multiplex;
6
7
 
7
- var socket = io.connect(multiplex.url);
8
-
9
- var notify = function( slideElement, indexh, indexv, origin ) {
10
- if( typeof origin === 'undefined' && origin !== 'remote' ) {
11
- var nextindexh;
12
- var nextindexv;
13
-
14
- var fragmentindex = Reveal.getIndices().f;
15
- if (typeof fragmentindex == 'undefined') {
16
- fragmentindex = 0;
17
- }
18
-
19
- if (slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION') {
20
- nextindexh = indexh;
21
- nextindexv = indexv + 1;
22
- } else {
23
- nextindexh = indexh + 1;
24
- nextindexv = 0;
25
- }
26
-
27
- var slideData = {
28
- indexh : indexh,
29
- indexv : indexv,
30
- indexf : fragmentindex,
31
- nextindexh : nextindexh,
32
- nextindexv : nextindexv,
33
- secret: multiplex.secret,
34
- socketId : multiplex.id
35
- };
36
-
37
- socket.emit('slidechanged', slideData);
38
- }
39
- }
40
-
41
- Reveal.addEventListener( 'slidechanged', function( event ) {
42
- notify( event.currentSlide, event.indexh, event.indexv, event.origin );
43
- } );
44
-
45
- var fragmentNotify = function( event ) {
46
- notify( Reveal.getCurrentSlide(), Reveal.getIndices().h, Reveal.getIndices().v, event.origin );
8
+ var socket = io.connect( multiplex.url );
9
+
10
+ function post() {
11
+
12
+ var messageData = {
13
+ state: Reveal.getState(),
14
+ secret: multiplex.secret,
15
+ socketId: multiplex.id
16
+ };
17
+
18
+ socket.emit( 'multiplex-statechanged', messageData );
19
+
47
20
  };
48
21
 
49
- Reveal.addEventListener( 'fragmentshown', fragmentNotify );
50
- Reveal.addEventListener( 'fragmenthidden', fragmentNotify );
22
+ // Monitor events that trigger a change in state
23
+ Reveal.addEventListener( 'slidechanged', post );
24
+ Reveal.addEventListener( 'fragmentshown', post );
25
+ Reveal.addEventListener( 'fragmenthidden', post );
26
+ Reveal.addEventListener( 'overviewhidden', post );
27
+ Reveal.addEventListener( 'overviewshown', post );
28
+ Reveal.addEventListener( 'paused', post );
29
+ Reveal.addEventListener( 'resumed', post );
30
+
51
31
  }());
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "reveal-js-multiplex",
3
+ "version": "1.0.0",
4
+ "description": "reveal.js multiplex server",
5
+ "homepage": "http://lab.hakim.se/reveal-js",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "engines": {
10
+ "node": "~4.1.1"
11
+ },
12
+ "dependencies": {
13
+ "express": "~4.13.3",
14
+ "grunt-cli": "~0.1.13",
15
+ "mustache": "~2.2.1",
16
+ "socket.io": "~1.3.7"
17
+ },
18
+ "license": "MIT"
19
+ }
@@ -41,10 +41,15 @@
41
41
  }
42
42
 
43
43
  // When a new notes window connects, post our current state
44
- socket.on( 'connect', function( data ) {
44
+ socket.on( 'new-subscriber', function( data ) {
45
45
  post();
46
46
  } );
47
47
 
48
+ // When the state changes from inside of the speaker view
49
+ socket.on( 'statechanged-speaker', function( data ) {
50
+ Reveal.setState( data.state );
51
+ } );
52
+
48
53
  // Monitor events that trigger a change in state
49
54
  Reveal.addEventListener( 'slidechanged', post );
50
55
  Reveal.addEventListener( 'fragmentshown', post );
@@ -1,39 +1,42 @@
1
+ var http = require('http');
1
2
  var express = require('express');
2
3
  var fs = require('fs');
3
4
  var io = require('socket.io');
4
- var _ = require('underscore');
5
5
  var Mustache = require('mustache');
6
6
 
7
- var app = express.createServer();
7
+ var app = express();
8
8
  var staticDir = express.static;
9
+ var server = http.createServer(app);
9
10
 
10
- io = io.listen(app);
11
+ io = io(server);
11
12
 
12
13
  var opts = {
13
14
  port : 1947,
14
15
  baseDir : __dirname + '/../../'
15
16
  };
16
17
 
17
- io.sockets.on( 'connection', function( socket ) {
18
+ io.on( 'connection', function( socket ) {
18
19
 
19
- socket.on( 'connect', function( data ) {
20
- socket.broadcast.emit( 'connect', data );
20
+ socket.on( 'new-subscriber', function( data ) {
21
+ socket.broadcast.emit( 'new-subscriber', data );
21
22
  });
22
23
 
23
24
  socket.on( 'statechanged', function( data ) {
25
+ delete data.state.overview;
24
26
  socket.broadcast.emit( 'statechanged', data );
25
27
  });
26
28
 
27
- });
28
-
29
- app.configure( function() {
30
-
31
- [ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) {
32
- app.use( '/' + dir, staticDir( opts.baseDir + dir ) );
29
+ socket.on( 'statechanged-speaker', function( data ) {
30
+ delete data.state.overview;
31
+ socket.broadcast.emit( 'statechanged-speaker', data );
33
32
  });
34
33
 
35
34
  });
36
35
 
36
+ [ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) {
37
+ app.use( '/' + dir, staticDir( opts.baseDir + dir ) );
38
+ });
39
+
37
40
  app.get('/', function( req, res ) {
38
41
 
39
42
  res.writeHead( 200, { 'Content-Type': 'text/html' } );
@@ -52,7 +55,7 @@ app.get( '/notes/:socketId', function( req, res ) {
52
55
  });
53
56
 
54
57
  // Actually listen
55
- app.listen( opts.port || null );
58
+ server.listen( opts.port || null );
56
59
 
57
60
  var brown = '\033[33m',
58
61
  green = '\033[32m',
@@ -62,5 +65,5 @@ var slidesLocation = 'http://localhost' + ( opts.port ? ( ':' + opts.port ) : ''
62
65
 
63
66
  console.log( brown + 'reveal.js - Speaker Notes' + reset );
64
67
  console.log( '1. Open the slides at ' + green + slidesLocation + reset );
65
- console.log( '2. Click on the link your JS console to go to the notes page' );
68
+ console.log( '2. Click on the link in your JS console to go to the notes page' );
66
69
  console.log( '3. Advance through your slides and your notes will advance automatically' );
@@ -8,6 +8,7 @@
8
8
  <style>
9
9
  body {
10
10
  font-family: Helvetica;
11
+ font-size: 18px;
11
12
  }
12
13
 
13
14
  #current-slide,
@@ -30,15 +31,26 @@
30
31
  position: absolute;
31
32
  top: 10px;
32
33
  left: 10px;
33
- font-weight: bold;
34
- font-size: 14px;
35
34
  z-index: 2;
36
- color: rgba( 255, 255, 255, 0.9 );
35
+ }
36
+
37
+ .overlay-element {
38
+ height: 34px;
39
+ line-height: 34px;
40
+ padding: 0 10px;
41
+ text-shadow: none;
42
+ background: rgba( 220, 220, 220, 0.8 );
43
+ color: #222;
44
+ font-size: 14px;
45
+ }
46
+
47
+ .overlay-element.interactive:hover {
48
+ background: rgba( 220, 220, 220, 1 );
37
49
  }
38
50
 
39
51
  #current-slide {
40
52
  position: absolute;
41
- width: 65%;
53
+ width: 60%;
42
54
  height: 100%;
43
55
  top: 0;
44
56
  left: 0;
@@ -47,19 +59,20 @@
47
59
 
48
60
  #upcoming-slide {
49
61
  position: absolute;
50
- width: 35%;
62
+ width: 40%;
51
63
  height: 40%;
52
64
  right: 0;
53
65
  top: 0;
54
66
  }
55
67
 
68
+ /* Speaker controls */
56
69
  #speaker-controls {
57
70
  position: absolute;
58
71
  top: 40%;
59
72
  right: 0;
60
- width: 35%;
73
+ width: 40%;
61
74
  height: 60%;
62
-
75
+ overflow: auto;
63
76
  font-size: 18px;
64
77
  }
65
78
 
@@ -124,26 +137,108 @@
124
137
  font-size: 1.2em;
125
138
  }
126
139
 
140
+ /* Layout selector */
141
+ #speaker-layout {
142
+ position: absolute;
143
+ top: 10px;
144
+ right: 10px;
145
+ color: #222;
146
+ z-index: 10;
147
+ }
148
+ #speaker-layout select {
149
+ position: absolute;
150
+ width: 100%;
151
+ height: 100%;
152
+ top: 0;
153
+ left: 0;
154
+ border: 0;
155
+ box-shadow: 0;
156
+ cursor: pointer;
157
+ opacity: 0;
158
+
159
+ font-size: 1em;
160
+ background-color: transparent;
161
+
162
+ -moz-appearance: none;
163
+ -webkit-appearance: none;
164
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
165
+ }
166
+
167
+ #speaker-layout select:focus {
168
+ outline: none;
169
+ box-shadow: none;
170
+ }
171
+
127
172
  .clear {
128
173
  clear: both;
129
174
  }
130
175
 
131
- @media screen and (max-width: 1080px) {
132
- #speaker-controls {
133
- font-size: 16px;
134
- }
176
+ /* Speaker layout: Wide */
177
+ body[data-speaker-layout="wide"] #current-slide,
178
+ body[data-speaker-layout="wide"] #upcoming-slide {
179
+ width: 50%;
180
+ height: 45%;
181
+ padding: 6px;
135
182
  }
136
183
 
137
- @media screen and (max-width: 900px) {
138
- #speaker-controls {
139
- font-size: 14px;
140
- }
184
+ body[data-speaker-layout="wide"] #current-slide {
185
+ top: 0;
186
+ left: 0;
141
187
  }
142
188
 
143
- @media screen and (max-width: 800px) {
144
- #speaker-controls {
145
- font-size: 12px;
146
- }
189
+ body[data-speaker-layout="wide"] #upcoming-slide {
190
+ top: 0;
191
+ left: 50%;
192
+ }
193
+
194
+ body[data-speaker-layout="wide"] #speaker-controls {
195
+ top: 45%;
196
+ left: 0;
197
+ width: 100%;
198
+ height: 50%;
199
+ font-size: 1.25em;
200
+ }
201
+
202
+ /* Speaker layout: Tall */
203
+ body[data-speaker-layout="tall"] #current-slide,
204
+ body[data-speaker-layout="tall"] #upcoming-slide {
205
+ width: 45%;
206
+ height: 50%;
207
+ padding: 6px;
208
+ }
209
+
210
+ body[data-speaker-layout="tall"] #current-slide {
211
+ top: 0;
212
+ left: 0;
213
+ }
214
+
215
+ body[data-speaker-layout="tall"] #upcoming-slide {
216
+ top: 50%;
217
+ left: 0;
218
+ }
219
+
220
+ body[data-speaker-layout="tall"] #speaker-controls {
221
+ padding-top: 40px;
222
+ top: 0;
223
+ left: 45%;
224
+ width: 55%;
225
+ height: 100%;
226
+ font-size: 1.25em;
227
+ }
228
+
229
+ /* Speaker layout: Notes only */
230
+ body[data-speaker-layout="notes-only"] #current-slide,
231
+ body[data-speaker-layout="notes-only"] #upcoming-slide {
232
+ display: none;
233
+ }
234
+
235
+ body[data-speaker-layout="notes-only"] #speaker-controls {
236
+ padding-top: 40px;
237
+ top: 0;
238
+ left: 0;
239
+ width: 100%;
240
+ height: 100%;
241
+ font-size: 1.25em;
147
242
  }
148
243
 
149
244
  </style>
@@ -152,7 +247,7 @@
152
247
  <body>
153
248
 
154
249
  <div id="current-slide"></div>
155
- <div id="upcoming-slide"><span class="label">UPCOMING:</span></div>
250
+ <div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
156
251
  <div id="speaker-controls">
157
252
  <div class="speaker-controls-time">
158
253
  <h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
@@ -170,6 +265,10 @@
170
265
  <div class="value"></div>
171
266
  </div>
172
267
  </div>
268
+ <div id="speaker-layout" class="overlay-element interactive">
269
+ <span class="speaker-layout-label"></span>
270
+ <select class="speaker-layout-dropdown"></select>
271
+ </div>
173
272
 
174
273
  <script src="/socket.io/socket.io.js"></script>
175
274
  <script src="/plugin/markdown/marked.js"></script>
@@ -182,11 +281,20 @@
182
281
  currentState,
183
282
  currentSlide,
184
283
  upcomingSlide,
284
+ layoutLabel,
285
+ layoutDropdown,
185
286
  connected = false;
186
287
 
187
288
  var socket = io.connect( window.location.origin ),
188
289
  socketId = '{{socketId}}';
189
290
 
291
+ var SPEAKER_LAYOUTS = {
292
+ 'default': 'Default',
293
+ 'wide': 'Wide',
294
+ 'tall': 'Tall',
295
+ 'notes-only': 'Notes only'
296
+ };
297
+
190
298
  socket.on( 'statechanged', function( data ) {
191
299
 
192
300
  // ignore data from sockets that aren't ours
@@ -195,7 +303,6 @@
195
303
  if( connected === false ) {
196
304
  connected = true;
197
305
 
198
- setupIframes( data );
199
306
  setupKeyboard();
200
307
  setupNotes();
201
308
  setupTimer();
@@ -206,13 +313,28 @@
206
313
 
207
314
  } );
208
315
 
316
+ setupLayout();
317
+
318
+ // Load our presentation iframes
319
+ setupIframes();
320
+
321
+ // Once the iframes have loaded, emit a signal saying there's
322
+ // a new subscriber which will trigger a 'statechanged'
323
+ // message to be sent back
209
324
  window.addEventListener( 'message', function( event ) {
210
325
 
211
326
  var data = JSON.parse( event.data );
212
327
 
213
328
  if( data && data.namespace === 'reveal' ) {
214
329
  if( /ready/.test( data.eventName ) ) {
215
- socket.emit( 'connect', { socketId: socketId } );
330
+ socket.emit( 'new-subscriber', { socketId: socketId } );
331
+ }
332
+ }
333
+
334
+ // Messages sent by reveal.js inside of the current slide preview
335
+ if( data && data.namespace === 'reveal' ) {
336
+ if( /slidechanged|fragmentshown|fragmenthidden|overviewshown|overviewhidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
337
+ socket.emit( 'statechanged-speaker', { state: data.state } );
216
338
  }
217
339
  }
218
340
 
@@ -267,7 +389,7 @@
267
389
  /**
268
390
  * Creates the preview iframes.
269
391
  */
270
- function setupIframes( data ) {
392
+ function setupIframes() {
271
393
 
272
394
  var params = [
273
395
  'receiver',
@@ -277,9 +399,8 @@
277
399
  'backgroundTransition=none'
278
400
  ].join( '&' );
279
401
 
280
- var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
281
- var currentURL = '/?' + params + '&postMessageEvents=true' + hash;
282
- var upcomingURL = '/?' + params + '&controls=false' + hash;
402
+ var currentURL = '/?' + params + '&postMessageEvents=true';
403
+ var upcomingURL = '/?' + params + '&controls=false';
283
404
 
284
405
  currentSlide = document.createElement( 'iframe' );
285
406
  currentSlide.setAttribute( 'width', 1280 );
@@ -351,6 +472,74 @@
351
472
 
352
473
  }
353
474
 
475
+ /**
476
+ * Sets up the speaker view layout and layout selector.
477
+ */
478
+ function setupLayout() {
479
+
480
+ layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
481
+ layoutLabel = document.querySelector( '.speaker-layout-label' );
482
+
483
+ // Render the list of available layouts
484
+ for( var id in SPEAKER_LAYOUTS ) {
485
+ var option = document.createElement( 'option' );
486
+ option.setAttribute( 'value', id );
487
+ option.textContent = SPEAKER_LAYOUTS[ id ];
488
+ layoutDropdown.appendChild( option );
489
+ }
490
+
491
+ // Monitor the dropdown for changes
492
+ layoutDropdown.addEventListener( 'change', function( event ) {
493
+
494
+ setLayout( layoutDropdown.value );
495
+
496
+ }, false );
497
+
498
+ // Restore any currently persisted layout
499
+ setLayout( getLayout() );
500
+
501
+ }
502
+
503
+ /**
504
+ * Sets a new speaker view layout. The layout is persisted
505
+ * in local storage.
506
+ */
507
+ function setLayout( value ) {
508
+
509
+ var title = SPEAKER_LAYOUTS[ value ];
510
+
511
+ layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
512
+ layoutDropdown.value = value;
513
+
514
+ document.body.setAttribute( 'data-speaker-layout', value );
515
+
516
+ // Persist locally
517
+ if( window.localStorage ) {
518
+ window.localStorage.setItem( 'reveal-speaker-layout', value );
519
+ }
520
+
521
+ }
522
+
523
+ /**
524
+ * Returns the ID of the most recently set speaker layout
525
+ * or our default layout if none has been set.
526
+ */
527
+ function getLayout() {
528
+
529
+ if( window.localStorage ) {
530
+ var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
531
+ if( layout ) {
532
+ return layout;
533
+ }
534
+ }
535
+
536
+ // Default to the first record in the layouts hash
537
+ for( var id in SPEAKER_LAYOUTS ) {
538
+ return id;
539
+ }
540
+
541
+ }
542
+
354
543
  function zeroPadInteger( num ) {
355
544
 
356
545
  var str = '00' + parseInt( num );