github.com/mweagle/Sparta@v1.15.0/docs_source/static/presentations/reveal.js-3.9.2/plugin/notes-server/notes.html (about) 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>