浏览代码

notes plugin now operates entirely through window.postMessage, adding support for file protocol

Hakim El Hattab 11 年之前
父节点
当前提交
5b18c1f308
共有 2 个文件被更改,包括 168 次插入96 次删除
  1. 109 67
      plugin/notes/notes.html
  2. 59 29
      plugin/notes/notes.js

+ 109 - 67
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>

+ 59 - 29
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