6 <title>reveal.js - Slide Notes</title>
10 font-family: Helvetica;
17 box-sizing: border-box;
18 -moz-box-sizing: border-box;
21 #current-slide iframe,
22 #upcoming-slide iframe {
25 border: 1px solid #ddd;
28 #current-slide .label,
29 #upcoming-slide .label {
36 color: rgba( 255, 255, 255, 0.9 );
66 .speaker-controls-time.hidden,
67 .speaker-controls-notes.hidden {
71 .speaker-controls-time .label,
72 .speaker-controls-notes .label {
73 text-transform: uppercase;
80 .speaker-controls-time {
81 border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
88 .speaker-controls-time .reset-button {
92 text-decoration: none;
94 .speaker-controls-time:hover .reset-button {
98 .speaker-controls-time .timer,
99 .speaker-controls-time .clock {
104 .speaker-controls-time .timer {
108 .speaker-controls-time .clock {
113 .speaker-controls-time span.mute {
117 .speaker-controls-notes {
121 .speaker-controls-notes .value {
131 @media screen and (max-width: 1080px) {
137 @media screen and (max-width: 900px) {
143 @media screen and (max-width: 800px) {
154 <div id="current-slide"></div>
155 <div id="upcoming-slide"><span class="label">UPCOMING:</span></div>
156 <div id="speaker-controls">
157 <div class="speaker-controls-time">
158 <h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
160 <span class="clock-value">0:00 AM</span>
163 <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
165 <div class="clear"></div>
168 <div class="speaker-controls-notes hidden">
169 <h4 class="label">Notes</h4>
170 <div class="value"></div>
174 <script src="/socket.io/socket.io.js"></script>
175 <script src="/plugin/markdown/marked.js"></script>
187 var socket = io.connect( window.location.origin ),
188 socketId = '{{socketId}}';
190 socket.on( 'statechanged', function( data ) {
192 // ignore data from sockets that aren't ours
193 if( data.socketId !== socketId ) { return; }
195 if( connected === false ) {
204 handleStateMessage( data );
208 // Load our presentation iframes
211 // Once the iframes have loaded, emit a signal saying there's
212 // a new subscriber which will trigger a 'statechanged'
213 // message to be sent back
214 window.addEventListener( 'message', function( event ) {
216 var data = JSON.parse( event.data );
218 if( data && data.namespace === 'reveal' ) {
219 if( /ready/.test( data.eventName ) ) {
220 socket.emit( 'new-subscriber', { socketId: socketId } );
227 * Called when the main window sends an updated state.
229 function handleStateMessage( data ) {
231 // Store the most recently set state to avoid circular loops
232 // applying the same state
233 currentState = JSON.stringify( data.state );
235 // No need for updating the notes in case of fragment changes
237 notes.classList.remove( 'hidden' );
238 if( data.markdown ) {
239 notesValue.innerHTML = marked( data.notes );
242 notesValue.innerHTML = data.notes;
246 notes.classList.add( 'hidden' );
249 // Update the note slides
250 currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
251 upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
252 upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
256 // Limit to max one state update per X ms
257 handleStateMessage = debounce( handleStateMessage, 200 );
260 * Forward keyboard events to the current slide window.
261 * This enables keyboard events to work even if focus
262 * isn't set on the current slide iframe.
264 function setupKeyboard() {
266 document.addEventListener( 'keydown', function( event ) {
267 currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
273 * Creates the preview iframes.
275 function setupIframes() {
282 'backgroundTransition=none'
285 var currentURL = '/?' + params + '&postMessageEvents=true';
286 var upcomingURL = '/?' + params + '&controls=false';
288 currentSlide = document.createElement( 'iframe' );
289 currentSlide.setAttribute( 'width', 1280 );
290 currentSlide.setAttribute( 'height', 1024 );
291 currentSlide.setAttribute( 'src', currentURL );
292 document.querySelector( '#current-slide' ).appendChild( currentSlide );
294 upcomingSlide = document.createElement( 'iframe' );
295 upcomingSlide.setAttribute( 'width', 640 );
296 upcomingSlide.setAttribute( 'height', 512 );
297 upcomingSlide.setAttribute( 'src', upcomingURL );
298 document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
303 * Setup the notes UI.
305 function setupNotes() {
307 notes = document.querySelector( '.speaker-controls-notes' );
308 notesValue = document.querySelector( '.speaker-controls-notes .value' );
313 * Create the timer and clock and start updating them
316 function setupTimer() {
318 var start = new Date(),
319 timeEl = document.querySelector( '.speaker-controls-time' ),
320 clockEl = timeEl.querySelector( '.clock-value' ),
321 hoursEl = timeEl.querySelector( '.hours-value' ),
322 minutesEl = timeEl.querySelector( '.minutes-value' ),
323 secondsEl = timeEl.querySelector( '.seconds-value' );
325 function _updateTimer() {
327 var diff, hours, minutes, seconds,
330 diff = now.getTime() - start.getTime();
331 hours = Math.floor( diff / ( 1000 * 60 * 60 ) );
332 minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 );
333 seconds = Math.floor( ( diff / 1000 ) % 60 );
335 clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
336 hoursEl.innerHTML = zeroPadInteger( hours );
337 hoursEl.className = hours > 0 ? '' : 'mute';
338 minutesEl.innerHTML = ':' + zeroPadInteger( minutes );
339 minutesEl.className = minutes > 0 ? '' : 'mute';
340 secondsEl.innerHTML = ':' + zeroPadInteger( seconds );
344 // Update once directly
347 // Then update every second
348 setInterval( _updateTimer, 1000 );
350 timeEl.addEventListener( 'click', function() {
358 function zeroPadInteger( num ) {
360 var str = '00' + parseInt( num );
361 return str.substring( str.length - 2 );
366 * Limits the frequency at which a function can be called.
368 function debounce( fn, ms ) {
375 var args = arguments;
378 clearTimeout( timeout );
380 var timeSinceLastCall = Date.now() - lastTime;
381 if( timeSinceLastCall > ms ) {
382 fn.apply( context, args );
383 lastTime = Date.now();
386 timeout = setTimeout( function() {
387 fn.apply( context, args );
388 lastTime = Date.now();
389 }, ms - timeSinceLastCall );