presentations

Presentations
Log | Files | Refs

notes.html (13582B)


      1 <!doctype html>
      2 <html lang="en">
      3 	<head>
      4 		<meta charset="utf-8">
      5 
      6 		<title>reveal.js - Slide Notes</title>
      7 
      8 		<style>
      9 			body {
     10 				font-family: Helvetica;
     11 				font-size: 18px;
     12 			}
     13 
     14 			#current-slide,
     15 			#upcoming-slide,
     16 			#speaker-controls {
     17 				padding: 6px;
     18 				box-sizing: border-box;
     19 				-moz-box-sizing: border-box;
     20 			}
     21 
     22 			#current-slide iframe,
     23 			#upcoming-slide iframe {
     24 				width: 100%;
     25 				height: 100%;
     26 				border: 1px solid #ddd;
     27 			}
     28 
     29 			#current-slide .label,
     30 			#upcoming-slide .label {
     31 				position: absolute;
     32 				top: 10px;
     33 				left: 10px;
     34 				z-index: 2;
     35 			}
     36 
     37 			.overlay-element {
     38 				height: 34px;
     39 				line-height: 34px;
     40 				padding: 0 10px;
     41 				text-shadow: none;
     42 				background: rgba( 220, 220, 220, 0.8 );
     43 				color: #222;
     44 				font-size: 14px;
     45 			}
     46 
     47 			.overlay-element.interactive:hover {
     48 				background: rgba( 220, 220, 220, 1 );
     49 			}
     50 
     51 			#current-slide {
     52 				position: absolute;
     53 				width: 60%;
     54 				height: 100%;
     55 				top: 0;
     56 				left: 0;
     57 				padding-right: 0;
     58 			}
     59 
     60 			#upcoming-slide {
     61 				position: absolute;
     62 				width: 40%;
     63 				height: 40%;
     64 				right: 0;
     65 				top: 0;
     66 			}
     67 
     68 			/* Speaker controls */
     69 			#speaker-controls {
     70 				position: absolute;
     71 				top: 40%;
     72 				right: 0;
     73 				width: 40%;
     74 				height: 60%;
     75 				overflow: auto;
     76 				font-size: 18px;
     77 			}
     78 
     79 				.speaker-controls-time.hidden,
     80 				.speaker-controls-notes.hidden {
     81 					display: none;
     82 				}
     83 
     84 				.speaker-controls-time .label,
     85 				.speaker-controls-notes .label {
     86 					text-transform: uppercase;
     87 					font-weight: normal;
     88 					font-size: 0.66em;
     89 					color: #666;
     90 					margin: 0;
     91 				}
     92 
     93 				.speaker-controls-time {
     94 					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
     95 					margin-bottom: 10px;
     96 					padding: 10px 16px;
     97 					padding-bottom: 20px;
     98 					cursor: pointer;
     99 				}
    100 
    101 				.speaker-controls-time .reset-button {
    102 					opacity: 0;
    103 					float: right;
    104 					color: #666;
    105 					text-decoration: none;
    106 				}
    107 				.speaker-controls-time:hover .reset-button {
    108 					opacity: 1;
    109 				}
    110 
    111 				.speaker-controls-time .timer,
    112 				.speaker-controls-time .clock {
    113 					width: 50%;
    114 					font-size: 1.9em;
    115 				}
    116 
    117 				.speaker-controls-time .timer {
    118 					float: left;
    119 				}
    120 
    121 				.speaker-controls-time .clock {
    122 					float: right;
    123 					text-align: right;
    124 				}
    125 
    126 				.speaker-controls-time span.mute {
    127 					color: #bbb;
    128 				}
    129 
    130 				.speaker-controls-notes {
    131 					padding: 10px 16px;
    132 				}
    133 
    134 				.speaker-controls-notes .value {
    135 					margin-top: 5px;
    136 					line-height: 1.4;
    137 					font-size: 1.2em;
    138 				}
    139 
    140 			/* Layout selector */
    141 			#speaker-layout {
    142 				position: absolute;
    143 				top: 10px;
    144 				right: 10px;
    145 				color: #222;
    146 				z-index: 10;
    147 			}
    148 				#speaker-layout select {
    149 					position: absolute;
    150 					width: 100%;
    151 					height: 100%;
    152 					top: 0;
    153 					left: 0;
    154 					border: 0;
    155 					box-shadow: 0;
    156 					cursor: pointer;
    157 					opacity: 0;
    158 
    159 					font-size: 1em;
    160 					background-color: transparent;
    161 
    162 					-moz-appearance: none;
    163 					-webkit-appearance: none;
    164 					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
    165 				}
    166 
    167 				#speaker-layout select:focus {
    168 					outline: none;
    169 					box-shadow: none;
    170 				}
    171 
    172 			.clear {
    173 				clear: both;
    174 			}
    175 
    176 			/* Speaker layout: Wide */
    177 			body[data-speaker-layout="wide"] #current-slide,
    178 			body[data-speaker-layout="wide"] #upcoming-slide {
    179 				width: 50%;
    180 				height: 45%;
    181 				padding: 6px;
    182 			}
    183 
    184 			body[data-speaker-layout="wide"] #current-slide {
    185 				top: 0;
    186 				left: 0;
    187 			}
    188 
    189 			body[data-speaker-layout="wide"] #upcoming-slide {
    190 				top: 0;
    191 				left: 50%;
    192 			}
    193 
    194 			body[data-speaker-layout="wide"] #speaker-controls {
    195 				top: 45%;
    196 				left: 0;
    197 				width: 100%;
    198 				height: 50%;
    199 				font-size: 1.25em;
    200 			}
    201 
    202 			/* Speaker layout: Tall */
    203 			body[data-speaker-layout="tall"] #current-slide,
    204 			body[data-speaker-layout="tall"] #upcoming-slide {
    205 				width: 45%;
    206 				height: 50%;
    207 				padding: 6px;
    208 			}
    209 
    210 			body[data-speaker-layout="tall"] #current-slide {
    211 				top: 0;
    212 				left: 0;
    213 			}
    214 
    215 			body[data-speaker-layout="tall"] #upcoming-slide {
    216 				top: 50%;
    217 				left: 0;
    218 			}
    219 
    220 			body[data-speaker-layout="tall"] #speaker-controls {
    221 				padding-top: 40px;
    222 				top: 0;
    223 				left: 45%;
    224 				width: 55%;
    225 				height: 100%;
    226 				font-size: 1.25em;
    227 			}
    228 
    229 			/* Speaker layout: Notes only */
    230 			body[data-speaker-layout="notes-only"] #current-slide,
    231 			body[data-speaker-layout="notes-only"] #upcoming-slide {
    232 				display: none;
    233 			}
    234 
    235 			body[data-speaker-layout="notes-only"] #speaker-controls {
    236 				padding-top: 40px;
    237 				top: 0;
    238 				left: 0;
    239 				width: 100%;
    240 				height: 100%;
    241 				font-size: 1.25em;
    242 			}
    243 
    244 		</style>
    245 	</head>
    246 
    247 	<body>
    248 
    249 		<div id="current-slide"></div>
    250 		<div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
    251 		<div id="speaker-controls">
    252 			<div class="speaker-controls-time">
    253 				<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
    254 				<div class="clock">
    255 					<span class="clock-value">0:00 AM</span>
    256 				</div>
    257 				<div class="timer">
    258 					<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
    259 				</div>
    260 				<div class="clear"></div>
    261 			</div>
    262 
    263 			<div class="speaker-controls-notes hidden">
    264 				<h4 class="label">Notes</h4>
    265 				<div class="value"></div>
    266 			</div>
    267 		</div>
    268 		<div id="speaker-layout" class="overlay-element interactive">
    269 			<span class="speaker-layout-label"></span>
    270 			<select class="speaker-layout-dropdown"></select>
    271 		</div>
    272 
    273 		<script src="/socket.io/socket.io.js"></script>
    274 		<script src="/plugin/markdown/marked.js"></script>
    275 
    276 		<script>
    277 		(function() {
    278 
    279 			var notes,
    280 				notesValue,
    281 				currentState,
    282 				currentSlide,
    283 				upcomingSlide,
    284 				layoutLabel,
    285 				layoutDropdown,
    286 				connected = false;
    287 
    288 			var socket = io.connect( window.location.origin ),
    289 				socketId = '{{socketId}}';
    290 
    291 			var SPEAKER_LAYOUTS = {
    292 				'default': 'Default',
    293 				'wide': 'Wide',
    294 				'tall': 'Tall',
    295 				'notes-only': 'Notes only'
    296 			};
    297 
    298 			socket.on( 'statechanged', function( data ) {
    299 
    300 				// ignore data from sockets that aren't ours
    301 				if( data.socketId !== socketId ) { return; }
    302 
    303 				if( connected === false ) {
    304 					connected = true;
    305 
    306 					setupKeyboard();
    307 					setupNotes();
    308 					setupTimer();
    309 
    310 				}
    311 
    312 				handleStateMessage( data );
    313 
    314 			} );
    315 
    316 			setupLayout();
    317 
    318 			// Load our presentation iframes
    319 			setupIframes();
    320 
    321 			// Once the iframes have loaded, emit a signal saying there's
    322 			// a new subscriber which will trigger a 'statechanged'
    323 			// message to be sent back
    324 			window.addEventListener( 'message', function( event ) {
    325 
    326 				var data = JSON.parse( event.data );
    327 
    328 				if( data && data.namespace === 'reveal' ) {
    329 					if( /ready/.test( data.eventName ) ) {
    330 						socket.emit( 'new-subscriber', { socketId: socketId } );
    331 					}
    332 				}
    333 
    334 				// Messages sent by reveal.js inside of the current slide preview
    335 				if( data && data.namespace === 'reveal' ) {
    336 					if( /slidechanged|fragmentshown|fragmenthidden|overviewshown|overviewhidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
    337 						socket.emit( 'statechanged-speaker', { state: data.state } );
    338 					}
    339 				}
    340 
    341 			} );
    342 
    343 			/**
    344 			 * Called when the main window sends an updated state.
    345 			 */
    346 			function handleStateMessage( data ) {
    347 
    348 				// Store the most recently set state to avoid circular loops
    349 				// applying the same state
    350 				currentState = JSON.stringify( data.state );
    351 
    352 				// No need for updating the notes in case of fragment changes
    353 				if ( data.notes ) {
    354 					notes.classList.remove( 'hidden' );
    355 					if( data.markdown ) {
    356 						notesValue.innerHTML = marked( data.notes );
    357 					}
    358 					else {
    359 						notesValue.innerHTML = data.notes;
    360 					}
    361 				}
    362 				else {
    363 					notes.classList.add( 'hidden' );
    364 				}
    365 
    366 				// Update the note slides
    367 				currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
    368 				upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
    369 				upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
    370 
    371 			}
    372 
    373 			// Limit to max one state update per X ms
    374 			handleStateMessage = debounce( handleStateMessage, 200 );
    375 
    376 			/**
    377 			 * Forward keyboard events to the current slide window.
    378 			 * This enables keyboard events to work even if focus
    379 			 * isn't set on the current slide iframe.
    380 			 */
    381 			function setupKeyboard() {
    382 
    383 				document.addEventListener( 'keydown', function( event ) {
    384 					currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
    385 				} );
    386 
    387 			}
    388 
    389 			/**
    390 			 * Creates the preview iframes.
    391 			 */
    392 			function setupIframes() {
    393 
    394 				var params = [
    395 					'receiver',
    396 					'progress=false',
    397 					'history=false',
    398 					'transition=none',
    399 					'backgroundTransition=none'
    400 				].join( '&' );
    401 
    402 				var currentURL = '/?' + params + '&postMessageEvents=true';
    403 				var upcomingURL = '/?' + params + '&controls=false';
    404 
    405 				currentSlide = document.createElement( 'iframe' );
    406 				currentSlide.setAttribute( 'width', 1280 );
    407 				currentSlide.setAttribute( 'height', 1024 );
    408 				currentSlide.setAttribute( 'src', currentURL );
    409 				document.querySelector( '#current-slide' ).appendChild( currentSlide );
    410 
    411 				upcomingSlide = document.createElement( 'iframe' );
    412 				upcomingSlide.setAttribute( 'width', 640 );
    413 				upcomingSlide.setAttribute( 'height', 512 );
    414 				upcomingSlide.setAttribute( 'src', upcomingURL );
    415 				document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
    416 
    417 			}
    418 
    419 			/**
    420 			 * Setup the notes UI.
    421 			 */
    422 			function setupNotes() {
    423 
    424 				notes = document.querySelector( '.speaker-controls-notes' );
    425 				notesValue = document.querySelector( '.speaker-controls-notes .value' );
    426 
    427 			}
    428 
    429 			/**
    430 			 * Create the timer and clock and start updating them
    431 			 * at an interval.
    432 			 */
    433 			function setupTimer() {
    434 
    435 				var start = new Date(),
    436 					timeEl = document.querySelector( '.speaker-controls-time' ),
    437 					clockEl = timeEl.querySelector( '.clock-value' ),
    438 					hoursEl = timeEl.querySelector( '.hours-value' ),
    439 					minutesEl = timeEl.querySelector( '.minutes-value' ),
    440 					secondsEl = timeEl.querySelector( '.seconds-value' );
    441 
    442 				function _updateTimer() {
    443 
    444 					var diff, hours, minutes, seconds,
    445 						now = new Date();
    446 
    447 					diff = now.getTime() - start.getTime();
    448 					hours = Math.floor( diff / ( 1000 * 60 * 60 ) );
    449 					minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 );
    450 					seconds = Math.floor( ( diff / 1000 ) % 60 );
    451 
    452 					clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
    453 					hoursEl.innerHTML = zeroPadInteger( hours );
    454 					hoursEl.className = hours > 0 ? '' : 'mute';
    455 					minutesEl.innerHTML = ':' + zeroPadInteger( minutes );
    456 					minutesEl.className = minutes > 0 ? '' : 'mute';
    457 					secondsEl.innerHTML = ':' + zeroPadInteger( seconds );
    458 
    459 				}
    460 
    461 				// Update once directly
    462 				_updateTimer();
    463 
    464 				// Then update every second
    465 				setInterval( _updateTimer, 1000 );
    466 
    467 				timeEl.addEventListener( 'click', function() {
    468 					start = new Date();
    469 					_updateTimer();
    470 					return false;
    471 				} );
    472 
    473 			}
    474 
    475 			/**
    476 				 * Sets up the speaker view layout and layout selector.
    477 				 */
    478 				function setupLayout() {
    479 
    480 					layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
    481 					layoutLabel = document.querySelector( '.speaker-layout-label' );
    482 
    483 					// Render the list of available layouts
    484 					for( var id in SPEAKER_LAYOUTS ) {
    485 						var option = document.createElement( 'option' );
    486 						option.setAttribute( 'value', id );
    487 						option.textContent = SPEAKER_LAYOUTS[ id ];
    488 						layoutDropdown.appendChild( option );
    489 					}
    490 
    491 					// Monitor the dropdown for changes
    492 					layoutDropdown.addEventListener( 'change', function( event ) {
    493 
    494 						setLayout( layoutDropdown.value );
    495 
    496 					}, false );
    497 
    498 					// Restore any currently persisted layout
    499 					setLayout( getLayout() );
    500 
    501 				}
    502 
    503 				/**
    504 				 * Sets a new speaker view layout. The layout is persisted
    505 				 * in local storage.
    506 				 */
    507 				function setLayout( value ) {
    508 
    509 					var title = SPEAKER_LAYOUTS[ value ];
    510 
    511 					layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
    512 					layoutDropdown.value = value;
    513 
    514 					document.body.setAttribute( 'data-speaker-layout', value );
    515 
    516 					// Persist locally
    517 					if( window.localStorage ) {
    518 						window.localStorage.setItem( 'reveal-speaker-layout', value );
    519 					}
    520 
    521 				}
    522 
    523 				/**
    524 				 * Returns the ID of the most recently set speaker layout
    525 				 * or our default layout if none has been set.
    526 				 */
    527 				function getLayout() {
    528 
    529 					if( window.localStorage ) {
    530 						var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
    531 						if( layout ) {
    532 							return layout;
    533 						}
    534 					}
    535 
    536 					// Default to the first record in the layouts hash
    537 					for( var id in SPEAKER_LAYOUTS ) {
    538 						return id;
    539 					}
    540 
    541 				}
    542 
    543 			function zeroPadInteger( num ) {
    544 
    545 				var str = '00' + parseInt( num );
    546 				return str.substring( str.length - 2 );
    547 
    548 			}
    549 
    550 			/**
    551 			 * Limits the frequency at which a function can be called.
    552 			 */
    553 			function debounce( fn, ms ) {
    554 
    555 				var lastTime = 0,
    556 					timeout;
    557 
    558 				return function() {
    559 
    560 					var args = arguments;
    561 					var context = this;
    562 
    563 					clearTimeout( timeout );
    564 
    565 					var timeSinceLastCall = Date.now() - lastTime;
    566 					if( timeSinceLastCall > ms ) {
    567 						fn.apply( context, args );
    568 						lastTime = Date.now();
    569 					}
    570 					else {
    571 						timeout = setTimeout( function() {
    572 							fn.apply( context, args );
    573 							lastTime = Date.now();
    574 						}, ms - timeSinceLastCall );
    575 					}
    576 
    577 				}
    578 
    579 			}
    580 
    581 		})();
    582 		</script>
    583 
    584 	</body>
    585 </html>