reveal.rb 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +2 -1
  4. data/Dockerfile +4 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +7 -1
  7. data/README.md +1 -1
  8. data/lib/reveal/command.rb +9 -3
  9. data/lib/reveal/templates/revealjs/css/print/paper.css +4 -3
  10. data/lib/reveal/templates/revealjs/css/print/pdf.css +59 -38
  11. data/lib/reveal/templates/revealjs/css/reveal.css +654 -274
  12. data/lib/reveal/templates/revealjs/css/theme/beige.css +65 -68
  13. data/lib/reveal/templates/revealjs/css/theme/black.css +58 -61
  14. data/lib/reveal/templates/revealjs/css/theme/blood.css +64 -62
  15. data/lib/reveal/templates/revealjs/css/theme/league.css +59 -62
  16. data/lib/reveal/templates/revealjs/css/theme/moon.css +59 -62
  17. data/lib/reveal/templates/revealjs/css/theme/night.css +58 -61
  18. data/lib/reveal/templates/revealjs/css/theme/serif.css +56 -59
  19. data/lib/reveal/templates/revealjs/css/theme/simple.css +60 -60
  20. data/lib/reveal/templates/revealjs/css/theme/sky.css +59 -62
  21. data/lib/reveal/templates/revealjs/css/theme/solarized.css +59 -62
  22. data/lib/reveal/templates/revealjs/css/theme/white.css +59 -62
  23. data/lib/reveal/templates/revealjs/index.html +14 -376
  24. data/lib/reveal/templates/revealjs/js/reveal.js +1073 -342
  25. data/lib/reveal/templates/revealjs/lib/css/zenburn.css +41 -78
  26. data/lib/reveal/templates/revealjs/lib/js/head.min.js +9 -8
  27. data/lib/reveal/templates/revealjs/plugin/highlight/highlight.js +51 -4
  28. data/lib/reveal/templates/revealjs/plugin/markdown/markdown.js +38 -19
  29. data/lib/reveal/templates/revealjs/plugin/markdown/marked.js +1 -1
  30. data/lib/reveal/templates/revealjs/plugin/math/math.js +5 -2
  31. data/lib/reveal/templates/revealjs/plugin/multiplex/client.js +1 -1
  32. data/lib/reveal/templates/revealjs/plugin/multiplex/index.js +24 -16
  33. data/lib/reveal/templates/revealjs/plugin/multiplex/master.js +26 -43
  34. data/lib/reveal/templates/revealjs/plugin/multiplex/package.json +19 -0
  35. data/lib/reveal/templates/revealjs/plugin/notes/notes.html +385 -32
  36. data/lib/reveal/templates/revealjs/plugin/notes/notes.js +39 -6
  37. data/lib/reveal/templates/revealjs/plugin/notes-server/client.js +6 -1
  38. data/lib/reveal/templates/revealjs/plugin/notes-server/index.js +17 -14
  39. data/lib/reveal/templates/revealjs/plugin/notes-server/notes.html +215 -26
  40. data/lib/reveal/templates/revealjs/plugin/print-pdf/print-pdf.js +48 -27
  41. data/lib/reveal/templates/revealjs/plugin/search/search.js +41 -31
  42. data/lib/reveal/templates/revealjs/plugin/zoom-js/zoom.js +17 -23
  43. data/lib/reveal/templates/template.html +12 -41
  44. data/lib/reveal/version.rb +1 -1
  45. data/spec/lib/reveal/command_spec.rb +37 -0
  46. metadata +14 -16
  47. data/lib/reveal/templates/revealjs/lib/font/league-gothic/LICENSE +0 -2
  48. data/lib/reveal/templates/revealjs/lib/font/source-sans-pro/LICENSE +0 -45
  49. data/lib/reveal/templates/revealjs/plugin/leap/leap.js +0 -159
  50. data/lib/reveal/templates/revealjs/plugin/remotes/remotes.js +0 -39
@@ -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,20 +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
-
64
76
  font-size: 18px;
65
77
  }
66
78
 
@@ -70,6 +82,7 @@
70
82
  }
71
83
 
72
84
  .speaker-controls-time .label,
85
+ .speaker-controls-pace .label,
73
86
  .speaker-controls-notes .label {
74
87
  text-transform: uppercase;
75
88
  font-weight: normal;
@@ -78,7 +91,7 @@
78
91
  margin: 0;
79
92
  }
80
93
 
81
- .speaker-controls-time {
94
+ .speaker-controls-time, .speaker-controls-pace {
82
95
  border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
83
96
  margin-bottom: 10px;
84
97
  padding: 10px 16px;
@@ -99,6 +112,13 @@
99
112
  .speaker-controls-time .timer,
100
113
  .speaker-controls-time .clock {
101
114
  width: 50%;
115
+ }
116
+
117
+ .speaker-controls-time .timer,
118
+ .speaker-controls-time .clock,
119
+ .speaker-controls-time .pacing .hours-value,
120
+ .speaker-controls-time .pacing .minutes-value,
121
+ .speaker-controls-time .pacing .seconds-value {
102
122
  font-size: 1.9em;
103
123
  }
104
124
 
@@ -112,7 +132,23 @@
112
132
  }
113
133
 
114
134
  .speaker-controls-time span.mute {
115
- color: #bbb;
135
+ opacity: 0.3;
136
+ }
137
+
138
+ .speaker-controls-time .pacing-title {
139
+ margin-top: 5px;
140
+ }
141
+
142
+ .speaker-controls-time .pacing.ahead {
143
+ color: blue;
144
+ }
145
+
146
+ .speaker-controls-time .pacing.on-track {
147
+ color: green;
148
+ }
149
+
150
+ .speaker-controls-time .pacing.behind {
151
+ color: red;
116
152
  }
117
153
 
118
154
  .speaker-controls-notes {
@@ -125,24 +161,124 @@
125
161
  font-size: 1.2em;
126
162
  }
127
163
 
164
+ /* Layout selector */
165
+ #speaker-layout {
166
+ position: absolute;
167
+ top: 10px;
168
+ right: 10px;
169
+ color: #222;
170
+ z-index: 10;
171
+ }
172
+ #speaker-layout select {
173
+ position: absolute;
174
+ width: 100%;
175
+ height: 100%;
176
+ top: 0;
177
+ left: 0;
178
+ border: 0;
179
+ box-shadow: 0;
180
+ cursor: pointer;
181
+ opacity: 0;
182
+
183
+ font-size: 1em;
184
+ background-color: transparent;
185
+
186
+ -moz-appearance: none;
187
+ -webkit-appearance: none;
188
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
189
+ }
190
+
191
+ #speaker-layout select:focus {
192
+ outline: none;
193
+ box-shadow: none;
194
+ }
195
+
128
196
  .clear {
129
197
  clear: both;
130
198
  }
131
199
 
200
+ /* Speaker layout: Wide */
201
+ body[data-speaker-layout="wide"] #current-slide,
202
+ body[data-speaker-layout="wide"] #upcoming-slide {
203
+ width: 50%;
204
+ height: 45%;
205
+ padding: 6px;
206
+ }
207
+
208
+ body[data-speaker-layout="wide"] #current-slide {
209
+ top: 0;
210
+ left: 0;
211
+ }
212
+
213
+ body[data-speaker-layout="wide"] #upcoming-slide {
214
+ top: 0;
215
+ left: 50%;
216
+ }
217
+
218
+ body[data-speaker-layout="wide"] #speaker-controls {
219
+ top: 45%;
220
+ left: 0;
221
+ width: 100%;
222
+ height: 50%;
223
+ font-size: 1.25em;
224
+ }
225
+
226
+ /* Speaker layout: Tall */
227
+ body[data-speaker-layout="tall"] #current-slide,
228
+ body[data-speaker-layout="tall"] #upcoming-slide {
229
+ width: 45%;
230
+ height: 50%;
231
+ padding: 6px;
232
+ }
233
+
234
+ body[data-speaker-layout="tall"] #current-slide {
235
+ top: 0;
236
+ left: 0;
237
+ }
238
+
239
+ body[data-speaker-layout="tall"] #upcoming-slide {
240
+ top: 50%;
241
+ left: 0;
242
+ }
243
+
244
+ body[data-speaker-layout="tall"] #speaker-controls {
245
+ padding-top: 40px;
246
+ top: 0;
247
+ left: 45%;
248
+ width: 55%;
249
+ height: 100%;
250
+ font-size: 1.25em;
251
+ }
252
+
253
+ /* Speaker layout: Notes only */
254
+ body[data-speaker-layout="notes-only"] #current-slide,
255
+ body[data-speaker-layout="notes-only"] #upcoming-slide {
256
+ display: none;
257
+ }
258
+
259
+ body[data-speaker-layout="notes-only"] #speaker-controls {
260
+ padding-top: 40px;
261
+ top: 0;
262
+ left: 0;
263
+ width: 100%;
264
+ height: 100%;
265
+ font-size: 1.25em;
266
+ }
267
+
132
268
  @media screen and (max-width: 1080px) {
133
- #speaker-controls {
269
+ body[data-speaker-layout="default"] #speaker-controls {
134
270
  font-size: 16px;
135
271
  }
136
272
  }
137
273
 
138
274
  @media screen and (max-width: 900px) {
139
- #speaker-controls {
275
+ body[data-speaker-layout="default"] #speaker-controls {
140
276
  font-size: 14px;
141
277
  }
142
278
  }
143
279
 
144
280
  @media screen and (max-width: 800px) {
145
- #speaker-controls {
281
+ body[data-speaker-layout="default"] #speaker-controls {
146
282
  font-size: 12px;
147
283
  }
148
284
  }
@@ -153,7 +289,7 @@
153
289
  <body>
154
290
 
155
291
  <div id="current-slide"></div>
156
- <div id="upcoming-slide"><span class="label">UPCOMING:</span></div>
292
+ <div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
157
293
  <div id="speaker-controls">
158
294
  <div class="speaker-controls-time">
159
295
  <h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
@@ -164,6 +300,11 @@
164
300
  <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
165
301
  </div>
166
302
  <div class="clear"></div>
303
+
304
+ <h4 class="label pacing-title" style="display: none">Pacing – Time to finish current slide</h4>
305
+ <div class="pacing" style="display: none">
306
+ <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
307
+ </div>
167
308
  </div>
168
309
 
169
310
  <div class="speaker-controls-notes hidden">
@@ -171,6 +312,10 @@
171
312
  <div class="value"></div>
172
313
  </div>
173
314
  </div>
315
+ <div id="speaker-layout" class="overlay-element interactive">
316
+ <span class="speaker-layout-label"></span>
317
+ <select class="speaker-layout-dropdown"></select>
318
+ </div>
174
319
 
175
320
  <script src="../../plugin/markdown/marked.js"></script>
176
321
  <script>
@@ -182,12 +327,27 @@
182
327
  currentState,
183
328
  currentSlide,
184
329
  upcomingSlide,
330
+ layoutLabel,
331
+ layoutDropdown,
185
332
  connected = false;
186
333
 
334
+ var SPEAKER_LAYOUTS = {
335
+ 'default': 'Default',
336
+ 'wide': 'Wide',
337
+ 'tall': 'Tall',
338
+ 'notes-only': 'Notes only'
339
+ };
340
+
341
+ setupLayout();
342
+
187
343
  window.addEventListener( 'message', function( event ) {
188
344
 
189
345
  var data = JSON.parse( event.data );
190
346
 
347
+ // The overview mode is only useful to the reveal.js instance
348
+ // where navigation occurs so we don't sync it
349
+ if( data.state ) delete data.state.overview;
350
+
191
351
  // Messages sent by the notes plugin inside of the main window
192
352
  if( data && data.namespace === 'reveal-notes' ) {
193
353
  if( data.type === 'connect' ) {
@@ -203,8 +363,10 @@
203
363
  // Send a message back to notify that the handshake is complete
204
364
  window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
205
365
  }
206
- else if( /slidechanged|fragmentshown|fragmenthidden|overviewshown|overviewhidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
366
+ else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
367
+
207
368
  window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' );
369
+
208
370
  }
209
371
  }
210
372
 
@@ -239,6 +401,7 @@
239
401
  // No need for updating the notes in case of fragment changes
240
402
  if ( data.notes ) {
241
403
  notes.classList.remove( 'hidden' );
404
+ notesValue.style.whiteSpace = data.whitespace;
242
405
  if( data.markdown ) {
243
406
  notesValue.innerHTML = marked( data.notes );
244
407
  }
@@ -287,9 +450,10 @@
287
450
  'backgroundTransition=none'
288
451
  ].join( '&' );
289
452
 
453
+ var urlSeparator = /\?/.test(data.url) ? '&' : '?';
290
454
  var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
291
- var currentURL = data.url + '?' + params + '&postMessageEvents=true' + hash;
292
- var upcomingURL = data.url + '?' + params + '&controls=false' + hash;
455
+ var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
456
+ var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash;
293
457
 
294
458
  currentSlide = document.createElement( 'iframe' );
295
459
  currentSlide.setAttribute( 'width', 1280 );
@@ -315,6 +479,47 @@
315
479
 
316
480
  }
317
481
 
482
+ function getTimings() {
483
+
484
+ var slides = Reveal.getSlides();
485
+ var defaultTiming = Reveal.getConfig().defaultTiming;
486
+ if (defaultTiming == null) {
487
+ return null;
488
+ }
489
+ var timings = [];
490
+ for ( var i in slides ) {
491
+ var slide = slides[i];
492
+ var timing = defaultTiming;
493
+ if( slide.hasAttribute( 'data-timing' )) {
494
+ var t = slide.getAttribute( 'data-timing' );
495
+ timing = parseInt(t);
496
+ if( isNaN(timing) ) {
497
+ console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
498
+ timing = defaultTiming;
499
+ }
500
+ }
501
+ timings.push(timing);
502
+ }
503
+ return timings;
504
+
505
+ }
506
+
507
+ /**
508
+ * Return the number of seconds allocated for presenting
509
+ * all slides up to and including this one.
510
+ */
511
+ function getTimeAllocated(timings) {
512
+
513
+ var slides = Reveal.getSlides();
514
+ var allocated = 0;
515
+ var currentSlide = Reveal.getSlidePastCount();
516
+ for (var i in slides.slice(0, currentSlide + 1)) {
517
+ allocated += timings[i];
518
+ }
519
+ return allocated;
520
+
521
+ }
522
+
318
523
  /**
319
524
  * Create the timer and clock and start updating them
320
525
  * at an interval.
@@ -322,28 +527,78 @@
322
527
  function setupTimer() {
323
528
 
324
529
  var start = new Date(),
325
- timeEl = document.querySelector( '.speaker-controls-time' ),
326
- clockEl = timeEl.querySelector( '.clock-value' ),
327
- hoursEl = timeEl.querySelector( '.hours-value' ),
328
- minutesEl = timeEl.querySelector( '.minutes-value' ),
329
- secondsEl = timeEl.querySelector( '.seconds-value' );
530
+ timeEl = document.querySelector( '.speaker-controls-time' ),
531
+ clockEl = timeEl.querySelector( '.clock-value' ),
532
+ hoursEl = timeEl.querySelector( '.hours-value' ),
533
+ minutesEl = timeEl.querySelector( '.minutes-value' ),
534
+ secondsEl = timeEl.querySelector( '.seconds-value' ),
535
+ pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
536
+ pacingEl = timeEl.querySelector( '.pacing' ),
537
+ pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
538
+ pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
539
+ pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
540
+
541
+ var timings = getTimings();
542
+ if (timings !== null) {
543
+ pacingTitleEl.style.removeProperty('display');
544
+ pacingEl.style.removeProperty('display');
545
+ }
546
+
547
+ function _displayTime( hrEl, minEl, secEl, time) {
548
+
549
+ var sign = Math.sign(time) == -1 ? "-" : "";
550
+ time = Math.abs(Math.round(time / 1000));
551
+ var seconds = time % 60;
552
+ var minutes = Math.floor( time / 60 ) % 60 ;
553
+ var hours = Math.floor( time / ( 60 * 60 )) ;
554
+ hrEl.innerHTML = sign + zeroPadInteger( hours );
555
+ if (hours == 0) {
556
+ hrEl.classList.add( 'mute' );
557
+ }
558
+ else {
559
+ hrEl.classList.remove( 'mute' );
560
+ }
561
+ minEl.innerHTML = ':' + zeroPadInteger( minutes );
562
+ if (hours == 0 && minutes == 0) {
563
+ minEl.classList.add( 'mute' );
564
+ }
565
+ else {
566
+ minEl.classList.remove( 'mute' );
567
+ }
568
+ secEl.innerHTML = ':' + zeroPadInteger( seconds );
569
+ }
330
570
 
331
571
  function _updateTimer() {
332
572
 
333
573
  var diff, hours, minutes, seconds,
334
- now = new Date();
574
+ now = new Date();
335
575
 
336
576
  diff = now.getTime() - start.getTime();
337
- hours = Math.floor( diff / ( 1000 * 60 * 60 ) );
338
- minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 );
339
- seconds = Math.floor( ( diff / 1000 ) % 60 );
340
577
 
341
578
  clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
342
- hoursEl.innerHTML = zeroPadInteger( hours );
343
- hoursEl.className = hours > 0 ? '' : 'mute';
344
- minutesEl.innerHTML = ':' + zeroPadInteger( minutes );
345
- minutesEl.className = minutes > 0 ? '' : 'mute';
346
- secondsEl.innerHTML = ':' + zeroPadInteger( seconds );
579
+ _displayTime( hoursEl, minutesEl, secondsEl, diff );
580
+ if (timings !== null) {
581
+ _updatePacing(diff);
582
+ }
583
+
584
+ }
585
+
586
+ function _updatePacing(diff) {
587
+
588
+ var slideEndTiming = getTimeAllocated(timings) * 1000;
589
+ var currentSlide = Reveal.getSlidePastCount();
590
+ var currentSlideTiming = timings[currentSlide] * 1000;
591
+ var timeLeftCurrentSlide = slideEndTiming - diff;
592
+ if (timeLeftCurrentSlide < 0) {
593
+ pacingEl.className = 'pacing behind';
594
+ }
595
+ else if (timeLeftCurrentSlide < currentSlideTiming) {
596
+ pacingEl.className = 'pacing on-track';
597
+ }
598
+ else {
599
+ pacingEl.className = 'pacing ahead';
600
+ }
601
+ _displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
347
602
 
348
603
  }
349
604
 
@@ -353,14 +608,112 @@
353
608
  // Then update every second
354
609
  setInterval( _updateTimer, 1000 );
355
610
 
356
- timeEl.addEventListener( 'click', function() {
357
- start = new Date();
611
+ function _resetTimer() {
612
+
613
+ if (timings == null) {
614
+ start = new Date();
615
+ }
616
+ else {
617
+ // Reset timer to beginning of current slide
618
+ var slideEndTiming = getTimeAllocated(timings) * 1000;
619
+ var currentSlide = Reveal.getSlidePastCount();
620
+ var currentSlideTiming = timings[currentSlide] * 1000;
621
+ var previousSlidesTiming = slideEndTiming - currentSlideTiming;
622
+ var now = new Date();
623
+ start = new Date(now.getTime() - previousSlidesTiming);
624
+ }
358
625
  _updateTimer();
626
+
627
+ }
628
+
629
+ timeEl.addEventListener( 'click', function() {
630
+ _resetTimer();
359
631
  return false;
360
632
  } );
361
633
 
362
634
  }
363
635
 
636
+ /**
637
+ * Sets up the speaker view layout and layout selector.
638
+ */
639
+ function setupLayout() {
640
+
641
+ layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
642
+ layoutLabel = document.querySelector( '.speaker-layout-label' );
643
+
644
+ // Render the list of available layouts
645
+ for( var id in SPEAKER_LAYOUTS ) {
646
+ var option = document.createElement( 'option' );
647
+ option.setAttribute( 'value', id );
648
+ option.textContent = SPEAKER_LAYOUTS[ id ];
649
+ layoutDropdown.appendChild( option );
650
+ }
651
+
652
+ // Monitor the dropdown for changes
653
+ layoutDropdown.addEventListener( 'change', function( event ) {
654
+
655
+ setLayout( layoutDropdown.value );
656
+
657
+ }, false );
658
+
659
+ // Restore any currently persisted layout
660
+ setLayout( getLayout() );
661
+
662
+ }
663
+
664
+ /**
665
+ * Sets a new speaker view layout. The layout is persisted
666
+ * in local storage.
667
+ */
668
+ function setLayout( value ) {
669
+
670
+ var title = SPEAKER_LAYOUTS[ value ];
671
+
672
+ layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
673
+ layoutDropdown.value = value;
674
+
675
+ document.body.setAttribute( 'data-speaker-layout', value );
676
+
677
+ // Persist locally
678
+ if( supportsLocalStorage() ) {
679
+ window.localStorage.setItem( 'reveal-speaker-layout', value );
680
+ }
681
+
682
+ }
683
+
684
+ /**
685
+ * Returns the ID of the most recently set speaker layout
686
+ * or our default layout if none has been set.
687
+ */
688
+ function getLayout() {
689
+
690
+ if( supportsLocalStorage() ) {
691
+ var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
692
+ if( layout ) {
693
+ return layout;
694
+ }
695
+ }
696
+
697
+ // Default to the first record in the layouts hash
698
+ for( var id in SPEAKER_LAYOUTS ) {
699
+ return id;
700
+ }
701
+
702
+ }
703
+
704
+ function supportsLocalStorage() {
705
+
706
+ try {
707
+ localStorage.setItem('test', 'test');
708
+ localStorage.removeItem('test');
709
+ return true;
710
+ }
711
+ catch( e ) {
712
+ return false;
713
+ }
714
+
715
+ }
716
+
364
717
  function zeroPadInteger( num ) {
365
718
 
366
719
  var str = '00' + parseInt( num );
@@ -11,10 +11,18 @@
11
11
  */
12
12
  var RevealNotes = (function() {
13
13
 
14
- function openNotes() {
15
- var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path
16
- jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path
17
- var notesPopup = window.open( jsFileLocation + 'notes.html', 'reveal.js - Notes', 'width=1100,height=700' );
14
+ function openNotes( notesFilePath ) {
15
+
16
+ if( !notesFilePath ) {
17
+ var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path
18
+ jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path
19
+ notesFilePath = jsFileLocation + 'notes.html';
20
+ }
21
+
22
+ var notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' );
23
+
24
+ // Allow popup window access to Reveal API
25
+ notesPopup.Reveal = this.Reveal;
18
26
 
19
27
  /**
20
28
  * Connect to the notes window through a postmessage handshake.
@@ -45,22 +53,40 @@ var RevealNotes = (function() {
45
53
  /**
46
54
  * Posts the current slide data to the notes window
47
55
  */
48
- function post() {
56
+ function post( event ) {
49
57
 
50
58
  var slideElement = Reveal.getCurrentSlide(),
51
- notesElement = slideElement.querySelector( 'aside.notes' );
59
+ notesElement = slideElement.querySelector( 'aside.notes' ),
60
+ fragmentElement = slideElement.querySelector( '.current-fragment' );
52
61
 
53
62
  var messageData = {
54
63
  namespace: 'reveal-notes',
55
64
  type: 'state',
56
65
  notes: '',
57
66
  markdown: false,
67
+ whitespace: 'normal',
58
68
  state: Reveal.getState()
59
69
  };
60
70
 
61
71
  // Look for notes defined in a slide attribute
62
72
  if( slideElement.hasAttribute( 'data-notes' ) ) {
63
73
  messageData.notes = slideElement.getAttribute( 'data-notes' );
74
+ messageData.whitespace = 'pre-wrap';
75
+ }
76
+
77
+ // Look for notes defined in a fragment
78
+ if( fragmentElement ) {
79
+ var fragmentNotes = fragmentElement.querySelector( 'aside.notes' );
80
+ if( fragmentNotes ) {
81
+ notesElement = fragmentNotes;
82
+ }
83
+ else if( fragmentElement.hasAttribute( 'data-notes' ) ) {
84
+ messageData.notes = fragmentElement.getAttribute( 'data-notes' );
85
+ messageData.whitespace = 'pre-wrap';
86
+
87
+ // In case there are slide notes
88
+ notesElement = null;
89
+ }
64
90
  }
65
91
 
66
92
  // Look for notes defined in an aside element
@@ -94,6 +120,7 @@ var RevealNotes = (function() {
94
120
  }
95
121
 
96
122
  connect();
123
+
97
124
  }
98
125
 
99
126
  if( !/receiver/i.test( window.location.search ) ) {
@@ -109,12 +136,18 @@ var RevealNotes = (function() {
109
136
  // modifier is present
110
137
  if ( document.querySelector( ':focus' ) !== null || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
111
138
 
139
+ // Disregard the event if keyboard is disabled
140
+ if ( Reveal.getConfig().keyboard === false ) return;
141
+
112
142
  if( event.keyCode === 83 ) {
113
143
  event.preventDefault();
114
144
  openNotes();
115
145
  }
116
146
  }, false );
117
147
 
148
+ // Show our keyboard shortcut in the reveal.js help overlay
149
+ if( window.Reveal ) Reveal.registerKeyboardShortcut( 'S', 'Speaker notes view' );
150
+
118
151
  }
119
152
 
120
153
  return { open: openNotes };
@@ -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 );