reveal-jekyll 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +94 -0
  4. data/_includes/basements.html +7 -0
  5. data/_includes/head.html +39 -0
  6. data/_includes/reveal.html +25 -0
  7. data/_layouts/presentation.html +26 -0
  8. data/_layouts/raw.html +20 -0
  9. data/assets/css/print/paper.css +203 -0
  10. data/assets/css/print/pdf.css +164 -0
  11. data/assets/css/reset.css +30 -0
  12. data/assets/css/reveal.css +1598 -0
  13. data/assets/css/reveal.scss +1768 -0
  14. data/assets/css/rouge-highlight/monokai.css +210 -0
  15. data/assets/css/theme/README.md +21 -0
  16. data/assets/css/theme/beige.css +277 -0
  17. data/assets/css/theme/black.css +273 -0
  18. data/assets/css/theme/blood.css +296 -0
  19. data/assets/css/theme/league.css +279 -0
  20. data/assets/css/theme/moon.css +277 -0
  21. data/assets/css/theme/night.css +271 -0
  22. data/assets/css/theme/serif.css +273 -0
  23. data/assets/css/theme/simple.css +276 -0
  24. data/assets/css/theme/sky.css +280 -0
  25. data/assets/css/theme/solarized.css +277 -0
  26. data/assets/css/theme/source/beige.scss +39 -0
  27. data/assets/css/theme/source/black.scss +49 -0
  28. data/assets/css/theme/source/blood.scss +78 -0
  29. data/assets/css/theme/source/league.scss +34 -0
  30. data/assets/css/theme/source/moon.scss +57 -0
  31. data/assets/css/theme/source/night.scss +34 -0
  32. data/assets/css/theme/source/serif.scss +35 -0
  33. data/assets/css/theme/source/simple.scss +43 -0
  34. data/assets/css/theme/source/sky.scss +46 -0
  35. data/assets/css/theme/source/solarized.scss +63 -0
  36. data/assets/css/theme/source/white.scss +49 -0
  37. data/assets/css/theme/template/mixins.scss +29 -0
  38. data/assets/css/theme/template/settings.scss +45 -0
  39. data/assets/css/theme/template/theme.scss +325 -0
  40. data/assets/css/theme/white.css +273 -0
  41. data/assets/js/reveal.js +6028 -0
  42. data/assets/lib/css/monokai.css +71 -0
  43. data/assets/lib/css/zenburn.css +80 -0
  44. data/assets/lib/font/Pacifico/Pacifico.eot +0 -0
  45. data/assets/lib/font/Pacifico/Pacifico.svg +10833 -0
  46. data/assets/lib/font/Pacifico/Pacifico.ttf +0 -0
  47. data/assets/lib/font/Pacifico/Pacifico.woff +0 -0
  48. data/assets/lib/font/Pacifico/Pacifico.woff2 +0 -0
  49. data/assets/lib/font/Pacifico/original-pacifico-regular.ttf +0 -0
  50. data/assets/lib/font/fontawesome/FontAwesome.otf +0 -0
  51. data/assets/lib/font/fontawesome/fontawesome-webfont.eot +0 -0
  52. data/assets/lib/font/fontawesome/fontawesome-webfont.svg +2671 -0
  53. data/assets/lib/font/fontawesome/fontawesome-webfont.ttf +0 -0
  54. data/assets/lib/font/fontawesome/fontawesome-webfont.woff +0 -0
  55. data/assets/lib/font/fontawesome/fontawesome-webfont.woff2 +0 -0
  56. data/assets/lib/font/league-gothic/LICENSE +2 -0
  57. data/assets/lib/font/league-gothic/league-gothic.css +10 -0
  58. data/assets/lib/font/league-gothic/league-gothic.eot +0 -0
  59. data/assets/lib/font/league-gothic/league-gothic.ttf +0 -0
  60. data/assets/lib/font/league-gothic/league-gothic.woff +0 -0
  61. data/assets/lib/font/source-sans-pro/LICENSE +45 -0
  62. data/assets/lib/font/source-sans-pro/source-sans-pro-italic.eot +0 -0
  63. data/assets/lib/font/source-sans-pro/source-sans-pro-italic.ttf +0 -0
  64. data/assets/lib/font/source-sans-pro/source-sans-pro-italic.woff +0 -0
  65. data/assets/lib/font/source-sans-pro/source-sans-pro-regular.eot +0 -0
  66. data/assets/lib/font/source-sans-pro/source-sans-pro-regular.ttf +0 -0
  67. data/assets/lib/font/source-sans-pro/source-sans-pro-regular.woff +0 -0
  68. data/assets/lib/font/source-sans-pro/source-sans-pro-semibold.eot +0 -0
  69. data/assets/lib/font/source-sans-pro/source-sans-pro-semibold.ttf +0 -0
  70. data/assets/lib/font/source-sans-pro/source-sans-pro-semibold.woff +0 -0
  71. data/assets/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot +0 -0
  72. data/assets/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf +0 -0
  73. data/assets/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff +0 -0
  74. data/assets/lib/font/source-sans-pro/source-sans-pro.css +39 -0
  75. data/assets/lib/js/html5shiv.js +7 -0
  76. data/assets/lib/js/promise.js +2 -0
  77. data/assets/plugin/highlight/highlight.js +168 -0
  78. data/assets/plugin/markdown/example.html +134 -0
  79. data/assets/plugin/markdown/example.md +36 -0
  80. data/assets/plugin/markdown/markdown.js +446 -0
  81. data/assets/plugin/markdown/marked.js +6 -0
  82. data/assets/plugin/math/math.js +92 -0
  83. data/assets/plugin/multiplex/client.js +13 -0
  84. data/assets/plugin/multiplex/index.js +64 -0
  85. data/assets/plugin/multiplex/master.js +34 -0
  86. data/assets/plugin/multiplex/package.json +19 -0
  87. data/assets/plugin/notes-server/client.js +65 -0
  88. data/assets/plugin/notes-server/index.js +69 -0
  89. data/assets/plugin/notes-server/notes.html +585 -0
  90. data/assets/plugin/notes/notes.html +834 -0
  91. data/assets/plugin/notes/notes.js +178 -0
  92. data/assets/plugin/print-pdf/print-pdf.js +67 -0
  93. data/assets/plugin/search/search.js +206 -0
  94. data/assets/plugin/zoom-js/zoom.js +277 -0
  95. metadata +180 -0
@@ -0,0 +1,834 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+
6
+ <title>reveal.js - Slide Notes</title>
7
+
8
+ <style>
9
+ body {
10
+ font-family: Helvetica;
11
+ font-size: 18px;
12
+ }
13
+
14
+ #current-slide,
15
+ #upcoming-slide,
16
+ #speaker-controls {
17
+ padding: 6px;
18
+ box-sizing: border-box;
19
+ -moz-box-sizing: border-box;
20
+ }
21
+
22
+ #current-slide iframe,
23
+ #upcoming-slide iframe {
24
+ width: 100%;
25
+ height: 100%;
26
+ border: 1px solid #ddd;
27
+ }
28
+
29
+ #current-slide .label,
30
+ #upcoming-slide .label {
31
+ position: absolute;
32
+ top: 10px;
33
+ left: 10px;
34
+ z-index: 2;
35
+ }
36
+
37
+ #connection-status {
38
+ position: absolute;
39
+ top: 0;
40
+ left: 0;
41
+ width: 100%;
42
+ height: 100%;
43
+ z-index: 20;
44
+ padding: 30% 20% 20% 20%;
45
+ font-size: 18px;
46
+ color: #222;
47
+ background: #fff;
48
+ text-align: center;
49
+ box-sizing: border-box;
50
+ line-height: 1.4;
51
+ }
52
+
53
+ .overlay-element {
54
+ height: 34px;
55
+ line-height: 34px;
56
+ padding: 0 10px;
57
+ text-shadow: none;
58
+ background: rgba( 220, 220, 220, 0.8 );
59
+ color: #222;
60
+ font-size: 14px;
61
+ }
62
+
63
+ .overlay-element.interactive:hover {
64
+ background: rgba( 220, 220, 220, 1 );
65
+ }
66
+
67
+ #current-slide {
68
+ position: absolute;
69
+ width: 60%;
70
+ height: 100%;
71
+ top: 0;
72
+ left: 0;
73
+ padding-right: 0;
74
+ }
75
+
76
+ #upcoming-slide {
77
+ position: absolute;
78
+ width: 40%;
79
+ height: 40%;
80
+ right: 0;
81
+ top: 0;
82
+ }
83
+
84
+ /* Speaker controls */
85
+ #speaker-controls {
86
+ position: absolute;
87
+ top: 40%;
88
+ right: 0;
89
+ width: 40%;
90
+ height: 60%;
91
+ overflow: auto;
92
+ font-size: 18px;
93
+ }
94
+
95
+ .speaker-controls-time.hidden,
96
+ .speaker-controls-notes.hidden {
97
+ display: none;
98
+ }
99
+
100
+ .speaker-controls-time .label,
101
+ .speaker-controls-pace .label,
102
+ .speaker-controls-notes .label {
103
+ text-transform: uppercase;
104
+ font-weight: normal;
105
+ font-size: 0.66em;
106
+ color: #666;
107
+ margin: 0;
108
+ }
109
+
110
+ .speaker-controls-time, .speaker-controls-pace {
111
+ border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
112
+ margin-bottom: 10px;
113
+ padding: 10px 16px;
114
+ padding-bottom: 20px;
115
+ cursor: pointer;
116
+ }
117
+
118
+ .speaker-controls-time .reset-button {
119
+ opacity: 0;
120
+ float: right;
121
+ color: #666;
122
+ text-decoration: none;
123
+ }
124
+ .speaker-controls-time:hover .reset-button {
125
+ opacity: 1;
126
+ }
127
+
128
+ .speaker-controls-time .timer,
129
+ .speaker-controls-time .clock {
130
+ width: 50%;
131
+ }
132
+
133
+ .speaker-controls-time .timer,
134
+ .speaker-controls-time .clock,
135
+ .speaker-controls-time .pacing .hours-value,
136
+ .speaker-controls-time .pacing .minutes-value,
137
+ .speaker-controls-time .pacing .seconds-value {
138
+ font-size: 1.9em;
139
+ }
140
+
141
+ .speaker-controls-time .timer {
142
+ float: left;
143
+ }
144
+
145
+ .speaker-controls-time .clock {
146
+ float: right;
147
+ text-align: right;
148
+ }
149
+
150
+ .speaker-controls-time span.mute {
151
+ opacity: 0.3;
152
+ }
153
+
154
+ .speaker-controls-time .pacing-title {
155
+ margin-top: 5px;
156
+ }
157
+
158
+ .speaker-controls-time .pacing.ahead {
159
+ color: blue;
160
+ }
161
+
162
+ .speaker-controls-time .pacing.on-track {
163
+ color: green;
164
+ }
165
+
166
+ .speaker-controls-time .pacing.behind {
167
+ color: red;
168
+ }
169
+
170
+ .speaker-controls-notes {
171
+ padding: 10px 16px;
172
+ }
173
+
174
+ .speaker-controls-notes .value {
175
+ margin-top: 5px;
176
+ line-height: 1.4;
177
+ font-size: 1.2em;
178
+ }
179
+
180
+ /* Layout selector */
181
+ #speaker-layout {
182
+ position: absolute;
183
+ top: 10px;
184
+ right: 10px;
185
+ color: #222;
186
+ z-index: 10;
187
+ }
188
+ #speaker-layout select {
189
+ position: absolute;
190
+ width: 100%;
191
+ height: 100%;
192
+ top: 0;
193
+ left: 0;
194
+ border: 0;
195
+ box-shadow: 0;
196
+ cursor: pointer;
197
+ opacity: 0;
198
+
199
+ font-size: 1em;
200
+ background-color: transparent;
201
+
202
+ -moz-appearance: none;
203
+ -webkit-appearance: none;
204
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
205
+ }
206
+
207
+ #speaker-layout select:focus {
208
+ outline: none;
209
+ box-shadow: none;
210
+ }
211
+
212
+ .clear {
213
+ clear: both;
214
+ }
215
+
216
+ /* Speaker layout: Wide */
217
+ body[data-speaker-layout="wide"] #current-slide,
218
+ body[data-speaker-layout="wide"] #upcoming-slide {
219
+ width: 50%;
220
+ height: 45%;
221
+ padding: 6px;
222
+ }
223
+
224
+ body[data-speaker-layout="wide"] #current-slide {
225
+ top: 0;
226
+ left: 0;
227
+ }
228
+
229
+ body[data-speaker-layout="wide"] #upcoming-slide {
230
+ top: 0;
231
+ left: 50%;
232
+ }
233
+
234
+ body[data-speaker-layout="wide"] #speaker-controls {
235
+ top: 45%;
236
+ left: 0;
237
+ width: 100%;
238
+ height: 50%;
239
+ font-size: 1.25em;
240
+ }
241
+
242
+ /* Speaker layout: Tall */
243
+ body[data-speaker-layout="tall"] #current-slide,
244
+ body[data-speaker-layout="tall"] #upcoming-slide {
245
+ width: 45%;
246
+ height: 50%;
247
+ padding: 6px;
248
+ }
249
+
250
+ body[data-speaker-layout="tall"] #current-slide {
251
+ top: 0;
252
+ left: 0;
253
+ }
254
+
255
+ body[data-speaker-layout="tall"] #upcoming-slide {
256
+ top: 50%;
257
+ left: 0;
258
+ }
259
+
260
+ body[data-speaker-layout="tall"] #speaker-controls {
261
+ padding-top: 40px;
262
+ top: 0;
263
+ left: 45%;
264
+ width: 55%;
265
+ height: 100%;
266
+ font-size: 1.25em;
267
+ }
268
+
269
+ /* Speaker layout: Notes only */
270
+ body[data-speaker-layout="notes-only"] #current-slide,
271
+ body[data-speaker-layout="notes-only"] #upcoming-slide {
272
+ display: none;
273
+ }
274
+
275
+ body[data-speaker-layout="notes-only"] #speaker-controls {
276
+ padding-top: 40px;
277
+ top: 0;
278
+ left: 0;
279
+ width: 100%;
280
+ height: 100%;
281
+ font-size: 1.25em;
282
+ }
283
+
284
+ @media screen and (max-width: 1080px) {
285
+ body[data-speaker-layout="default"] #speaker-controls {
286
+ font-size: 16px;
287
+ }
288
+ }
289
+
290
+ @media screen and (max-width: 900px) {
291
+ body[data-speaker-layout="default"] #speaker-controls {
292
+ font-size: 14px;
293
+ }
294
+ }
295
+
296
+ @media screen and (max-width: 800px) {
297
+ body[data-speaker-layout="default"] #speaker-controls {
298
+ font-size: 12px;
299
+ }
300
+ }
301
+
302
+ </style>
303
+ </head>
304
+
305
+ <body>
306
+
307
+ <div id="connection-status">Loading speaker view...</div>
308
+
309
+ <div id="current-slide"></div>
310
+ <div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
311
+ <div id="speaker-controls">
312
+ <div class="speaker-controls-time">
313
+ <h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
314
+ <div class="clock">
315
+ <span class="clock-value">0:00 AM</span>
316
+ </div>
317
+ <div class="timer">
318
+ <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
319
+ </div>
320
+ <div class="clear"></div>
321
+
322
+ <h4 class="label pacing-title" style="display: none">Pacing – Time to finish current slide</h4>
323
+ <div class="pacing" style="display: none">
324
+ <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
325
+ </div>
326
+ </div>
327
+
328
+ <div class="speaker-controls-notes hidden">
329
+ <h4 class="label">Notes</h4>
330
+ <div class="value"></div>
331
+ </div>
332
+ </div>
333
+ <div id="speaker-layout" class="overlay-element interactive">
334
+ <span class="speaker-layout-label"></span>
335
+ <select class="speaker-layout-dropdown"></select>
336
+ </div>
337
+
338
+ <script src="../markdown/marked.js"></script>
339
+ <script>
340
+
341
+ (function() {
342
+
343
+ var notes,
344
+ notesValue,
345
+ currentState,
346
+ currentSlide,
347
+ upcomingSlide,
348
+ layoutLabel,
349
+ layoutDropdown,
350
+ pendingCalls = {},
351
+ lastRevealApiCallId = 0,
352
+ connected = false;
353
+
354
+ var SPEAKER_LAYOUTS = {
355
+ 'default': 'Default',
356
+ 'wide': 'Wide',
357
+ 'tall': 'Tall',
358
+ 'notes-only': 'Notes only'
359
+ };
360
+
361
+ setupLayout();
362
+
363
+ var connectionStatus = document.querySelector( '#connection-status' );
364
+ var connectionTimeout = setTimeout( function() {
365
+ connectionStatus.innerHTML = 'Error connecting to main window.<br>Please try closing and reopening the speaker view.';
366
+ }, 5000 );
367
+
368
+ window.addEventListener( 'message', function( event ) {
369
+
370
+ clearTimeout( connectionTimeout );
371
+ connectionStatus.style.display = 'none';
372
+
373
+ var data = JSON.parse( event.data );
374
+
375
+ // The overview mode is only useful to the reveal.js instance
376
+ // where navigation occurs so we don't sync it
377
+ if( data.state ) delete data.state.overview;
378
+
379
+ // Messages sent by the notes plugin inside of the main window
380
+ if( data && data.namespace === 'reveal-notes' ) {
381
+ if( data.type === 'connect' ) {
382
+ handleConnectMessage( data );
383
+ }
384
+ else if( data.type === 'state' ) {
385
+ handleStateMessage( data );
386
+ }
387
+ else if( data.type === 'return' ) {
388
+ pendingCalls[data.callId](data.result);
389
+ delete pendingCalls[data.callId];
390
+ }
391
+ }
392
+ // Messages sent by the reveal.js inside of the current slide preview
393
+ else if( data && data.namespace === 'reveal' ) {
394
+ if( /ready/.test( data.eventName ) ) {
395
+ // Send a message back to notify that the handshake is complete
396
+ window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
397
+ }
398
+ else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
399
+
400
+ window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' );
401
+
402
+ }
403
+ }
404
+
405
+ } );
406
+
407
+ /**
408
+ * Asynchronously calls the Reveal.js API of the main frame.
409
+ */
410
+ function callRevealApi( methodName, methodArguments, callback ) {
411
+
412
+ var callId = ++lastRevealApiCallId;
413
+ pendingCalls[callId] = callback;
414
+ window.opener.postMessage( JSON.stringify( {
415
+ namespace: 'reveal-notes',
416
+ type: 'call',
417
+ callId: callId,
418
+ methodName: methodName,
419
+ arguments: methodArguments
420
+ } ), '*' );
421
+
422
+ }
423
+
424
+ /**
425
+ * Called when the main window is trying to establish a
426
+ * connection.
427
+ */
428
+ function handleConnectMessage( data ) {
429
+
430
+ if( connected === false ) {
431
+ connected = true;
432
+
433
+ setupIframes( data );
434
+ setupKeyboard();
435
+ setupNotes();
436
+ setupTimer();
437
+ }
438
+
439
+ }
440
+
441
+ /**
442
+ * Called when the main window sends an updated state.
443
+ */
444
+ function handleStateMessage( data ) {
445
+
446
+ // Store the most recently set state to avoid circular loops
447
+ // applying the same state
448
+ currentState = JSON.stringify( data.state );
449
+
450
+ // No need for updating the notes in case of fragment changes
451
+ if ( data.notes ) {
452
+ notes.classList.remove( 'hidden' );
453
+ notesValue.style.whiteSpace = data.whitespace;
454
+ if( data.markdown ) {
455
+ notesValue.innerHTML = marked( data.notes );
456
+ }
457
+ else {
458
+ notesValue.innerHTML = data.notes;
459
+ }
460
+ }
461
+ else {
462
+ notes.classList.add( 'hidden' );
463
+ }
464
+
465
+ // Update the note slides
466
+ currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
467
+ upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
468
+ upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
469
+
470
+ }
471
+
472
+ // Limit to max one state update per X ms
473
+ handleStateMessage = debounce( handleStateMessage, 200 );
474
+
475
+ /**
476
+ * Forward keyboard events to the current slide window.
477
+ * This enables keyboard events to work even if focus
478
+ * isn't set on the current slide iframe.
479
+ *
480
+ * Block F5 default handling, it reloads and disconnects
481
+ * the speaker notes window.
482
+ */
483
+ function setupKeyboard() {
484
+
485
+ document.addEventListener( 'keydown', function( event ) {
486
+ if( event.keyCode === 116 || ( event.metaKey && event.keyCode === 82 ) ) {
487
+ event.preventDefault();
488
+ return false;
489
+ }
490
+ currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
491
+ } );
492
+
493
+ }
494
+
495
+ /**
496
+ * Creates the preview iframes.
497
+ */
498
+ function setupIframes( data ) {
499
+
500
+ var params = [
501
+ 'receiver',
502
+ 'progress=false',
503
+ 'history=false',
504
+ 'transition=none',
505
+ 'autoSlide=0',
506
+ 'backgroundTransition=none'
507
+ ].join( '&' );
508
+
509
+ var urlSeparator = /\?/.test(data.url) ? '&' : '?';
510
+ var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
511
+ var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
512
+ var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash;
513
+
514
+ currentSlide = document.createElement( 'iframe' );
515
+ currentSlide.setAttribute( 'width', 1280 );
516
+ currentSlide.setAttribute( 'height', 1024 );
517
+ currentSlide.setAttribute( 'src', currentURL );
518
+ document.querySelector( '#current-slide' ).appendChild( currentSlide );
519
+
520
+ upcomingSlide = document.createElement( 'iframe' );
521
+ upcomingSlide.setAttribute( 'width', 640 );
522
+ upcomingSlide.setAttribute( 'height', 512 );
523
+ upcomingSlide.setAttribute( 'src', upcomingURL );
524
+ document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
525
+
526
+ }
527
+
528
+ /**
529
+ * Setup the notes UI.
530
+ */
531
+ function setupNotes() {
532
+
533
+ notes = document.querySelector( '.speaker-controls-notes' );
534
+ notesValue = document.querySelector( '.speaker-controls-notes .value' );
535
+
536
+ }
537
+
538
+ function getTimings( callback ) {
539
+
540
+ callRevealApi( 'getSlidesAttributes', [], function ( slideAttributes ) {
541
+ callRevealApi( 'getConfig', [], function ( config ) {
542
+ var defaultTiming = config.defaultTiming;
543
+ if (defaultTiming == null) {
544
+ callback(null);
545
+ return;
546
+ }
547
+
548
+ var timings = [];
549
+ for ( var i in slideAttributes ) {
550
+ var slide = slideAttributes[ i ];
551
+ var timing = defaultTiming;
552
+ if( slide.hasOwnProperty( 'data-timing' )) {
553
+ var t = slide[ 'data-timing' ];
554
+ timing = parseInt(t);
555
+ if( isNaN(timing) ) {
556
+ console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
557
+ timing = defaultTiming;
558
+ }
559
+ }
560
+ timings.push(timing);
561
+ }
562
+
563
+ callback( timings );
564
+ } );
565
+ } );
566
+
567
+ }
568
+
569
+ /**
570
+ * Return the number of seconds allocated for presenting
571
+ * all slides up to and including this one.
572
+ */
573
+ function getTimeAllocated( timings, callback ) {
574
+
575
+ callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
576
+ var allocated = 0;
577
+ for (var i in timings.slice(0, currentSlide + 1)) {
578
+ allocated += timings[i];
579
+ }
580
+ callback( allocated );
581
+ } );
582
+
583
+ }
584
+
585
+ /**
586
+ * Create the timer and clock and start updating them
587
+ * at an interval.
588
+ */
589
+ function setupTimer() {
590
+
591
+ var start = new Date(),
592
+ timeEl = document.querySelector( '.speaker-controls-time' ),
593
+ clockEl = timeEl.querySelector( '.clock-value' ),
594
+ hoursEl = timeEl.querySelector( '.hours-value' ),
595
+ minutesEl = timeEl.querySelector( '.minutes-value' ),
596
+ secondsEl = timeEl.querySelector( '.seconds-value' ),
597
+ pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
598
+ pacingEl = timeEl.querySelector( '.pacing' ),
599
+ pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
600
+ pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
601
+ pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
602
+
603
+ var timings = null;
604
+ getTimings( function ( _timings ) {
605
+
606
+ timings = _timings;
607
+ if (_timings !== null) {
608
+ pacingTitleEl.style.removeProperty('display');
609
+ pacingEl.style.removeProperty('display');
610
+ }
611
+
612
+ // Update once directly
613
+ _updateTimer();
614
+
615
+ // Then update every second
616
+ setInterval( _updateTimer, 1000 );
617
+
618
+ } );
619
+
620
+
621
+ function _resetTimer() {
622
+
623
+ if (timings == null) {
624
+ start = new Date();
625
+ _updateTimer();
626
+ }
627
+ else {
628
+ // Reset timer to beginning of current slide
629
+ getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
630
+ var slideEndTiming = slideEndTimingSeconds * 1000;
631
+ callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
632
+ var currentSlideTiming = timings[currentSlide] * 1000;
633
+ var previousSlidesTiming = slideEndTiming - currentSlideTiming;
634
+ var now = new Date();
635
+ start = new Date(now.getTime() - previousSlidesTiming);
636
+ _updateTimer();
637
+ } );
638
+ } );
639
+ }
640
+
641
+ }
642
+
643
+ timeEl.addEventListener( 'click', function() {
644
+ _resetTimer();
645
+ return false;
646
+ } );
647
+
648
+ function _displayTime( hrEl, minEl, secEl, time) {
649
+
650
+ var sign = Math.sign(time) == -1 ? "-" : "";
651
+ time = Math.abs(Math.round(time / 1000));
652
+ var seconds = time % 60;
653
+ var minutes = Math.floor( time / 60 ) % 60 ;
654
+ var hours = Math.floor( time / ( 60 * 60 )) ;
655
+ hrEl.innerHTML = sign + zeroPadInteger( hours );
656
+ if (hours == 0) {
657
+ hrEl.classList.add( 'mute' );
658
+ }
659
+ else {
660
+ hrEl.classList.remove( 'mute' );
661
+ }
662
+ minEl.innerHTML = ':' + zeroPadInteger( minutes );
663
+ if (hours == 0 && minutes == 0) {
664
+ minEl.classList.add( 'mute' );
665
+ }
666
+ else {
667
+ minEl.classList.remove( 'mute' );
668
+ }
669
+ secEl.innerHTML = ':' + zeroPadInteger( seconds );
670
+ }
671
+
672
+ function _updateTimer() {
673
+
674
+ var diff, hours, minutes, seconds,
675
+ now = new Date();
676
+
677
+ diff = now.getTime() - start.getTime();
678
+
679
+ clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
680
+ _displayTime( hoursEl, minutesEl, secondsEl, diff );
681
+ if (timings !== null) {
682
+ _updatePacing(diff);
683
+ }
684
+
685
+ }
686
+
687
+ function _updatePacing(diff) {
688
+
689
+ getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
690
+ var slideEndTiming = slideEndTimingSeconds * 1000;
691
+
692
+ callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
693
+ var currentSlideTiming = timings[currentSlide] * 1000;
694
+ var timeLeftCurrentSlide = slideEndTiming - diff;
695
+ if (timeLeftCurrentSlide < 0) {
696
+ pacingEl.className = 'pacing behind';
697
+ }
698
+ else if (timeLeftCurrentSlide < currentSlideTiming) {
699
+ pacingEl.className = 'pacing on-track';
700
+ }
701
+ else {
702
+ pacingEl.className = 'pacing ahead';
703
+ }
704
+ _displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
705
+ } );
706
+ } );
707
+ }
708
+
709
+ }
710
+
711
+ /**
712
+ * Sets up the speaker view layout and layout selector.
713
+ */
714
+ function setupLayout() {
715
+
716
+ layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
717
+ layoutLabel = document.querySelector( '.speaker-layout-label' );
718
+
719
+ // Render the list of available layouts
720
+ for( var id in SPEAKER_LAYOUTS ) {
721
+ var option = document.createElement( 'option' );
722
+ option.setAttribute( 'value', id );
723
+ option.textContent = SPEAKER_LAYOUTS[ id ];
724
+ layoutDropdown.appendChild( option );
725
+ }
726
+
727
+ // Monitor the dropdown for changes
728
+ layoutDropdown.addEventListener( 'change', function( event ) {
729
+
730
+ setLayout( layoutDropdown.value );
731
+
732
+ }, false );
733
+
734
+ // Restore any currently persisted layout
735
+ setLayout( getLayout() );
736
+
737
+ }
738
+
739
+ /**
740
+ * Sets a new speaker view layout. The layout is persisted
741
+ * in local storage.
742
+ */
743
+ function setLayout( value ) {
744
+
745
+ var title = SPEAKER_LAYOUTS[ value ];
746
+
747
+ layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
748
+ layoutDropdown.value = value;
749
+
750
+ document.body.setAttribute( 'data-speaker-layout', value );
751
+
752
+ // Persist locally
753
+ if( supportsLocalStorage() ) {
754
+ window.localStorage.setItem( 'reveal-speaker-layout', value );
755
+ }
756
+
757
+ }
758
+
759
+ /**
760
+ * Returns the ID of the most recently set speaker layout
761
+ * or our default layout if none has been set.
762
+ */
763
+ function getLayout() {
764
+
765
+ if( supportsLocalStorage() ) {
766
+ var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
767
+ if( layout ) {
768
+ return layout;
769
+ }
770
+ }
771
+
772
+ // Default to the first record in the layouts hash
773
+ for( var id in SPEAKER_LAYOUTS ) {
774
+ return id;
775
+ }
776
+
777
+ }
778
+
779
+ function supportsLocalStorage() {
780
+
781
+ try {
782
+ localStorage.setItem('test', 'test');
783
+ localStorage.removeItem('test');
784
+ return true;
785
+ }
786
+ catch( e ) {
787
+ return false;
788
+ }
789
+
790
+ }
791
+
792
+ function zeroPadInteger( num ) {
793
+
794
+ var str = '00' + parseInt( num );
795
+ return str.substring( str.length - 2 );
796
+
797
+ }
798
+
799
+ /**
800
+ * Limits the frequency at which a function can be called.
801
+ */
802
+ function debounce( fn, ms ) {
803
+
804
+ var lastTime = 0,
805
+ timeout;
806
+
807
+ return function() {
808
+
809
+ var args = arguments;
810
+ var context = this;
811
+
812
+ clearTimeout( timeout );
813
+
814
+ var timeSinceLastCall = Date.now() - lastTime;
815
+ if( timeSinceLastCall > ms ) {
816
+ fn.apply( context, args );
817
+ lastTime = Date.now();
818
+ }
819
+ else {
820
+ timeout = setTimeout( function() {
821
+ fn.apply( context, args );
822
+ lastTime = Date.now();
823
+ }, ms - timeSinceLastCall );
824
+ }
825
+
826
+ }
827
+
828
+ }
829
+
830
+ })();
831
+
832
+ </script>
833
+ </body>
834
+ </html>