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>