| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 | <!doctype html><html lang="en">	<head>		<meta charset="utf-8">		<title>reveal.js - Slide Notes</title>		<style>			body {				font-family: Helvetica;			}			#current-slide,			#upcoming-slide,			#speaker-controls {				padding: 6px;				box-sizing: border-box;				-moz-box-sizing: border-box;			}			#current-slide iframe,			#upcoming-slide iframe {				width: 100%;				height: 100%;				border: 1px solid #ddd;			}			#current-slide .label,			#upcoming-slide .label {				position: absolute;				top: 10px;				left: 10px;				font-weight: bold;				font-size: 14px;				z-index: 2;				color: rgba( 255, 255, 255, 0.9 );			}			#current-slide {				position: absolute;				width: 65%;				height: 100%;				top: 0;				left: 0;				padding-right: 0;			}			#upcoming-slide {				position: absolute;				width: 35%;				height: 40%;				right: 0;				top: 0;			}			#speaker-controls {				position: absolute;				top: 40%;				right: 0;				width: 35%;				height: 60%;				font-size: 18px;			}				.speaker-controls-time.hidden,				.speaker-controls-notes.hidden {					display: none;				}				.speaker-controls-time .label,				.speaker-controls-notes .label {					text-transform: uppercase;					font-weight: normal;					font-size: 0.66em;					color: #666;					margin: 0;				}				.speaker-controls-time {					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );					margin-bottom: 10px;					padding: 10px 16px;					padding-bottom: 20px;					cursor: pointer;				}				.speaker-controls-time .reset-button {					opacity: 0;					float: right;					color: #666;					text-decoration: none;				}				.speaker-controls-time:hover .reset-button {					opacity: 1;				}				.speaker-controls-time .timer,				.speaker-controls-time .clock {					width: 50%;					font-size: 1.9em;				}				.speaker-controls-time .timer {					float: left;				}				.speaker-controls-time .clock {					float: right;					text-align: right;				}				.speaker-controls-time span.mute {					color: #bbb;				}				.speaker-controls-notes {					padding: 10px 16px;				}				.speaker-controls-notes .value {					margin-top: 5px;					line-height: 1.4;					font-size: 1.2em;				}			.clear {				clear: both;			}			@media screen and (max-width: 1080px) {				#speaker-controls {					font-size: 16px;				}			}			@media screen and (max-width: 900px) {				#speaker-controls {					font-size: 14px;				}			}			@media screen and (max-width: 800px) {				#speaker-controls {					font-size: 12px;				}			}		</style>	</head>	<body>		<div id="current-slide"></div>		<div id="upcoming-slide"><span class="label">UPCOMING:</span></div>		<div id="speaker-controls">			<div class="speaker-controls-time">				<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>				<div class="clock">					<span class="clock-value">0:00 AM</span>				</div>				<div class="timer">					<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>				</div>				<div class="clear"></div>			</div>			<div class="speaker-controls-notes hidden">				<h4 class="label">Notes</h4>				<div class="value"></div>			</div>		</div>		<script src="/socket.io/socket.io.js"></script>		<script src="/plugin/markdown/marked.js"></script>		<script>		(function() {			var notes,				notesValue,				currentState,				currentSlide,				upcomingSlide,				connected = false;			var socket = io.connect( window.location.origin ),				socketId = '{{socketId}}';			socket.on( 'state', function( data ) {				// ignore data from sockets that aren't ours				if( data.socketId !== socketId ) { return; }				if( connected === false ) {					connected = true;					setupIframes( data );					setupKeyboard();					setupNotes();					setupTimer();				}				handleStateMessage( data );			} );			/**			 * 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 ) {					notes.classList.remove( 'hidden' );					if( data.markdown ) {						notesValue.innerHTML = marked( data.notes );					}					else {						notesValue.innerHTML = data.notes;					}				}				else {					notes.classList.add( 'hidden' );				}				// Update the note slides				currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );				upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );				upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );			}			// Limit to max one state update per X ms			handleStateMessage = debounce( handleStateMessage, 200 );			/**			 * Forward keyboard events to the current slide window.			 * This enables keyboard events to work even if focus			 * isn't set on the current slide iframe.			 */			function setupKeyboard() {				document.addEventListener( 'keydown', function( event ) {					currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );				} );			}			/**			 * Creates the preview iframes.			 */			function setupIframes( data ) {				var params = [					'receiver',					'progress=false',					'history=false',					'transition=none',					'backgroundTransition=none'				].join( '&' );				var hash = '#/' + data.state.indexh + '/' + data.state.indexv;				var currentURL = '/?' + params + hash;				var upcomingURL = '/?' + params + '&controls=false' + hash;				currentSlide = document.createElement( 'iframe' );				currentSlide.setAttribute( 'width', 1280 );				currentSlide.setAttribute( 'height', 1024 );				currentSlide.setAttribute( 'src', currentURL );				document.querySelector( '#current-slide' ).appendChild( currentSlide );				upcomingSlide = document.createElement( 'iframe' );				upcomingSlide.setAttribute( 'width', 640 );				upcomingSlide.setAttribute( 'height', 512 );				upcomingSlide.setAttribute( 'src', upcomingURL );				document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );			}			/**			 * Setup the notes UI.			 */			function setupNotes() {				notes = document.querySelector( '.speaker-controls-notes' );				notesValue = document.querySelector( '.speaker-controls-notes .value' );			}			/**			 * Create the timer and clock and start updating them			 * at an interval.			 */			function setupTimer() {				var start = new Date(),					timeEl = document.querySelector( '.speaker-controls-time' ),					clockEl = timeEl.querySelector( '.clock-value' ),					hoursEl = timeEl.querySelector( '.hours-value' ),					minutesEl = timeEl.querySelector( '.minutes-value' ),					secondsEl = timeEl.querySelector( '.seconds-value' );				function _updateTimer() {					var diff, hours, minutes, seconds,						now = new Date();					diff = now.getTime() - start.getTime();					hours = Math.floor( diff / ( 1000 * 60 * 60 ) );					minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 );					seconds = Math.floor( ( diff / 1000 ) % 60 );					clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );					hoursEl.innerHTML = zeroPadInteger( hours );					hoursEl.className = hours > 0 ? '' : 'mute';					minutesEl.innerHTML = ':' + zeroPadInteger( minutes );					minutesEl.className = minutes > 0 ? '' : 'mute';					secondsEl.innerHTML = ':' + zeroPadInteger( seconds );				}				// Update once directly				_updateTimer();				// Then update every second				setInterval( _updateTimer, 1000 );				timeEl.addEventListener( 'click', function() {					start = new Date();					_updateTimer();					return false;				} );			}			function zeroPadInteger( num ) {				var str = '00' + parseInt( num );				return str.substring( str.length - 2 );			}			/**			 * Limits the frequency at which a function can be called.			 */			function debounce( fn, ms ) {				var lastTime = 0,					timeout;				return function() {					var args = arguments;					var context = this;					clearTimeout( timeout );					var timeSinceLastCall = Date.now() - lastTime;					if( timeSinceLastCall > ms ) {						fn.apply( context, args );						lastTime = Date.now();					}					else {						timeout = setTimeout( function() {							fn.apply( context, args );							lastTime = Date.now();						}, ms - timeSinceLastCall );					}				}			}		})();		</script>	</body></html>
 |