reveal.rb 0.4.0 → 0.5.0

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