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 );
67 .speaker-controls-time.hidden,
68 .speaker-controls-notes.hidden {
72 .speaker-controls-time .label,
73 .speaker-controls-notes .label {
74 text-transform: uppercase;
81 .speaker-controls-time {
82 border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
89 .speaker-controls-time .reset-button {
93 text-decoration: none;
95 .speaker-controls-time:hover .reset-button {
99 .speaker-controls-time .timer,
100 .speaker-controls-time .clock {
105 .speaker-controls-time .timer {
109 .speaker-controls-time .clock {
114 .speaker-controls-time span.mute {
118 .speaker-controls-notes {
122 .speaker-controls-notes .value {
132 @media screen and (max-width: 1080px) {
138 @media screen and (max-width: 900px) {
144 @media screen and (max-width: 800px) {
155 <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>
161 <span class="clock-value">0:00 AM</span>
164 <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
166 <div class="clear"></div>
169 <div class="speaker-controls-notes hidden">
170 <h4 class="label">Notes</h4>
171 <div class="value"></div>
175 <script src="../../plugin/markdown/marked.js"></script>
187 window.addEventListener( 'message', function( event ) {
189 var data = JSON.parse( event.data );
191 // 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 );
196 else if( data.type === 'state' ) {
197 handleStateMessage( data );
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'} ), '*' );
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 ]} ), '*' );
214 * Called when the main window is trying to establish a
217 function handleConnectMessage( data ) {
219 if( connected === false ) {
222 setupIframes( data );
231 * Called when the main window sends an updated state.
233 function handleStateMessage( data ) {
235 // Store the most recently set state to avoid circular loops
236 // applying the same state
237 currentState = JSON.stringify( data.state );
239 // No need for updating the notes in case of fragment changes
241 notes.classList.remove( 'hidden' );
242 notesValue.style.whiteSpace = data.whitespace;
243 if( data.markdown ) {
244 notesValue.innerHTML = marked( data.notes );
247 notesValue.innerHTML = data.notes;
251 notes.classList.add( 'hidden' );
254 // Update the note slides
255 currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
256 upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
257 upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
261 // Limit to max one state update per X ms
262 handleStateMessage = debounce( handleStateMessage, 200 );
265 * Forward keyboard events to the current slide window.
266 * This enables keyboard events to work even if focus
267 * isn't set on the current slide iframe.
269 function setupKeyboard() {
271 document.addEventListener( 'keydown', function( event ) {
272 currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
278 * Creates the preview iframes.
280 function setupIframes( data ) {
288 'backgroundTransition=none'
291 var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
292 var currentURL = data.url + '?' + params + '&postMessageEvents=true' + hash;
293 var upcomingURL = data.url + '?' + params + '&controls=false' + hash;
295 currentSlide = document.createElement( 'iframe' );
296 currentSlide.setAttribute( 'width', 1280 );
297 currentSlide.setAttribute( 'height', 1024 );
298 currentSlide.setAttribute( 'src', currentURL );
299 document.querySelector( '#current-slide' ).appendChild( currentSlide );
301 upcomingSlide = document.createElement( 'iframe' );
302 upcomingSlide.setAttribute( 'width', 640 );
303 upcomingSlide.setAttribute( 'height', 512 );
304 upcomingSlide.setAttribute( 'src', upcomingURL );
305 document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
310 * Setup the notes UI.
312 function setupNotes() {
314 notes = document.querySelector( '.speaker-controls-notes' );
315 notesValue = document.querySelector( '.speaker-controls-notes .value' );
320 * Create the timer and clock and start updating them
323 function setupTimer() {
325 var start = new Date(),
326 timeEl = document.querySelector( '.speaker-controls-time' ),
327 clockEl = timeEl.querySelector( '.clock-value' ),
328 hoursEl = timeEl.querySelector( '.hours-value' ),
329 minutesEl = timeEl.querySelector( '.minutes-value' ),
330 secondsEl = timeEl.querySelector( '.seconds-value' );
332 function _updateTimer() {
334 var diff, hours, minutes, seconds,
337 diff = now.getTime() - start.getTime();
338 hours = Math.floor( diff / ( 1000 * 60 * 60 ) );
339 minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 );
340 seconds = Math.floor( ( diff / 1000 ) % 60 );
342 clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
343 hoursEl.innerHTML = zeroPadInteger( hours );
344 hoursEl.className = hours > 0 ? '' : 'mute';
345 minutesEl.innerHTML = ':' + zeroPadInteger( minutes );
346 minutesEl.className = minutes > 0 ? '' : 'mute';
347 secondsEl.innerHTML = ':' + zeroPadInteger( seconds );
351 // Update once directly
354 // Then update every second
355 setInterval( _updateTimer, 1000 );
357 timeEl.addEventListener( 'click', function() {
365 function zeroPadInteger( num ) {
367 var str = '00' + parseInt( num );
368 return str.substring( str.length - 2 );
373 * Limits the frequency at which a function can be called.
375 function debounce( fn, ms ) {
382 var args = arguments;
385 clearTimeout( timeout );
387 var timeSinceLastCall = Date.now() - lastTime;
388 if( timeSinceLastCall > ms ) {
389 fn.apply( context, args );
390 lastTime = Date.now();
393 timeout = setTimeout( function() {
394 fn.apply( context, args );
395 lastTime = Date.now();
396 }, ms - timeSinceLastCall );