notes plugin now operates entirely through window.postMessage, adding support for file protocol
This commit is contained in:
		| @@ -82,6 +82,7 @@ | |||||||
| 				left: 3px; | 				left: 3px; | ||||||
| 				font-weight: bold; | 				font-weight: bold; | ||||||
| 				font-size: 14px; | 				font-size: 14px; | ||||||
|  | 				z-index: 2; | ||||||
| 				color: rgba( 255, 255, 255, 0.9 ); | 				color: rgba( 255, 255, 255, 0.9 ); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -138,22 +139,8 @@ | |||||||
|  |  | ||||||
| 	<body> | 	<body> | ||||||
|  |  | ||||||
| 		<script> | 		<div id="wrap-current-slide" class="slides"></div> | ||||||
| 			function getNotesURL( controls ) { | 		<div id="wrap-next-slide" class="slides"><span>UPCOMING:</span></div> | ||||||
| 				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 class="time"> | 		<div class="time"> | ||||||
| 			<div class="clock"> | 			<div class="clock"> | ||||||
| @@ -171,37 +158,112 @@ | |||||||
| 		<script src="../../plugin/markdown/marked.js"></script> | 		<script src="../../plugin/markdown/marked.js"></script> | ||||||
| 		<script> | 		<script> | ||||||
|  |  | ||||||
| 			window.addEventListener( 'load', function() { | 			(function() { | ||||||
|  |  | ||||||
| 				if( window.opener && window.opener.location && window.opener.location.href ) { | 				var notes, | ||||||
|  | 					currentState, | ||||||
|  | 					currentSlide, | ||||||
|  | 					nextSlide, | ||||||
|  | 					connected = false; | ||||||
|  |  | ||||||
| 					var notes = document.getElementById( 'notes' ), | 				window.addEventListener( 'message', function( event ) { | ||||||
| 						currentSlide = document.getElementById( 'current-slide' ), |  | ||||||
| 						nextSlide = document.getElementById( 'next-slide' ), |  | ||||||
| 						silenced = false; |  | ||||||
|  |  | ||||||
| 					window.addEventListener( 'message', function( event ) { | 					var data = JSON.parse( event.data ); | ||||||
| 						var data = JSON.parse( event.data ); |  | ||||||
|  |  | ||||||
| 						// No need for updating the notes in case of fragment changes | 					// Messages sent by the notes plugin inside of the main window | ||||||
| 						if ( data.notes !== undefined) { | 					if( data && data.namespace === 'reveal-notes' ) { | ||||||
| 							if( data.markdown ) { | 						if( data.type === 'connect' ) { | ||||||
| 								notes.innerHTML = marked( data.notes ); | 							handleConnectMessage( data ); | ||||||
| 							} |  | ||||||
| 							else { |  | ||||||
| 								notes.innerHTML = data.notes; |  | ||||||
| 							} |  | ||||||
| 						} | 						} | ||||||
|  | 						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 ]} ), '*' ); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
| 						silenced = true; | 				} ); | ||||||
|  |  | ||||||
| 						// Update the note slides | 				/** | ||||||
| 						currentSlide.contentWindow.Reveal.slide( data.indexh, data.indexv, data.indexf ); | 				 * Called when the main window is trying to establish a | ||||||
| 						nextSlide.contentWindow.Reveal.slide( data.nextindexh, data.nextindexv ); | 				 * connection. | ||||||
|  | 				 */ | ||||||
|  | 				function handleConnectMessage( data ) { | ||||||
|  |  | ||||||
| 						silenced = false; | 					if( connected === false ) { | ||||||
|  | 						connected = true; | ||||||
|  |  | ||||||
| 					}, false ); | 						setupIframes( data ); | ||||||
|  | 						setupTimer(); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				/** | ||||||
|  | 				 * Called when the main window sends an updated state. | ||||||
|  | 				 */ | ||||||
|  | 				function handleStateMessage( 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; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// 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' }), '*' ); | ||||||
|  |  | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				/** | ||||||
|  | 				 * Creates the preview iframes. | ||||||
|  | 				 */ | ||||||
|  | 				function setupIframes( data ) { | ||||||
|  |  | ||||||
|  | 					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 ); | ||||||
|  |  | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				/** | ||||||
|  | 				 * Create the timer and clock and start updating them | ||||||
|  | 				 * at an interval. | ||||||
|  | 				 */ | ||||||
|  | 				function setupTimer() { | ||||||
|  |  | ||||||
| 					var start = new Date(), | 					var start = new Date(), | ||||||
| 						timeEl = document.querySelector( '.time' ), | 						timeEl = document.querySelector( '.time' ), | ||||||
| @@ -224,43 +286,23 @@ | |||||||
|  |  | ||||||
| 						clockEl.innerHTML = now.toLocaleTimeString(); | 						clockEl.innerHTML = now.toLocaleTimeString(); | ||||||
| 						hoursEl.innerHTML = zeroPadInteger( hours ); | 						hoursEl.innerHTML = zeroPadInteger( hours ); | ||||||
| 						hoursEl.className = hours > 0 ? "" : "mute"; | 						hoursEl.className = hours > 0 ? '' : 'mute'; | ||||||
| 						minutesEl.innerHTML = ":" + zeroPadInteger( minutes ); | 						minutesEl.innerHTML = ':' + zeroPadInteger( minutes ); | ||||||
| 						minutesEl.className = minutes > 0 ? "" : "mute"; | 						minutesEl.className = minutes > 0 ? '' : 'mute'; | ||||||
| 						secondsEl.innerHTML = ":" + zeroPadInteger( seconds ); | 						secondsEl.innerHTML = ':' + zeroPadInteger( seconds ); | ||||||
|  |  | ||||||
| 					}, 1000 ); | 					}, 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> | 		</script> | ||||||
| 	</body> | 	</body> | ||||||
|   | |||||||
| @@ -1,6 +1,13 @@ | |||||||
| /** | /** | ||||||
|  * Handles opening of and synchronization with the reveal.js |  * Handles opening of and synchronization with the reveal.js | ||||||
|  * notes window. |  * 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() { | var RevealNotes = (function() { | ||||||
|  |  | ||||||
| @@ -9,41 +16,46 @@ var RevealNotes = (function() { | |||||||
| 		jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, '');   // the js folder path | 		jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, '');   // the js folder path | ||||||
| 		var notesPopup = window.open( jsFileLocation + 'notes.html', 'reveal.js - Notes', 'width=1120,height=850' ); | 		var notesPopup = window.open( jsFileLocation + 'notes.html', 'reveal.js - Notes', 'width=1120,height=850' ); | ||||||
|  |  | ||||||
| 		// Fires when slide is changed | 		/** | ||||||
| 		Reveal.addEventListener( 'slidechanged', 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 shown | 			window.addEventListener( 'message', function( event ) { | ||||||
| 		Reveal.addEventListener( 'fragmentshown', post ); | 				var data = JSON.parse( event.data ); | ||||||
|  | 				if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) { | ||||||
| 		// Fires when a fragment is hidden | 					clearInterval( connectInterval ); | ||||||
| 		Reveal.addEventListener( 'fragmenthidden', post ); | 					onConnected(); | ||||||
|  | 				} | ||||||
|  | 			} ); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		/** | 		/** | ||||||
| 		 * Posts the current slide data to the notes window | 		 * Posts the current slide data to the notes window | ||||||
| 		 */ | 		 */ | ||||||
| 		function post() { | 		function post() { | ||||||
| 			var slideElement = Reveal.getCurrentSlide(), |  | ||||||
| 				slideIndices = Reveal.getIndices(), |  | ||||||
| 				notesElement = slideElement.querySelector( 'aside.notes' ), |  | ||||||
| 				nextindexh, |  | ||||||
| 				nextindexv; |  | ||||||
|  |  | ||||||
| 			if( slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION' ) { | 			var slideElement = Reveal.getCurrentSlide(), | ||||||
| 				nextindexh = slideIndices.h; | 				notesElement = slideElement.querySelector( 'aside.notes' ); | ||||||
| 				nextindexv = slideIndices.v + 1; |  | ||||||
| 			} else { |  | ||||||
| 				nextindexh = slideIndices.h + 1; |  | ||||||
| 				nextindexv = 0; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var messageData = { | 			var messageData = { | ||||||
| 				notes : '', | 				namespace: 'reveal-notes', | ||||||
| 				indexh : slideIndices.h, | 				type: 'state', | ||||||
| 				indexv : slideIndices.v, | 				notes: '', | ||||||
| 				indexf : slideIndices.f, | 				markdown: false, | ||||||
| 				nextindexh : nextindexh, | 				state: Reveal.getState() | ||||||
| 				nextindexv : nextindexv, |  | ||||||
| 				markdown : false |  | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			// Look for notes defined in a slide attribute | 			// Look for notes defined in a slide attribute | ||||||
| @@ -58,12 +70,30 @@ var RevealNotes = (function() { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			notesPopup.postMessage( JSON.stringify( messageData ), '*' ); | 			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(); | 			post(); | ||||||
| 		}, false ); |  | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		connect(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// If the there's a 'notes' query set, open directly | 	// If the there's a 'notes' query set, open directly | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user