blob: 0cc8cf6127636090ec5e729a7bbcab44b7507e57 [file] [log] [blame]
JJ Allaire2ec40242014-09-15 09:18:39 -04001<!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 }
12
timelyportfoliobbdec0b2015-03-05 08:51:24 -060013 #current-slide,
14 #upcoming-slide,
15 #speaker-controls {
16 padding: 6px;
17 box-sizing: border-box;
18 -moz-box-sizing: border-box;
JJ Allaire2ec40242014-09-15 09:18:39 -040019 }
20
timelyportfoliobbdec0b2015-03-05 08:51:24 -060021 #current-slide iframe,
22 #upcoming-slide iframe {
23 width: 100%;
24 height: 100%;
25 border: 1px solid #ddd;
JJ Allaire2ec40242014-09-15 09:18:39 -040026 }
27
timelyportfoliobbdec0b2015-03-05 08:51:24 -060028 #current-slide .label,
29 #upcoming-slide .label {
JJ Allaire2ec40242014-09-15 09:18:39 -040030 position: absolute;
timelyportfoliobbdec0b2015-03-05 08:51:24 -060031 top: 10px;
32 left: 10px;
JJ Allaire2ec40242014-09-15 09:18:39 -040033 font-weight: bold;
34 font-size: 14px;
timelyportfoliobbdec0b2015-03-05 08:51:24 -060035 z-index: 2;
JJ Allaire2ec40242014-09-15 09:18:39 -040036 color: rgba( 255, 255, 255, 0.9 );
37 }
38
timelyportfoliobbdec0b2015-03-05 08:51:24 -060039 #current-slide {
40 position: absolute;
41 width: 65%;
42 height: 100%;
43 top: 0;
44 left: 0;
45 padding-right: 0;
JJ Allaire2ec40242014-09-15 09:18:39 -040046 }
47
timelyportfoliobbdec0b2015-03-05 08:51:24 -060048 #upcoming-slide {
49 position: absolute;
50 width: 35%;
51 height: 40%;
52 right: 0;
53 top: 0;
JJ Allaire2ec40242014-09-15 09:18:39 -040054 }
55
timelyportfoliobbdec0b2015-03-05 08:51:24 -060056 #speaker-controls {
57 position: absolute;
58 top: 40%;
59 right: 0;
60 width: 35%;
61 height: 60%;
62 overflow: auto;
JJ Allaire2ec40242014-09-15 09:18:39 -040063
timelyportfoliobbdec0b2015-03-05 08:51:24 -060064 font-size: 18px;
JJ Allaire2ec40242014-09-15 09:18:39 -040065 }
66
timelyportfoliobbdec0b2015-03-05 08:51:24 -060067 .speaker-controls-time.hidden,
68 .speaker-controls-notes.hidden {
69 display: none;
70 }
71
72 .speaker-controls-time .label,
73 .speaker-controls-notes .label {
74 text-transform: uppercase;
75 font-weight: normal;
76 font-size: 0.66em;
77 color: #666;
78 margin: 0;
79 }
80
81 .speaker-controls-time {
82 border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
83 margin-bottom: 10px;
84 padding: 10px 16px;
85 padding-bottom: 20px;
86 cursor: pointer;
87 }
88
89 .speaker-controls-time .reset-button {
90 opacity: 0;
91 float: right;
92 color: #666;
93 text-decoration: none;
94 }
95 .speaker-controls-time:hover .reset-button {
96 opacity: 1;
97 }
98
99 .speaker-controls-time .timer,
100 .speaker-controls-time .clock {
101 width: 50%;
102 font-size: 1.9em;
103 }
104
105 .speaker-controls-time .timer {
106 float: left;
107 }
108
109 .speaker-controls-time .clock {
110 float: right;
111 text-align: right;
112 }
113
114 .speaker-controls-time span.mute {
115 color: #bbb;
116 }
117
118 .speaker-controls-notes {
119 padding: 10px 16px;
120 }
121
122 .speaker-controls-notes .value {
123 margin-top: 5px;
124 line-height: 1.4;
125 font-size: 1.2em;
126 }
127
128 .clear {
129 clear: both;
JJ Allaire2ec40242014-09-15 09:18:39 -0400130 }
131
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600132 @media screen and (max-width: 1080px) {
133 #speaker-controls {
134 font-size: 16px;
135 }
JJ Allaire2ec40242014-09-15 09:18:39 -0400136 }
137
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600138 @media screen and (max-width: 900px) {
139 #speaker-controls {
140 font-size: 14px;
141 }
142 }
143
144 @media screen and (max-width: 800px) {
145 #speaker-controls {
146 font-size: 12px;
147 }
JJ Allaire2ec40242014-09-15 09:18:39 -0400148 }
149
150 </style>
151 </head>
152
153 <body>
154
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600155 <div id="current-slide"></div>
156 <div id="upcoming-slide"><span class="label">UPCOMING:</span></div>
157 <div id="speaker-controls">
158 <div class="speaker-controls-time">
159 <h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
160 <div class="clock">
161 <span class="clock-value">0:00 AM</span>
162 </div>
163 <div class="timer">
164 <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
165 </div>
166 <div class="clear"></div>
JJ Allaire2ec40242014-09-15 09:18:39 -0400167 </div>
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600168
169 <div class="speaker-controls-notes hidden">
170 <h4 class="label">Notes</h4>
171 <div class="value"></div>
JJ Allaire2ec40242014-09-15 09:18:39 -0400172 </div>
173 </div>
174
JJ Allaire2ec40242014-09-15 09:18:39 -0400175 <script src="../../plugin/markdown/marked.js"></script>
176 <script>
177
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600178 (function() {
JJ Allaire2ec40242014-09-15 09:18:39 -0400179
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600180 var notes,
181 notesValue,
182 currentState,
183 currentSlide,
184 upcomingSlide,
185 connected = false;
JJ Allaire2ec40242014-09-15 09:18:39 -0400186
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600187 window.addEventListener( 'message', function( event ) {
JJ Allaire2ec40242014-09-15 09:18:39 -0400188
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600189 var data = JSON.parse( event.data );
JJ Allaire2ec40242014-09-15 09:18:39 -0400190
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600191 // Messages sent by the notes plugin inside of the main window
192 if( data && data.namespace === 'reveal-notes' ) {
193 if( data.type === 'connect' ) {
194 handleConnectMessage( data );
JJ Allaire2ec40242014-09-15 09:18:39 -0400195 }
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600196 else if( data.type === 'state' ) {
197 handleStateMessage( data );
198 }
199 }
200 // Messages sent by the reveal.js inside of the current slide preview
201 else if( data && data.namespace === 'reveal' ) {
202 if( /ready/.test( data.eventName ) ) {
203 // Send a message back to notify that the handshake is complete
204 window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
205 }
206 else if( /slidechanged|fragmentshown|fragmenthidden|overviewshown|overviewhidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
207 window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' );
208 }
209 }
JJ Allaire2ec40242014-09-15 09:18:39 -0400210
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600211 } );
JJ Allaire2ec40242014-09-15 09:18:39 -0400212
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600213 /**
214 * Called when the main window is trying to establish a
215 * connection.
216 */
217 function handleConnectMessage( data ) {
JJ Allaire2ec40242014-09-15 09:18:39 -0400218
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600219 if( connected === false ) {
220 connected = true;
JJ Allaire2ec40242014-09-15 09:18:39 -0400221
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600222 setupIframes( data );
223 setupKeyboard();
224 setupNotes();
225 setupTimer();
226 }
227
228 }
229
230 /**
231 * Called when the main window sends an updated state.
232 */
233 function handleStateMessage( data ) {
234
235 // Store the most recently set state to avoid circular loops
236 // applying the same state
237 currentState = JSON.stringify( data.state );
238
239 // No need for updating the notes in case of fragment changes
240 if ( data.notes ) {
241 notes.classList.remove( 'hidden' );
242 if( data.markdown ) {
243 notesValue.innerHTML = marked( data.notes );
244 }
245 else {
246 notesValue.innerHTML = data.notes;
247 }
248 }
249 else {
250 notes.classList.add( 'hidden' );
251 }
252
253 // Update the note slides
254 currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
255 upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
256 upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
257
258 }
259
260 // Limit to max one state update per X ms
261 handleStateMessage = debounce( handleStateMessage, 200 );
262
263 /**
264 * Forward keyboard events to the current slide window.
265 * This enables keyboard events to work even if focus
266 * isn't set on the current slide iframe.
267 */
268 function setupKeyboard() {
269
270 document.addEventListener( 'keydown', function( event ) {
271 currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
272 } );
273
274 }
275
276 /**
277 * Creates the preview iframes.
278 */
279 function setupIframes( data ) {
280
281 var params = [
282 'receiver',
283 'progress=false',
284 'history=false',
285 'transition=none',
286 'autoSlide=0',
287 'backgroundTransition=none'
288 ].join( '&' );
289
290 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;
293
294 currentSlide = document.createElement( 'iframe' );
295 currentSlide.setAttribute( 'width', 1280 );
296 currentSlide.setAttribute( 'height', 1024 );
297 currentSlide.setAttribute( 'src', currentURL );
298 document.querySelector( '#current-slide' ).appendChild( currentSlide );
299
300 upcomingSlide = document.createElement( 'iframe' );
301 upcomingSlide.setAttribute( 'width', 640 );
302 upcomingSlide.setAttribute( 'height', 512 );
303 upcomingSlide.setAttribute( 'src', upcomingURL );
304 document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
305
306 }
307
308 /**
309 * Setup the notes UI.
310 */
311 function setupNotes() {
312
313 notes = document.querySelector( '.speaker-controls-notes' );
314 notesValue = document.querySelector( '.speaker-controls-notes .value' );
315
316 }
317
318 /**
319 * Create the timer and clock and start updating them
320 * at an interval.
321 */
322 function setupTimer() {
JJ Allaire2ec40242014-09-15 09:18:39 -0400323
324 var start = new Date(),
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600325 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' );
JJ Allaire2ec40242014-09-15 09:18:39 -0400330
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600331 function _updateTimer() {
JJ Allaire2ec40242014-09-15 09:18:39 -0400332
333 var diff, hours, minutes, seconds,
334 now = new Date();
335
336 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
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600341 clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
JJ Allaire2ec40242014-09-15 09:18:39 -0400342 hoursEl.innerHTML = zeroPadInteger( hours );
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600343 hoursEl.className = hours > 0 ? '' : 'mute';
344 minutesEl.innerHTML = ':' + zeroPadInteger( minutes );
345 minutesEl.className = minutes > 0 ? '' : 'mute';
346 secondsEl.innerHTML = ':' + zeroPadInteger( seconds );
JJ Allaire2ec40242014-09-15 09:18:39 -0400347
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600348 }
JJ Allaire2ec40242014-09-15 09:18:39 -0400349
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600350 // Update once directly
351 _updateTimer();
JJ Allaire2ec40242014-09-15 09:18:39 -0400352
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600353 // Then update every second
354 setInterval( _updateTimer, 1000 );
355
356 timeEl.addEventListener( 'click', function() {
357 start = new Date();
358 _updateTimer();
359 return false;
360 } );
361
362 }
363
364 function zeroPadInteger( num ) {
365
366 var str = '00' + parseInt( num );
367 return str.substring( str.length - 2 );
368
369 }
370
371 /**
372 * Limits the frequency at which a function can be called.
373 */
374 function debounce( fn, ms ) {
375
376 var lastTime = 0,
377 timeout;
378
379 return function() {
380
381 var args = arguments;
382 var context = this;
383
384 clearTimeout( timeout );
385
386 var timeSinceLastCall = Date.now() - lastTime;
387 if( timeSinceLastCall > ms ) {
388 fn.apply( context, args );
389 lastTime = Date.now();
390 }
391 else {
392 timeout = setTimeout( function() {
393 fn.apply( context, args );
394 lastTime = Date.now();
395 }, ms - timeSinceLastCall );
JJ Allaire2ec40242014-09-15 09:18:39 -0400396 }
397
398 }
399
JJ Allaire2ec40242014-09-15 09:18:39 -0400400 }
401
timelyportfoliobbdec0b2015-03-05 08:51:24 -0600402 })();
JJ Allaire2ec40242014-09-15 09:18:39 -0400403
404 </script>
405 </body>
406</html>