diff options
-rw-r--r-- | plugin/notes/notes.html | 176 | ||||
-rw-r--r-- | plugin/notes/notes.js | 88 |
2 files changed, 168 insertions, 96 deletions
diff --git a/plugin/notes/notes.html b/plugin/notes/notes.html index 847499d..3e9e8b7 100644 --- a/plugin/notes/notes.html +++ b/plugin/notes/notes.html @@ -82,6 +82,7 @@ left: 3px; font-weight: bold; font-size: 14px; + z-index: 2; color: rgba( 255, 255, 255, 0.9 ); } @@ -138,22 +139,8 @@ <body> - <script> - function getNotesURL( controls ) { - return window.opener.location.protocol + '//' + window.opener.location.host + window.opener.location.pathname + '?receiver&controls='+ ( controls || 'false' ) +'&progress=false&overview=false' + window.opener.location.hash; - } - var notesCurrentSlideURL = getNotesURL( true ); - var notesNextSlideURL = getNotesURL( false ); - </script> - - <div id="wrap-current-slide" class="slides"> - <script>document.write( '<iframe width="1280" height="1024" id="current-slide" src="'+ notesCurrentSlideURL +'"></iframe>' );</script> - </div> - - <div id="wrap-next-slide" class="slides"> - <script>document.write( '<iframe width="640" height="512" id="next-slide" src="'+ notesNextSlideURL +'"></iframe>' );</script> - <span>UPCOMING:</span> - </div> + <div id="wrap-current-slide" class="slides"></div> + <div id="wrap-next-slide" class="slides"><span>UPCOMING:</span></div> <div class="time"> <div class="clock"> @@ -171,37 +158,112 @@ <script src="../../plugin/markdown/marked.js"></script> <script> - window.addEventListener( 'load', function() { + (function() { + + var notes, + currentState, + currentSlide, + nextSlide, + connected = false; + + window.addEventListener( 'message', function( event ) { + + var data = JSON.parse( event.data ); + + // Messages sent by the notes plugin inside of the main window + if( data && data.namespace === 'reveal-notes' ) { + if( data.type === 'connect' ) { + handleConnectMessage( data ); + } + else if( data.type === 'state' ) { + handleStateMessage( data ); + } + } + // Messages sent by the reveal.js inside of the current slide preview + else if( data && data.namespace === 'reveal' ) { + if( /ready/.test( data.eventName ) ) { + // Send a message back to notify that the handshake is complete + window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' ); + } + else if( /slidechanged|fragmentshown|fragmenthidden|overviewshown|overviewhidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) { + window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' ); + } + } + + } ); + + /** + * Called when the main window is trying to establish a + * connection. + */ + function handleConnectMessage( data ) { + + if( connected === false ) { + connected = true; + + setupIframes( data ); + setupTimer(); + } - if( window.opener && window.opener.location && window.opener.location.href ) { + } - var notes = document.getElementById( 'notes' ), - currentSlide = document.getElementById( 'current-slide' ), - nextSlide = document.getElementById( 'next-slide' ), - silenced = false; + /** + * Called when the main window sends an updated state. + */ + function handleStateMessage( data ) { - window.addEventListener( 'message', function( event ) { - var data = JSON.parse( event.data ); + // Store the most recently set state to avoid circular loops + // applying the same state + currentState = JSON.stringify( data.state ); - // No need for updating the notes in case of fragment changes - if ( data.notes !== undefined) { - if( data.markdown ) { - notes.innerHTML = marked( data.notes ); - } - else { - notes.innerHTML = data.notes; - } + // No need for updating the notes in case of fragment changes + if ( data.notes !== undefined) { + if( data.markdown ) { + notes.innerHTML = marked( data.notes ); + } + else { + notes.innerHTML = data.notes; } + } + + // Update the note slides + currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' ); + nextSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' ); + nextSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' ); - silenced = true; + } - // Update the note slides - currentSlide.contentWindow.Reveal.slide( data.indexh, data.indexv, data.indexf ); - nextSlide.contentWindow.Reveal.slide( data.nextindexh, data.nextindexv ); + /** + * Creates the preview iframes. + */ + function setupIframes( data ) { - silenced = false; + notes = document.getElementById( 'notes' ); + + var url = data.url + '?receiver&progress=false&overview=false&history=false'; + var hash = '#/' + data.state.indexh + '/' + data.state.indexv; + + currentSlide = document.createElement( 'iframe' ); + currentSlide.setAttribute( 'id', 'current-slide' ); + currentSlide.setAttribute( 'width', 1280 ); + currentSlide.setAttribute( 'height', 1024 ); + currentSlide.setAttribute( 'src', url + '&postMessageEvents=true' + hash ); + document.querySelector( '#wrap-current-slide' ).appendChild( currentSlide ); + + nextSlide = document.createElement( 'iframe' ); + nextSlide.setAttribute( 'id', 'next-slide' ); + nextSlide.setAttribute( 'width', 640 ); + nextSlide.setAttribute( 'height', 512 ); + nextSlide.setAttribute( 'src', url + '&controls=false' + hash ); + document.querySelector( '#wrap-next-slide' ).appendChild( nextSlide ); + + } - }, false ); + /** + * Create the timer and clock and start updating them + * at an interval. + */ + function setupTimer() { var start = new Date(), timeEl = document.querySelector( '.time' ), @@ -224,43 +286,23 @@ clockEl.innerHTML = now.toLocaleTimeString(); hoursEl.innerHTML = zeroPadInteger( hours ); - hoursEl.className = hours > 0 ? "" : "mute"; - minutesEl.innerHTML = ":" + zeroPadInteger( minutes ); - minutesEl.className = minutes > 0 ? "" : "mute"; - secondsEl.innerHTML = ":" + zeroPadInteger( seconds ); + hoursEl.className = hours > 0 ? '' : 'mute'; + minutesEl.innerHTML = ':' + zeroPadInteger( minutes ); + minutesEl.className = minutes > 0 ? '' : 'mute'; + secondsEl.innerHTML = ':' + zeroPadInteger( seconds ); }, 1000 ); - // Broadcasts the state of the notes window to synchronize - // the main window - function synchronizeMainWindow() { - - if( !silenced ) { - var indices = currentSlide.contentWindow.Reveal.getIndices(); - window.opener.Reveal.slide( indices.h, indices.v, indices.f ); - } - - } - - // Navigate the main window when the notes slide changes - currentSlide.contentWindow.Reveal.addEventListener( 'slidechanged', synchronizeMainWindow ); - currentSlide.contentWindow.Reveal.addEventListener( 'fragmentshown', synchronizeMainWindow ); - currentSlide.contentWindow.Reveal.addEventListener( 'fragmenthidden', synchronizeMainWindow ); - } - else { - - document.body.innerHTML = '<p class="error">Unable to access <code>window.opener.location</code>.<br>Make sure the presentation is running on a web server.</p>'; - } + function zeroPadInteger( num ) { + var str = '00' + parseInt( num ); + return str.substring( str.length - 2 ); - }, false ); + } - function zeroPadInteger( num ) { - var str = "00" + parseInt( num ); - return str.substring( str.length - 2 ); - } + })(); </script> </body> diff --git a/plugin/notes/notes.js b/plugin/notes/notes.js index 3f68b5d..31efd81 100644 --- a/plugin/notes/notes.js +++ b/plugin/notes/notes.js @@ -1,6 +1,13 @@ /** * Handles opening of and synchronization with the reveal.js * notes window. + * + * Handshake process: + * 1. This window posts 'connect' to notes window + * - Includes URL of presentation to show + * 2. Notes window responds with 'connected' when it is available + * 3. This window proceeds to send the current presentation state + * to the notes window */ var RevealNotes = (function() { @@ -9,41 +16,46 @@ var RevealNotes = (function() { jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path var notesPopup = window.open( jsFileLocation + 'notes.html', 'reveal.js - Notes', 'width=1120,height=850' ); - // Fires when slide is changed - Reveal.addEventListener( 'slidechanged', post ); - - // Fires when a fragment is shown - Reveal.addEventListener( 'fragmentshown', post ); + /** + * Connect to the notes window through a postmessage handshake. + * Using postmessage enables us to work in situations where the + * origins differ, such as a presentation being opened from the + * file system. + */ + function connect() { + // Keep trying to connect until we get a 'connected' message back + var connectInterval = setInterval( function() { + notesPopup.postMessage( JSON.stringify( { + namespace: 'reveal-notes', + type: 'connect', + url: window.location.protocol + '//' + window.location.host + window.location.pathname, + state: Reveal.getState() + } ), '*' ); + }, 500 ); - // Fires when a fragment is hidden - Reveal.addEventListener( 'fragmenthidden', post ); + window.addEventListener( 'message', function( event ) { + var data = JSON.parse( event.data ); + if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) { + clearInterval( connectInterval ); + onConnected(); + } + } ); + } /** * Posts the current slide data to the notes window */ function post() { + var slideElement = Reveal.getCurrentSlide(), - slideIndices = Reveal.getIndices(), - notesElement = slideElement.querySelector( 'aside.notes' ), - nextindexh, - nextindexv; - - if( slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION' ) { - nextindexh = slideIndices.h; - nextindexv = slideIndices.v + 1; - } else { - nextindexh = slideIndices.h + 1; - nextindexv = 0; - } + notesElement = slideElement.querySelector( 'aside.notes' ); var messageData = { - notes : '', - indexh : slideIndices.h, - indexv : slideIndices.v, - indexf : slideIndices.f, - nextindexh : nextindexh, - nextindexv : nextindexv, - markdown : false + namespace: 'reveal-notes', + type: 'state', + notes: '', + markdown: false, + state: Reveal.getState() }; // Look for notes defined in a slide attribute @@ -58,12 +70,30 @@ var RevealNotes = (function() { } notesPopup.postMessage( JSON.stringify( messageData ), '*' ); + } - // Navigate to the current slide when the notes are loaded - notesPopup.addEventListener( 'load', function( event ) { + /** + * Called once we have established a connection to the notes + * window. + */ + function onConnected() { + + // Monitor events that trigger a change in state + Reveal.addEventListener( 'slidechanged', post ); + Reveal.addEventListener( 'fragmentshown', post ); + Reveal.addEventListener( 'fragmenthidden', post ); + Reveal.addEventListener( 'overviewhidden', post ); + Reveal.addEventListener( 'overviewshown', post ); + Reveal.addEventListener( 'paused', post ); + Reveal.addEventListener( 'resumed', post ); + + // Post the initial state post(); - }, false ); + + } + + connect(); } // If the there's a 'notes' query set, open directly |