v1
This commit is contained in:
		
							
								
								
									
										77
									
								
								plugin/highlight/highlight.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								plugin/highlight/highlight.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										136
									
								
								plugin/markdown/example.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								plugin/markdown/example.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta charset="utf-8">
 | 
			
		||||
 | 
			
		||||
		<title>reveal.js - Markdown Demo</title>
 | 
			
		||||
 | 
			
		||||
		<link rel="stylesheet" href="../../css/reveal.css">
 | 
			
		||||
		<link rel="stylesheet" href="../../css/theme/white.css" id="theme">
 | 
			
		||||
 | 
			
		||||
        <link rel="stylesheet" href="../../lib/css/zenburn.css">
 | 
			
		||||
	</head>
 | 
			
		||||
 | 
			
		||||
	<body>
 | 
			
		||||
 | 
			
		||||
		<div class="reveal">
 | 
			
		||||
 | 
			
		||||
			<div class="slides">
 | 
			
		||||
 | 
			
		||||
                <!-- Use external markdown resource, separate slides by three newlines; vertical slides by two newlines -->
 | 
			
		||||
                <section data-markdown="example.md" data-separator="^\n\n\n" data-separator-vertical="^\n\n"></section>
 | 
			
		||||
 | 
			
		||||
                <!-- Slides are separated by three dashes (quick 'n dirty regular expression) -->
 | 
			
		||||
                <section data-markdown data-separator="---">
 | 
			
		||||
                    <script type="text/template">
 | 
			
		||||
                        ## Demo 1
 | 
			
		||||
                        Slide 1
 | 
			
		||||
                        ---
 | 
			
		||||
                        ## Demo 1
 | 
			
		||||
                        Slide 2
 | 
			
		||||
                        ---
 | 
			
		||||
                        ## Demo 1
 | 
			
		||||
                        Slide 3
 | 
			
		||||
                    </script>
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
                <!-- Slides are separated by newline + three dashes + newline, vertical slides identical but two dashes -->
 | 
			
		||||
                <section data-markdown data-separator="^\n---\n$" data-separator-vertical="^\n--\n$">
 | 
			
		||||
                    <script type="text/template">
 | 
			
		||||
                        ## Demo 2
 | 
			
		||||
                        Slide 1.1
 | 
			
		||||
 | 
			
		||||
                        --
 | 
			
		||||
 | 
			
		||||
                        ## Demo 2
 | 
			
		||||
                        Slide 1.2
 | 
			
		||||
 | 
			
		||||
                        ---
 | 
			
		||||
 | 
			
		||||
                        ## Demo 2
 | 
			
		||||
                        Slide 2
 | 
			
		||||
                    </script>
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
                <!-- No "extra" slides, since there are no separators defined (so they'll become horizontal rulers) -->
 | 
			
		||||
                <section data-markdown>
 | 
			
		||||
                    <script type="text/template">
 | 
			
		||||
                        A
 | 
			
		||||
 | 
			
		||||
                        ---
 | 
			
		||||
 | 
			
		||||
                        B
 | 
			
		||||
 | 
			
		||||
                        ---
 | 
			
		||||
 | 
			
		||||
                        C
 | 
			
		||||
                    </script>
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
                <!-- Slide attributes -->
 | 
			
		||||
                <section data-markdown>
 | 
			
		||||
                    <script type="text/template">
 | 
			
		||||
                        <!-- .slide: data-background="#000000" -->
 | 
			
		||||
                        ## Slide attributes
 | 
			
		||||
                    </script>
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
                <!-- Element attributes -->
 | 
			
		||||
                <section data-markdown>
 | 
			
		||||
                    <script type="text/template">
 | 
			
		||||
                        ## Element attributes
 | 
			
		||||
                        - Item 1 <!-- .element: class="fragment" data-fragment-index="2" -->
 | 
			
		||||
                        - Item 2 <!-- .element: class="fragment" data-fragment-index="1" -->
 | 
			
		||||
                    </script>
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
                <!-- Code -->
 | 
			
		||||
                <section data-markdown>
 | 
			
		||||
                    <script type="text/template">
 | 
			
		||||
                        ```php
 | 
			
		||||
                        public function foo()
 | 
			
		||||
                        {
 | 
			
		||||
                            $foo = array(
 | 
			
		||||
                                'bar' => 'bar'
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                        ```
 | 
			
		||||
                    </script>
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
                <!-- Images -->
 | 
			
		||||
                <section data-markdown>
 | 
			
		||||
                    <script type="text/template">
 | 
			
		||||
                        
 | 
			
		||||
                    </script>
 | 
			
		||||
                </section>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<script src="../../lib/js/head.min.js"></script>
 | 
			
		||||
		<script src="../../js/reveal.js"></script>
 | 
			
		||||
 | 
			
		||||
		<script>
 | 
			
		||||
 | 
			
		||||
			Reveal.initialize({
 | 
			
		||||
				controls: true,
 | 
			
		||||
				progress: true,
 | 
			
		||||
				history: true,
 | 
			
		||||
				center: true,
 | 
			
		||||
 | 
			
		||||
				// Optional libraries used to extend on reveal.js
 | 
			
		||||
				dependencies: [
 | 
			
		||||
					{ src: '../../lib/js/classList.js', condition: function() { return !document.body.classList; } },
 | 
			
		||||
					{ src: 'marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
 | 
			
		||||
                    { src: 'markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
 | 
			
		||||
                    { src: '../highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
 | 
			
		||||
					{ src: '../notes/notes.js' }
 | 
			
		||||
				]
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		</script>
 | 
			
		||||
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										36
									
								
								plugin/markdown/example.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								plugin/markdown/example.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
# Markdown Demo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## External 1.1
 | 
			
		||||
 | 
			
		||||
Content 1.1
 | 
			
		||||
 | 
			
		||||
Note: This will only appear in the speaker notes window.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## External 1.2
 | 
			
		||||
 | 
			
		||||
Content 1.2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## External 2
 | 
			
		||||
 | 
			
		||||
Content 2.1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## External 3.1
 | 
			
		||||
 | 
			
		||||
Content 3.1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## External 3.2
 | 
			
		||||
 | 
			
		||||
Content 3.2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## External 3.3
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
							
								
								
									
										412
									
								
								plugin/markdown/markdown.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										412
									
								
								plugin/markdown/markdown.js
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,412 @@
 | 
			
		||||
/**
 | 
			
		||||
 * The reveal.js markdown plugin. Handles parsing of
 | 
			
		||||
 * markdown inside of presentations as well as loading
 | 
			
		||||
 * of external markdown documents.
 | 
			
		||||
 */
 | 
			
		||||
(function( root, factory ) {
 | 
			
		||||
	if (typeof define === 'function' && define.amd) {
 | 
			
		||||
		root.marked = require( './marked' );
 | 
			
		||||
		root.RevealMarkdown = factory( root.marked );
 | 
			
		||||
		root.RevealMarkdown.initialize();
 | 
			
		||||
	} else if( typeof exports === 'object' ) {
 | 
			
		||||
		module.exports = factory( require( './marked' ) );
 | 
			
		||||
	} else {
 | 
			
		||||
		// Browser globals (root is window)
 | 
			
		||||
		root.RevealMarkdown = factory( root.marked );
 | 
			
		||||
		root.RevealMarkdown.initialize();
 | 
			
		||||
	}
 | 
			
		||||
}( this, function( marked ) {
 | 
			
		||||
 | 
			
		||||
	var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
 | 
			
		||||
		DEFAULT_NOTES_SEPARATOR = 'notes?:',
 | 
			
		||||
		DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
 | 
			
		||||
		DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
 | 
			
		||||
 | 
			
		||||
	var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Retrieves the markdown contents of a slide section
 | 
			
		||||
	 * element. Normalizes leading tabs/whitespace.
 | 
			
		||||
	 */
 | 
			
		||||
	function getMarkdownFromSlide( section ) {
 | 
			
		||||
 | 
			
		||||
		// look for a <script> or <textarea data-template> wrapper
 | 
			
		||||
		var template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
 | 
			
		||||
 | 
			
		||||
		// strip leading whitespace so it isn't evaluated as code
 | 
			
		||||
		var text = ( template || section ).textContent;
 | 
			
		||||
 | 
			
		||||
		// restore script end tags
 | 
			
		||||
		text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
 | 
			
		||||
 | 
			
		||||
		var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
 | 
			
		||||
			leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
 | 
			
		||||
 | 
			
		||||
		if( leadingTabs > 0 ) {
 | 
			
		||||
			text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
 | 
			
		||||
		}
 | 
			
		||||
		else if( leadingWs > 1 ) {
 | 
			
		||||
			text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return text;
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Given a markdown slide section element, this will
 | 
			
		||||
	 * return all arguments that aren't related to markdown
 | 
			
		||||
	 * parsing. Used to forward any other user-defined arguments
 | 
			
		||||
	 * to the output markdown slide.
 | 
			
		||||
	 */
 | 
			
		||||
	function getForwardedAttributes( section ) {
 | 
			
		||||
 | 
			
		||||
		var attributes = section.attributes;
 | 
			
		||||
		var result = [];
 | 
			
		||||
 | 
			
		||||
		for( var i = 0, len = attributes.length; i < len; i++ ) {
 | 
			
		||||
			var name = attributes[i].name,
 | 
			
		||||
				value = attributes[i].value;
 | 
			
		||||
 | 
			
		||||
			// disregard attributes that are used for markdown loading/parsing
 | 
			
		||||
			if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
 | 
			
		||||
 | 
			
		||||
			if( value ) {
 | 
			
		||||
				result.push( name + '="' + value + '"' );
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				result.push( name );
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result.join( ' ' );
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Inspects the given options and fills out default
 | 
			
		||||
	 * values for what's not defined.
 | 
			
		||||
	 */
 | 
			
		||||
	function getSlidifyOptions( options ) {
 | 
			
		||||
 | 
			
		||||
		options = options || {};
 | 
			
		||||
		options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
 | 
			
		||||
		options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
 | 
			
		||||
		options.attributes = options.attributes || '';
 | 
			
		||||
 | 
			
		||||
		return options;
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Helper function for constructing a markdown slide.
 | 
			
		||||
	 */
 | 
			
		||||
	function createMarkdownSlide( content, options ) {
 | 
			
		||||
 | 
			
		||||
		options = getSlidifyOptions( options );
 | 
			
		||||
 | 
			
		||||
		var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
 | 
			
		||||
 | 
			
		||||
		if( notesMatch.length === 2 ) {
 | 
			
		||||
			content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// prevent script end tags in the content from interfering
 | 
			
		||||
		// with parsing
 | 
			
		||||
		content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
 | 
			
		||||
 | 
			
		||||
		return '<script type="text/template">' + content + '</script>';
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Parses a data string into multiple slides based
 | 
			
		||||
	 * on the passed in separator arguments.
 | 
			
		||||
	 */
 | 
			
		||||
	function slidify( markdown, options ) {
 | 
			
		||||
 | 
			
		||||
		options = getSlidifyOptions( options );
 | 
			
		||||
 | 
			
		||||
		var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
 | 
			
		||||
			horizontalSeparatorRegex = new RegExp( options.separator );
 | 
			
		||||
 | 
			
		||||
		var matches,
 | 
			
		||||
			lastIndex = 0,
 | 
			
		||||
			isHorizontal,
 | 
			
		||||
			wasHorizontal = true,
 | 
			
		||||
			content,
 | 
			
		||||
			sectionStack = [];
 | 
			
		||||
 | 
			
		||||
		// iterate until all blocks between separators are stacked up
 | 
			
		||||
		while( matches = separatorRegex.exec( markdown ) ) {
 | 
			
		||||
			notes = null;
 | 
			
		||||
 | 
			
		||||
			// determine direction (horizontal by default)
 | 
			
		||||
			isHorizontal = horizontalSeparatorRegex.test( matches[0] );
 | 
			
		||||
 | 
			
		||||
			if( !isHorizontal && wasHorizontal ) {
 | 
			
		||||
				// create vertical stack
 | 
			
		||||
				sectionStack.push( [] );
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// pluck slide content from markdown input
 | 
			
		||||
			content = markdown.substring( lastIndex, matches.index );
 | 
			
		||||
 | 
			
		||||
			if( isHorizontal && wasHorizontal ) {
 | 
			
		||||
				// add to horizontal stack
 | 
			
		||||
				sectionStack.push( content );
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				// add to vertical stack
 | 
			
		||||
				sectionStack[sectionStack.length-1].push( content );
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			lastIndex = separatorRegex.lastIndex;
 | 
			
		||||
			wasHorizontal = isHorizontal;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// add the remaining slide
 | 
			
		||||
		( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
 | 
			
		||||
 | 
			
		||||
		var markdownSections = '';
 | 
			
		||||
 | 
			
		||||
		// flatten the hierarchical stack, and insert <section data-markdown> tags
 | 
			
		||||
		for( var i = 0, len = sectionStack.length; i < len; i++ ) {
 | 
			
		||||
			// vertical
 | 
			
		||||
			if( sectionStack[i] instanceof Array ) {
 | 
			
		||||
				markdownSections += '<section '+ options.attributes +'>';
 | 
			
		||||
 | 
			
		||||
				sectionStack[i].forEach( function( child ) {
 | 
			
		||||
					markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
 | 
			
		||||
				} );
 | 
			
		||||
 | 
			
		||||
				markdownSections += '</section>';
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return markdownSections;
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Parses any current data-markdown slides, splits
 | 
			
		||||
	 * multi-slide markdown into separate sections and
 | 
			
		||||
	 * handles loading of external markdown.
 | 
			
		||||
	 */
 | 
			
		||||
	function processSlides() {
 | 
			
		||||
 | 
			
		||||
		var sections = document.querySelectorAll( '[data-markdown]'),
 | 
			
		||||
			section;
 | 
			
		||||
 | 
			
		||||
		for( var i = 0, len = sections.length; i < len; i++ ) {
 | 
			
		||||
 | 
			
		||||
			section = sections[i];
 | 
			
		||||
 | 
			
		||||
			if( section.getAttribute( 'data-markdown' ).length ) {
 | 
			
		||||
 | 
			
		||||
				var xhr = new XMLHttpRequest(),
 | 
			
		||||
					url = section.getAttribute( 'data-markdown' );
 | 
			
		||||
 | 
			
		||||
				datacharset = section.getAttribute( 'data-charset' );
 | 
			
		||||
 | 
			
		||||
				// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
 | 
			
		||||
				if( datacharset != null && datacharset != '' ) {
 | 
			
		||||
					xhr.overrideMimeType( 'text/html; charset=' + datacharset );
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				xhr.onreadystatechange = function() {
 | 
			
		||||
					if( xhr.readyState === 4 ) {
 | 
			
		||||
						// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
 | 
			
		||||
						if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
 | 
			
		||||
 | 
			
		||||
							section.outerHTML = slidify( xhr.responseText, {
 | 
			
		||||
								separator: section.getAttribute( 'data-separator' ),
 | 
			
		||||
								verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
 | 
			
		||||
								notesSeparator: section.getAttribute( 'data-separator-notes' ),
 | 
			
		||||
								attributes: getForwardedAttributes( section )
 | 
			
		||||
							});
 | 
			
		||||
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
 | 
			
		||||
							section.outerHTML = '<section data-state="alert">' +
 | 
			
		||||
								'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
 | 
			
		||||
								'Check your browser\'s JavaScript console for more details.' +
 | 
			
		||||
								'<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
 | 
			
		||||
								'</section>';
 | 
			
		||||
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				xhr.open( 'GET', url, false );
 | 
			
		||||
 | 
			
		||||
				try {
 | 
			
		||||
					xhr.send();
 | 
			
		||||
				}
 | 
			
		||||
				catch ( e ) {
 | 
			
		||||
					alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
			else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
 | 
			
		||||
 | 
			
		||||
				section.outerHTML = slidify( getMarkdownFromSlide( section ), {
 | 
			
		||||
					separator: section.getAttribute( 'data-separator' ),
 | 
			
		||||
					verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
 | 
			
		||||
					notesSeparator: section.getAttribute( 'data-separator-notes' ),
 | 
			
		||||
					attributes: getForwardedAttributes( section )
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Check if a node value has the attributes pattern.
 | 
			
		||||
	 * If yes, extract it and add that value as one or several attributes
 | 
			
		||||
	 * the the terget element.
 | 
			
		||||
	 *
 | 
			
		||||
	 * You need Cache Killer on Chrome to see the effect on any FOM transformation
 | 
			
		||||
	 * directly on refresh (F5)
 | 
			
		||||
	 * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
 | 
			
		||||
	 */
 | 
			
		||||
	function addAttributeInElement( node, elementTarget, separator ) {
 | 
			
		||||
 | 
			
		||||
		var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
 | 
			
		||||
		var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
 | 
			
		||||
		var nodeValue = node.nodeValue;
 | 
			
		||||
		if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
 | 
			
		||||
 | 
			
		||||
			var classes = matches[1];
 | 
			
		||||
			nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
 | 
			
		||||
			node.nodeValue = nodeValue;
 | 
			
		||||
			while( matchesClass = mardownClassRegex.exec( classes ) ) {
 | 
			
		||||
				elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
 | 
			
		||||
			}
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add attributes to the parent element of a text node,
 | 
			
		||||
	 * or the element of an attribute node.
 | 
			
		||||
	 */
 | 
			
		||||
	function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
 | 
			
		||||
 | 
			
		||||
		if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
 | 
			
		||||
			previousParentElement = element;
 | 
			
		||||
			for( var i = 0; i < element.childNodes.length; i++ ) {
 | 
			
		||||
				childElement = element.childNodes[i];
 | 
			
		||||
				if ( i > 0 ) {
 | 
			
		||||
					j = i - 1;
 | 
			
		||||
					while ( j >= 0 ) {
 | 
			
		||||
						aPreviousChildElement = element.childNodes[j];
 | 
			
		||||
						if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
 | 
			
		||||
							previousParentElement = aPreviousChildElement;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
						j = j - 1;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				parentSection = section;
 | 
			
		||||
				if( childElement.nodeName ==  "section" ) {
 | 
			
		||||
					parentSection = childElement ;
 | 
			
		||||
					previousParentElement = childElement ;
 | 
			
		||||
				}
 | 
			
		||||
				if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
 | 
			
		||||
					addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ( element.nodeType == Node.COMMENT_NODE ) {
 | 
			
		||||
			if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
 | 
			
		||||
				addAttributeInElement( element, section, separatorSectionAttributes );
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Converts any current data-markdown slides in the
 | 
			
		||||
	 * DOM to HTML.
 | 
			
		||||
	 */
 | 
			
		||||
	function convertSlides() {
 | 
			
		||||
 | 
			
		||||
		var sections = document.querySelectorAll( '[data-markdown]');
 | 
			
		||||
 | 
			
		||||
		for( var i = 0, len = sections.length; i < len; i++ ) {
 | 
			
		||||
 | 
			
		||||
			var section = sections[i];
 | 
			
		||||
 | 
			
		||||
			// Only parse the same slide once
 | 
			
		||||
			if( !section.getAttribute( 'data-markdown-parsed' ) ) {
 | 
			
		||||
 | 
			
		||||
				section.setAttribute( 'data-markdown-parsed', true )
 | 
			
		||||
 | 
			
		||||
				var notes = section.querySelector( 'aside.notes' );
 | 
			
		||||
				var markdown = getMarkdownFromSlide( section );
 | 
			
		||||
 | 
			
		||||
				section.innerHTML = marked( markdown );
 | 
			
		||||
				addAttributes( 	section, section, null, section.getAttribute( 'data-element-attributes' ) ||
 | 
			
		||||
								section.parentNode.getAttribute( 'data-element-attributes' ) ||
 | 
			
		||||
								DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
 | 
			
		||||
								section.getAttribute( 'data-attributes' ) ||
 | 
			
		||||
								section.parentNode.getAttribute( 'data-attributes' ) ||
 | 
			
		||||
								DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
 | 
			
		||||
 | 
			
		||||
				// If there were notes, we need to re-add them after
 | 
			
		||||
				// having overwritten the section's HTML
 | 
			
		||||
				if( notes ) {
 | 
			
		||||
					section.appendChild( notes );
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// API
 | 
			
		||||
	return {
 | 
			
		||||
 | 
			
		||||
		initialize: function() {
 | 
			
		||||
			if( typeof marked === 'undefined' ) {
 | 
			
		||||
				throw 'The reveal.js Markdown plugin requires marked to be loaded';
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if( typeof hljs !== 'undefined' ) {
 | 
			
		||||
				marked.setOptions({
 | 
			
		||||
					highlight: function( code, lang ) {
 | 
			
		||||
						return hljs.highlightAuto( code, [lang] ).value;
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var options = Reveal.getConfig().markdown;
 | 
			
		||||
 | 
			
		||||
			if ( options ) {
 | 
			
		||||
				marked.setOptions( options );
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			processSlides();
 | 
			
		||||
			convertSlides();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// TODO: Do these belong in the API?
 | 
			
		||||
		processSlides: processSlides,
 | 
			
		||||
		convertSlides: convertSlides,
 | 
			
		||||
		slidify: slidify
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}));
 | 
			
		||||
							
								
								
									
										6
									
								
								plugin/markdown/marked.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								plugin/markdown/marked.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										67
									
								
								plugin/math/math.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										67
									
								
								plugin/math/math.js
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
/**
 | 
			
		||||
 * A plugin which enables rendering of math equations inside
 | 
			
		||||
 * of reveal.js slides. Essentially a thin wrapper for MathJax.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Hakim El Hattab
 | 
			
		||||
 */
 | 
			
		||||
var RevealMath = window.RevealMath || (function(){
 | 
			
		||||
 | 
			
		||||
	var options = Reveal.getConfig().math || {};
 | 
			
		||||
	options.mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js';
 | 
			
		||||
	options.config = options.config || 'TeX-AMS_HTML-full';
 | 
			
		||||
	options.tex2jax = options.tex2jax || {
 | 
			
		||||
				inlineMath: [['$','$'],['\\(','\\)']] ,
 | 
			
		||||
				skipTags: ['script','noscript','style','textarea','pre'] };
 | 
			
		||||
 | 
			
		||||
	loadScript( options.mathjax + '?config=' + options.config, function() {
 | 
			
		||||
 | 
			
		||||
		MathJax.Hub.Config({
 | 
			
		||||
			messageStyle: 'none',
 | 
			
		||||
			tex2jax: options.tex2jax,
 | 
			
		||||
			skipStartupTypeset: true
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Typeset followed by an immediate reveal.js layout since
 | 
			
		||||
		// the typesetting process could affect slide height
 | 
			
		||||
		MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] );
 | 
			
		||||
		MathJax.Hub.Queue( Reveal.layout );
 | 
			
		||||
 | 
			
		||||
		// Reprocess equations in slides when they turn visible
 | 
			
		||||
		Reveal.addEventListener( 'slidechanged', function( event ) {
 | 
			
		||||
 | 
			
		||||
			MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] );
 | 
			
		||||
 | 
			
		||||
		} );
 | 
			
		||||
 | 
			
		||||
	} );
 | 
			
		||||
 | 
			
		||||
	function loadScript( url, callback ) {
 | 
			
		||||
 | 
			
		||||
		var head = document.querySelector( 'head' );
 | 
			
		||||
		var script = document.createElement( 'script' );
 | 
			
		||||
		script.type = 'text/javascript';
 | 
			
		||||
		script.src = url;
 | 
			
		||||
 | 
			
		||||
		// Wrapper for callback to make sure it only fires once
 | 
			
		||||
		var finish = function() {
 | 
			
		||||
			if( typeof callback === 'function' ) {
 | 
			
		||||
				callback.call();
 | 
			
		||||
				callback = null;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		script.onload = finish;
 | 
			
		||||
 | 
			
		||||
		// IE
 | 
			
		||||
		script.onreadystatechange = function() {
 | 
			
		||||
			if ( this.readyState === 'loaded' ) {
 | 
			
		||||
				finish();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Normal browsers
 | 
			
		||||
		head.appendChild( script );
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										13
									
								
								plugin/multiplex/client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								plugin/multiplex/client.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
(function() {
 | 
			
		||||
	var multiplex = Reveal.getConfig().multiplex;
 | 
			
		||||
	var socketId = multiplex.id;
 | 
			
		||||
	var socket = io.connect(multiplex.url);
 | 
			
		||||
 | 
			
		||||
	socket.on(multiplex.id, function(data) {
 | 
			
		||||
		// ignore data from sockets that aren't ours
 | 
			
		||||
		if (data.socketId !== socketId) { return; }
 | 
			
		||||
		if( window.location.host === 'localhost:1947' ) return;
 | 
			
		||||
 | 
			
		||||
		Reveal.setState(data.state);
 | 
			
		||||
	});
 | 
			
		||||
}());
 | 
			
		||||
							
								
								
									
										64
									
								
								plugin/multiplex/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								plugin/multiplex/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
var http        = require('http');
 | 
			
		||||
var express		= require('express');
 | 
			
		||||
var fs			= require('fs');
 | 
			
		||||
var io			= require('socket.io');
 | 
			
		||||
var crypto		= require('crypto');
 | 
			
		||||
 | 
			
		||||
var app       	= express();
 | 
			
		||||
var staticDir 	= express.static;
 | 
			
		||||
var server    	= http.createServer(app);
 | 
			
		||||
 | 
			
		||||
io = io(server);
 | 
			
		||||
 | 
			
		||||
var opts = {
 | 
			
		||||
	port: process.env.PORT || 1948,
 | 
			
		||||
	baseDir : __dirname + '/../../'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
io.on( 'connection', function( socket ) {
 | 
			
		||||
	socket.on('multiplex-statechanged', function(data) {
 | 
			
		||||
		if (typeof data.secret == 'undefined' || data.secret == null || data.secret === '') return;
 | 
			
		||||
		if (createHash(data.secret) === data.socketId) {
 | 
			
		||||
			data.secret = null;
 | 
			
		||||
			socket.broadcast.emit(data.socketId, data);
 | 
			
		||||
		};
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
[ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) {
 | 
			
		||||
	app.use('/' + dir, staticDir(opts.baseDir + dir));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/", function(req, res) {
 | 
			
		||||
	res.writeHead(200, {'Content-Type': 'text/html'});
 | 
			
		||||
 | 
			
		||||
	var stream = fs.createReadStream(opts.baseDir + '/index.html');
 | 
			
		||||
	stream.on('error', function( error ) {
 | 
			
		||||
		res.write('<style>body{font-family: sans-serif;}</style><h2>reveal.js multiplex server.</h2><a href="/token">Generate token</a>');
 | 
			
		||||
		res.end();
 | 
			
		||||
	});
 | 
			
		||||
	stream.on('readable', function() {
 | 
			
		||||
		stream.pipe(res);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/token", function(req,res) {
 | 
			
		||||
	var ts = new Date().getTime();
 | 
			
		||||
	var rand = Math.floor(Math.random()*9999999);
 | 
			
		||||
	var secret = ts.toString() + rand.toString();
 | 
			
		||||
	res.send({secret: secret, socketId: createHash(secret)});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var createHash = function(secret) {
 | 
			
		||||
	var cipher = crypto.createCipher('blowfish', secret);
 | 
			
		||||
	return(cipher.final('hex'));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Actually listen
 | 
			
		||||
server.listen( opts.port || null );
 | 
			
		||||
 | 
			
		||||
var brown = '\033[33m',
 | 
			
		||||
	green = '\033[32m',
 | 
			
		||||
	reset = '\033[0m';
 | 
			
		||||
 | 
			
		||||
console.log( brown + "reveal.js:" + reset + " Multiplex running on port " + green + opts.port + reset );
 | 
			
		||||
							
								
								
									
										34
									
								
								plugin/multiplex/master.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								plugin/multiplex/master.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
	// Don't emit events from inside of notes windows
 | 
			
		||||
	if ( window.location.search.match( /receiver/gi ) ) { return; }
 | 
			
		||||
 | 
			
		||||
	var multiplex = Reveal.getConfig().multiplex;
 | 
			
		||||
 | 
			
		||||
	var socket = io.connect( multiplex.url );
 | 
			
		||||
 | 
			
		||||
	function post() {
 | 
			
		||||
 | 
			
		||||
		var messageData = {
 | 
			
		||||
			state: Reveal.getState(),
 | 
			
		||||
			secret: multiplex.secret,
 | 
			
		||||
			socketId: multiplex.id
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		socket.emit( 'multiplex-statechanged', messageData );
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// post once the page is loaded, so the client follows also on "open URL".
 | 
			
		||||
	window.addEventListener( 'load', post );
 | 
			
		||||
 | 
			
		||||
	// 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 );
 | 
			
		||||
 | 
			
		||||
}());
 | 
			
		||||
							
								
								
									
										19
									
								
								plugin/multiplex/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								plugin/multiplex/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "reveal-js-multiplex",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "reveal.js multiplex server",
 | 
			
		||||
  "homepage": "http://revealjs.com",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node index.js"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": "~4.1.1"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "express": "~4.13.3",
 | 
			
		||||
    "grunt-cli": "~0.1.13",
 | 
			
		||||
    "mustache": "~2.2.1",
 | 
			
		||||
    "socket.io": "~1.3.7"
 | 
			
		||||
  },
 | 
			
		||||
  "license": "MIT"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								plugin/notes-server/client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								plugin/notes-server/client.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
	// don't emit events from inside the previews themselves
 | 
			
		||||
	if( window.location.search.match( /receiver/gi ) ) { return; }
 | 
			
		||||
 | 
			
		||||
	var socket = io.connect( window.location.origin ),
 | 
			
		||||
		socketId = Math.random().toString().slice( 2 );
 | 
			
		||||
 | 
			
		||||
	console.log( 'View slide notes at ' + window.location.origin + '/notes/' + socketId );
 | 
			
		||||
 | 
			
		||||
	window.open( window.location.origin + '/notes/' + socketId, 'notes-' + socketId );
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Posts the current slide data to the notes window
 | 
			
		||||
	 */
 | 
			
		||||
	function post() {
 | 
			
		||||
 | 
			
		||||
		var slideElement = Reveal.getCurrentSlide(),
 | 
			
		||||
			notesElement = slideElement.querySelector( 'aside.notes' );
 | 
			
		||||
 | 
			
		||||
		var messageData = {
 | 
			
		||||
			notes: '',
 | 
			
		||||
			markdown: false,
 | 
			
		||||
			socketId: socketId,
 | 
			
		||||
			state: Reveal.getState()
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// Look for notes defined in a slide attribute
 | 
			
		||||
		if( slideElement.hasAttribute( 'data-notes' ) ) {
 | 
			
		||||
			messageData.notes = slideElement.getAttribute( 'data-notes' );
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Look for notes defined in an aside element
 | 
			
		||||
		if( notesElement ) {
 | 
			
		||||
			messageData.notes = notesElement.innerHTML;
 | 
			
		||||
			messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		socket.emit( 'statechanged', messageData );
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When a new notes window connects, post our current state
 | 
			
		||||
	socket.on( 'new-subscriber', function( data ) {
 | 
			
		||||
		post();
 | 
			
		||||
	} );
 | 
			
		||||
 | 
			
		||||
	// When the state changes from inside of the speaker view
 | 
			
		||||
	socket.on( 'statechanged-speaker', function( data ) {
 | 
			
		||||
		Reveal.setState( data.state );
 | 
			
		||||
	} );
 | 
			
		||||
 | 
			
		||||
	// 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();
 | 
			
		||||
 | 
			
		||||
}());
 | 
			
		||||
							
								
								
									
										69
									
								
								plugin/notes-server/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								plugin/notes-server/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
var http      = require('http');
 | 
			
		||||
var express   = require('express');
 | 
			
		||||
var fs        = require('fs');
 | 
			
		||||
var io        = require('socket.io');
 | 
			
		||||
var Mustache  = require('mustache');
 | 
			
		||||
 | 
			
		||||
var app       = express();
 | 
			
		||||
var staticDir = express.static;
 | 
			
		||||
var server    = http.createServer(app);
 | 
			
		||||
 | 
			
		||||
io = io(server);
 | 
			
		||||
 | 
			
		||||
var opts = {
 | 
			
		||||
	port :      1947,
 | 
			
		||||
	baseDir :   __dirname + '/../../'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
io.on( 'connection', function( socket ) {
 | 
			
		||||
 | 
			
		||||
	socket.on( 'new-subscriber', function( data ) {
 | 
			
		||||
		socket.broadcast.emit( 'new-subscriber', data );
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	socket.on( 'statechanged', function( data ) {
 | 
			
		||||
		delete data.state.overview;
 | 
			
		||||
		socket.broadcast.emit( 'statechanged', data );
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	socket.on( 'statechanged-speaker', function( data ) {
 | 
			
		||||
		delete data.state.overview;
 | 
			
		||||
		socket.broadcast.emit( 'statechanged-speaker', data );
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
[ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) {
 | 
			
		||||
	app.use( '/' + dir, staticDir( opts.baseDir + dir ) );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get('/', function( req, res ) {
 | 
			
		||||
 | 
			
		||||
	res.writeHead( 200, { 'Content-Type': 'text/html' } );
 | 
			
		||||
	fs.createReadStream( opts.baseDir + '/index.html' ).pipe( res );
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get( '/notes/:socketId', function( req, res ) {
 | 
			
		||||
 | 
			
		||||
	fs.readFile( opts.baseDir + 'plugin/notes-server/notes.html', function( err, data ) {
 | 
			
		||||
		res.send( Mustache.to_html( data.toString(), {
 | 
			
		||||
			socketId : req.params.socketId
 | 
			
		||||
		}));
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Actually listen
 | 
			
		||||
server.listen( opts.port || null );
 | 
			
		||||
 | 
			
		||||
var brown = '\033[33m',
 | 
			
		||||
	green = '\033[32m',
 | 
			
		||||
	reset = '\033[0m';
 | 
			
		||||
 | 
			
		||||
var slidesLocation = 'http://localhost' + ( opts.port ? ( ':' + opts.port ) : '' );
 | 
			
		||||
 | 
			
		||||
console.log( brown + 'reveal.js - Speaker Notes' + reset );
 | 
			
		||||
console.log( '1. Open the slides at ' + green + slidesLocation + reset );
 | 
			
		||||
console.log( '2. Click on the link in your JS console to go to the notes page' );
 | 
			
		||||
console.log( '3. Advance through your slides and your notes will advance automatically' );
 | 
			
		||||
							
								
								
									
										585
									
								
								plugin/notes-server/notes.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								plugin/notes-server/notes.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,585 @@
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta charset="utf-8">
 | 
			
		||||
 | 
			
		||||
		<title>reveal.js - Slide Notes</title>
 | 
			
		||||
 | 
			
		||||
		<style>
 | 
			
		||||
			body {
 | 
			
		||||
				font-family: Helvetica;
 | 
			
		||||
				font-size: 18px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			#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;
 | 
			
		||||
				z-index: 2;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			.overlay-element {
 | 
			
		||||
				height: 34px;
 | 
			
		||||
				line-height: 34px;
 | 
			
		||||
				padding: 0 10px;
 | 
			
		||||
				text-shadow: none;
 | 
			
		||||
				background: rgba( 220, 220, 220, 0.8 );
 | 
			
		||||
				color: #222;
 | 
			
		||||
				font-size: 14px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			.overlay-element.interactive:hover {
 | 
			
		||||
				background: rgba( 220, 220, 220, 1 );
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			#current-slide {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				width: 60%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				padding-right: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			#upcoming-slide {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				width: 40%;
 | 
			
		||||
				height: 40%;
 | 
			
		||||
				right: 0;
 | 
			
		||||
				top: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker controls */
 | 
			
		||||
			#speaker-controls {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				top: 40%;
 | 
			
		||||
				right: 0;
 | 
			
		||||
				width: 40%;
 | 
			
		||||
				height: 60%;
 | 
			
		||||
				overflow: auto;
 | 
			
		||||
				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;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			/* Layout selector */
 | 
			
		||||
			#speaker-layout {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				top: 10px;
 | 
			
		||||
				right: 10px;
 | 
			
		||||
				color: #222;
 | 
			
		||||
				z-index: 10;
 | 
			
		||||
			}
 | 
			
		||||
				#speaker-layout select {
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					width: 100%;
 | 
			
		||||
					height: 100%;
 | 
			
		||||
					top: 0;
 | 
			
		||||
					left: 0;
 | 
			
		||||
					border: 0;
 | 
			
		||||
					box-shadow: 0;
 | 
			
		||||
					cursor: pointer;
 | 
			
		||||
					opacity: 0;
 | 
			
		||||
 | 
			
		||||
					font-size: 1em;
 | 
			
		||||
					background-color: transparent;
 | 
			
		||||
 | 
			
		||||
					-moz-appearance: none;
 | 
			
		||||
					-webkit-appearance: none;
 | 
			
		||||
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				#speaker-layout select:focus {
 | 
			
		||||
					outline: none;
 | 
			
		||||
					box-shadow: none;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			.clear {
 | 
			
		||||
				clear: both;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker layout: Wide */
 | 
			
		||||
			body[data-speaker-layout="wide"] #current-slide,
 | 
			
		||||
			body[data-speaker-layout="wide"] #upcoming-slide {
 | 
			
		||||
				width: 50%;
 | 
			
		||||
				height: 45%;
 | 
			
		||||
				padding: 6px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="wide"] #current-slide {
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="wide"] #upcoming-slide {
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 50%;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="wide"] #speaker-controls {
 | 
			
		||||
				top: 45%;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				height: 50%;
 | 
			
		||||
				font-size: 1.25em;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker layout: Tall */
 | 
			
		||||
			body[data-speaker-layout="tall"] #current-slide,
 | 
			
		||||
			body[data-speaker-layout="tall"] #upcoming-slide {
 | 
			
		||||
				width: 45%;
 | 
			
		||||
				height: 50%;
 | 
			
		||||
				padding: 6px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="tall"] #current-slide {
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="tall"] #upcoming-slide {
 | 
			
		||||
				top: 50%;
 | 
			
		||||
				left: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="tall"] #speaker-controls {
 | 
			
		||||
				padding-top: 40px;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 45%;
 | 
			
		||||
				width: 55%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				font-size: 1.25em;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker layout: Notes only */
 | 
			
		||||
			body[data-speaker-layout="notes-only"] #current-slide,
 | 
			
		||||
			body[data-speaker-layout="notes-only"] #upcoming-slide {
 | 
			
		||||
				display: none;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="notes-only"] #speaker-controls {
 | 
			
		||||
				padding-top: 40px;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				font-size: 1.25em;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		</style>
 | 
			
		||||
	</head>
 | 
			
		||||
 | 
			
		||||
	<body>
 | 
			
		||||
 | 
			
		||||
		<div id="current-slide"></div>
 | 
			
		||||
		<div id="upcoming-slide"><span class="overlay-element 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>
 | 
			
		||||
		<div id="speaker-layout" class="overlay-element interactive">
 | 
			
		||||
			<span class="speaker-layout-label"></span>
 | 
			
		||||
			<select class="speaker-layout-dropdown"></select>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<script src="/socket.io/socket.io.js"></script>
 | 
			
		||||
		<script src="/plugin/markdown/marked.js"></script>
 | 
			
		||||
 | 
			
		||||
		<script>
 | 
			
		||||
		(function() {
 | 
			
		||||
 | 
			
		||||
			var notes,
 | 
			
		||||
				notesValue,
 | 
			
		||||
				currentState,
 | 
			
		||||
				currentSlide,
 | 
			
		||||
				upcomingSlide,
 | 
			
		||||
				layoutLabel,
 | 
			
		||||
				layoutDropdown,
 | 
			
		||||
				connected = false;
 | 
			
		||||
 | 
			
		||||
			var socket = io.connect( window.location.origin ),
 | 
			
		||||
				socketId = '{{socketId}}';
 | 
			
		||||
 | 
			
		||||
			var SPEAKER_LAYOUTS = {
 | 
			
		||||
				'default': 'Default',
 | 
			
		||||
				'wide': 'Wide',
 | 
			
		||||
				'tall': 'Tall',
 | 
			
		||||
				'notes-only': 'Notes only'
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			socket.on( 'statechanged', function( data ) {
 | 
			
		||||
 | 
			
		||||
				// ignore data from sockets that aren't ours
 | 
			
		||||
				if( data.socketId !== socketId ) { return; }
 | 
			
		||||
 | 
			
		||||
				if( connected === false ) {
 | 
			
		||||
					connected = true;
 | 
			
		||||
 | 
			
		||||
					setupKeyboard();
 | 
			
		||||
					setupNotes();
 | 
			
		||||
					setupTimer();
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				handleStateMessage( data );
 | 
			
		||||
 | 
			
		||||
			} );
 | 
			
		||||
 | 
			
		||||
			setupLayout();
 | 
			
		||||
 | 
			
		||||
			// Load our presentation iframes
 | 
			
		||||
			setupIframes();
 | 
			
		||||
 | 
			
		||||
			// Once the iframes have loaded, emit a signal saying there's
 | 
			
		||||
			// a new subscriber which will trigger a 'statechanged'
 | 
			
		||||
			// message to be sent back
 | 
			
		||||
			window.addEventListener( 'message', function( event ) {
 | 
			
		||||
 | 
			
		||||
				var data = JSON.parse( event.data );
 | 
			
		||||
 | 
			
		||||
				if( data && data.namespace === 'reveal' ) {
 | 
			
		||||
					if( /ready/.test( data.eventName ) ) {
 | 
			
		||||
						socket.emit( 'new-subscriber', { socketId: socketId } );
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Messages sent by reveal.js inside of the current slide preview
 | 
			
		||||
				if( data && data.namespace === 'reveal' ) {
 | 
			
		||||
					if( /slidechanged|fragmentshown|fragmenthidden|overviewshown|overviewhidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
 | 
			
		||||
						socket.emit( 'statechanged-speaker', { state: data.state } );
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			} );
 | 
			
		||||
 | 
			
		||||
			/**
 | 
			
		||||
			 * 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() {
 | 
			
		||||
 | 
			
		||||
				var params = [
 | 
			
		||||
					'receiver',
 | 
			
		||||
					'progress=false',
 | 
			
		||||
					'history=false',
 | 
			
		||||
					'transition=none',
 | 
			
		||||
					'backgroundTransition=none'
 | 
			
		||||
				].join( '&' );
 | 
			
		||||
 | 
			
		||||
				var currentURL = '/?' + params + '&postMessageEvents=true';
 | 
			
		||||
				var upcomingURL = '/?' + params + '&controls=false';
 | 
			
		||||
 | 
			
		||||
				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;
 | 
			
		||||
				} );
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/**
 | 
			
		||||
				 * Sets up the speaker view layout and layout selector.
 | 
			
		||||
				 */
 | 
			
		||||
				function setupLayout() {
 | 
			
		||||
 | 
			
		||||
					layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
 | 
			
		||||
					layoutLabel = document.querySelector( '.speaker-layout-label' );
 | 
			
		||||
 | 
			
		||||
					// Render the list of available layouts
 | 
			
		||||
					for( var id in SPEAKER_LAYOUTS ) {
 | 
			
		||||
						var option = document.createElement( 'option' );
 | 
			
		||||
						option.setAttribute( 'value', id );
 | 
			
		||||
						option.textContent = SPEAKER_LAYOUTS[ id ];
 | 
			
		||||
						layoutDropdown.appendChild( option );
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Monitor the dropdown for changes
 | 
			
		||||
					layoutDropdown.addEventListener( 'change', function( event ) {
 | 
			
		||||
 | 
			
		||||
						setLayout( layoutDropdown.value );
 | 
			
		||||
 | 
			
		||||
					}, false );
 | 
			
		||||
 | 
			
		||||
					// Restore any currently persisted layout
 | 
			
		||||
					setLayout( getLayout() );
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * Sets a new speaker view layout. The layout is persisted
 | 
			
		||||
				 * in local storage.
 | 
			
		||||
				 */
 | 
			
		||||
				function setLayout( value ) {
 | 
			
		||||
 | 
			
		||||
					var title = SPEAKER_LAYOUTS[ value ];
 | 
			
		||||
 | 
			
		||||
					layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
 | 
			
		||||
					layoutDropdown.value = value;
 | 
			
		||||
 | 
			
		||||
					document.body.setAttribute( 'data-speaker-layout', value );
 | 
			
		||||
 | 
			
		||||
					// Persist locally
 | 
			
		||||
					if( window.localStorage ) {
 | 
			
		||||
						window.localStorage.setItem( 'reveal-speaker-layout', value );
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * Returns the ID of the most recently set speaker layout
 | 
			
		||||
				 * or our default layout if none has been set.
 | 
			
		||||
				 */
 | 
			
		||||
				function getLayout() {
 | 
			
		||||
 | 
			
		||||
					if( window.localStorage ) {
 | 
			
		||||
						var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
 | 
			
		||||
						if( layout ) {
 | 
			
		||||
							return layout;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Default to the first record in the layouts hash
 | 
			
		||||
					for( var id in SPEAKER_LAYOUTS ) {
 | 
			
		||||
						return id;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			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>
 | 
			
		||||
							
								
								
									
										792
									
								
								plugin/notes/notes.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										792
									
								
								plugin/notes/notes.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,792 @@
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta charset="utf-8">
 | 
			
		||||
 | 
			
		||||
		<title>reveal.js - Slide Notes</title>
 | 
			
		||||
 | 
			
		||||
		<style>
 | 
			
		||||
			body {
 | 
			
		||||
				font-family: Helvetica;
 | 
			
		||||
				font-size: 18px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			#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;
 | 
			
		||||
				z-index: 2;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			#connection-status {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				z-index: 20;
 | 
			
		||||
				padding: 30% 20% 20% 20%;
 | 
			
		||||
				font-size: 18px;
 | 
			
		||||
				color: #222;
 | 
			
		||||
				background: #fff;
 | 
			
		||||
				text-align: center;
 | 
			
		||||
				box-sizing: border-box;
 | 
			
		||||
				line-height: 1.4;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			.overlay-element {
 | 
			
		||||
				height: 34px;
 | 
			
		||||
				line-height: 34px;
 | 
			
		||||
				padding: 0 10px;
 | 
			
		||||
				text-shadow: none;
 | 
			
		||||
				background: rgba( 220, 220, 220, 0.8 );
 | 
			
		||||
				color: #222;
 | 
			
		||||
				font-size: 14px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			.overlay-element.interactive:hover {
 | 
			
		||||
				background: rgba( 220, 220, 220, 1 );
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			#current-slide {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				width: 60%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				padding-right: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			#upcoming-slide {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				width: 40%;
 | 
			
		||||
				height: 40%;
 | 
			
		||||
				right: 0;
 | 
			
		||||
				top: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker controls */
 | 
			
		||||
			#speaker-controls {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				top: 40%;
 | 
			
		||||
				right: 0;
 | 
			
		||||
				width: 40%;
 | 
			
		||||
				height: 60%;
 | 
			
		||||
				overflow: auto;
 | 
			
		||||
				font-size: 18px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time.hidden,
 | 
			
		||||
				.speaker-controls-notes.hidden {
 | 
			
		||||
					display: none;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .label,
 | 
			
		||||
				.speaker-controls-pace .label,
 | 
			
		||||
				.speaker-controls-notes .label {
 | 
			
		||||
					text-transform: uppercase;
 | 
			
		||||
					font-weight: normal;
 | 
			
		||||
					font-size: 0.66em;
 | 
			
		||||
					color: #666;
 | 
			
		||||
					margin: 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time, .speaker-controls-pace {
 | 
			
		||||
					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%;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .timer,
 | 
			
		||||
				.speaker-controls-time .clock,
 | 
			
		||||
				.speaker-controls-time .pacing .hours-value,
 | 
			
		||||
				.speaker-controls-time .pacing .minutes-value,
 | 
			
		||||
				.speaker-controls-time .pacing .seconds-value {
 | 
			
		||||
					font-size: 1.9em;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .timer {
 | 
			
		||||
					float: left;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .clock {
 | 
			
		||||
					float: right;
 | 
			
		||||
					text-align: right;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time span.mute {
 | 
			
		||||
					opacity: 0.3;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .pacing-title {
 | 
			
		||||
					margin-top: 5px;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .pacing.ahead {
 | 
			
		||||
					color: blue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .pacing.on-track {
 | 
			
		||||
					color: green;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-time .pacing.behind {
 | 
			
		||||
					color: red;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-notes {
 | 
			
		||||
					padding: 10px 16px;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				.speaker-controls-notes .value {
 | 
			
		||||
					margin-top: 5px;
 | 
			
		||||
					line-height: 1.4;
 | 
			
		||||
					font-size: 1.2em;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			/* Layout selector */
 | 
			
		||||
			#speaker-layout {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				top: 10px;
 | 
			
		||||
				right: 10px;
 | 
			
		||||
				color: #222;
 | 
			
		||||
				z-index: 10;
 | 
			
		||||
			}
 | 
			
		||||
				#speaker-layout select {
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					width: 100%;
 | 
			
		||||
					height: 100%;
 | 
			
		||||
					top: 0;
 | 
			
		||||
					left: 0;
 | 
			
		||||
					border: 0;
 | 
			
		||||
					box-shadow: 0;
 | 
			
		||||
					cursor: pointer;
 | 
			
		||||
					opacity: 0;
 | 
			
		||||
 | 
			
		||||
					font-size: 1em;
 | 
			
		||||
					background-color: transparent;
 | 
			
		||||
 | 
			
		||||
					-moz-appearance: none;
 | 
			
		||||
					-webkit-appearance: none;
 | 
			
		||||
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				#speaker-layout select:focus {
 | 
			
		||||
					outline: none;
 | 
			
		||||
					box-shadow: none;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			.clear {
 | 
			
		||||
				clear: both;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker layout: Wide */
 | 
			
		||||
			body[data-speaker-layout="wide"] #current-slide,
 | 
			
		||||
			body[data-speaker-layout="wide"] #upcoming-slide {
 | 
			
		||||
				width: 50%;
 | 
			
		||||
				height: 45%;
 | 
			
		||||
				padding: 6px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="wide"] #current-slide {
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="wide"] #upcoming-slide {
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 50%;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="wide"] #speaker-controls {
 | 
			
		||||
				top: 45%;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				height: 50%;
 | 
			
		||||
				font-size: 1.25em;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker layout: Tall */
 | 
			
		||||
			body[data-speaker-layout="tall"] #current-slide,
 | 
			
		||||
			body[data-speaker-layout="tall"] #upcoming-slide {
 | 
			
		||||
				width: 45%;
 | 
			
		||||
				height: 50%;
 | 
			
		||||
				padding: 6px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="tall"] #current-slide {
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="tall"] #upcoming-slide {
 | 
			
		||||
				top: 50%;
 | 
			
		||||
				left: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="tall"] #speaker-controls {
 | 
			
		||||
				padding-top: 40px;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 45%;
 | 
			
		||||
				width: 55%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				font-size: 1.25em;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/* Speaker layout: Notes only */
 | 
			
		||||
			body[data-speaker-layout="notes-only"] #current-slide,
 | 
			
		||||
			body[data-speaker-layout="notes-only"] #upcoming-slide {
 | 
			
		||||
				display: none;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body[data-speaker-layout="notes-only"] #speaker-controls {
 | 
			
		||||
				padding-top: 40px;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
				font-size: 1.25em;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@media screen and (max-width: 1080px) {
 | 
			
		||||
				body[data-speaker-layout="default"] #speaker-controls {
 | 
			
		||||
					font-size: 16px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@media screen and (max-width: 900px) {
 | 
			
		||||
				body[data-speaker-layout="default"] #speaker-controls {
 | 
			
		||||
					font-size: 14px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@media screen and (max-width: 800px) {
 | 
			
		||||
				body[data-speaker-layout="default"] #speaker-controls {
 | 
			
		||||
					font-size: 12px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		</style>
 | 
			
		||||
	</head>
 | 
			
		||||
 | 
			
		||||
	<body>
 | 
			
		||||
 | 
			
		||||
		<div id="connection-status">Loading speaker view...</div>
 | 
			
		||||
 | 
			
		||||
		<div id="current-slide"></div>
 | 
			
		||||
		<div id="upcoming-slide"><span class="overlay-element 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>
 | 
			
		||||
 | 
			
		||||
				<h4 class="label pacing-title" style="display: none">Pacing – Time to finish current slide</h4>
 | 
			
		||||
				<div class="pacing" style="display: none">
 | 
			
		||||
					<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="speaker-controls-notes hidden">
 | 
			
		||||
				<h4 class="label">Notes</h4>
 | 
			
		||||
				<div class="value"></div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div id="speaker-layout" class="overlay-element interactive">
 | 
			
		||||
			<span class="speaker-layout-label"></span>
 | 
			
		||||
			<select class="speaker-layout-dropdown"></select>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<script src="../../plugin/markdown/marked.js"></script>
 | 
			
		||||
		<script>
 | 
			
		||||
 | 
			
		||||
			(function() {
 | 
			
		||||
 | 
			
		||||
				var notes,
 | 
			
		||||
					notesValue,
 | 
			
		||||
					currentState,
 | 
			
		||||
					currentSlide,
 | 
			
		||||
					upcomingSlide,
 | 
			
		||||
					layoutLabel,
 | 
			
		||||
					layoutDropdown,
 | 
			
		||||
					connected = false;
 | 
			
		||||
 | 
			
		||||
				var SPEAKER_LAYOUTS = {
 | 
			
		||||
					'default': 'Default',
 | 
			
		||||
					'wide': 'Wide',
 | 
			
		||||
					'tall': 'Tall',
 | 
			
		||||
					'notes-only': 'Notes only'
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				setupLayout();
 | 
			
		||||
 | 
			
		||||
				var connectionStatus = document.querySelector( '#connection-status' );
 | 
			
		||||
				var connectionTimeout = setTimeout( function() {
 | 
			
		||||
					connectionStatus.innerHTML = 'Error connecting to main window.<br>Please try closing and reopening the speaker view.';
 | 
			
		||||
				}, 5000 );
 | 
			
		||||
 | 
			
		||||
				window.addEventListener( 'message', function( event ) {
 | 
			
		||||
 | 
			
		||||
					clearTimeout( connectionTimeout );
 | 
			
		||||
					connectionStatus.style.display = 'none';
 | 
			
		||||
 | 
			
		||||
					var data = JSON.parse( event.data );
 | 
			
		||||
 | 
			
		||||
					// The overview mode is only useful to the reveal.js instance
 | 
			
		||||
					// where navigation occurs so we don't sync it
 | 
			
		||||
					if( data.state ) delete data.state.overview;
 | 
			
		||||
 | 
			
		||||
					// Messages sent by the notes plugin inside of the main window
 | 
			
		||||
					if( data && data.namespace === 'reveal-notes' ) {
 | 
			
		||||
						if( data.type === 'connect' ) {
 | 
			
		||||
							handleConnectMessage( data );
 | 
			
		||||
						}
 | 
			
		||||
						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|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
 | 
			
		||||
 | 
			
		||||
							window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' );
 | 
			
		||||
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				} );
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * Called when the main window is trying to establish a
 | 
			
		||||
				 * connection.
 | 
			
		||||
				 */
 | 
			
		||||
				function handleConnectMessage( data ) {
 | 
			
		||||
 | 
			
		||||
					if( connected === false ) {
 | 
			
		||||
						connected = true;
 | 
			
		||||
 | 
			
		||||
						setupIframes( data );
 | 
			
		||||
						setupKeyboard();
 | 
			
		||||
						setupNotes();
 | 
			
		||||
						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 ) {
 | 
			
		||||
						notes.classList.remove( 'hidden' );
 | 
			
		||||
						notesValue.style.whiteSpace = data.whitespace;
 | 
			
		||||
						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.
 | 
			
		||||
				 *
 | 
			
		||||
				 * Block F5 default handling, it reloads and disconnects
 | 
			
		||||
				 * the speaker notes window.
 | 
			
		||||
				 */
 | 
			
		||||
				function setupKeyboard() {
 | 
			
		||||
 | 
			
		||||
					document.addEventListener( 'keydown', function( event ) {
 | 
			
		||||
						if( event.keyCode === 116 || ( event.metaKey && event.keyCode === 82 ) ) {
 | 
			
		||||
							event.preventDefault();
 | 
			
		||||
							return false;
 | 
			
		||||
						}
 | 
			
		||||
						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',
 | 
			
		||||
						'autoSlide=0',
 | 
			
		||||
						'backgroundTransition=none'
 | 
			
		||||
					].join( '&' );
 | 
			
		||||
 | 
			
		||||
					var urlSeparator = /\?/.test(data.url) ? '&' : '?';
 | 
			
		||||
					var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
 | 
			
		||||
					var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
 | 
			
		||||
					var upcomingURL = data.url + urlSeparator + 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' );
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				function getTimings() {
 | 
			
		||||
 | 
			
		||||
					var slides = Reveal.getSlides();
 | 
			
		||||
					var defaultTiming = Reveal.getConfig().defaultTiming;
 | 
			
		||||
					if (defaultTiming == null) {
 | 
			
		||||
						return null;
 | 
			
		||||
					}
 | 
			
		||||
					var timings = [];
 | 
			
		||||
					for ( var i in slides ) {
 | 
			
		||||
						var slide = slides[i];
 | 
			
		||||
						var timing = defaultTiming;
 | 
			
		||||
						if( slide.hasAttribute( 'data-timing' )) {
 | 
			
		||||
							var t = slide.getAttribute( 'data-timing' );
 | 
			
		||||
							timing = parseInt(t);
 | 
			
		||||
							if( isNaN(timing) ) {
 | 
			
		||||
								console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
 | 
			
		||||
								timing = defaultTiming;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						timings.push(timing);
 | 
			
		||||
					}
 | 
			
		||||
					return timings;
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * Return the number of seconds allocated for presenting
 | 
			
		||||
				 * all slides up to and including this one.
 | 
			
		||||
				 */
 | 
			
		||||
				function getTimeAllocated(timings) {
 | 
			
		||||
 | 
			
		||||
					var slides = Reveal.getSlides();
 | 
			
		||||
					var allocated = 0;
 | 
			
		||||
					var currentSlide = Reveal.getSlidePastCount();
 | 
			
		||||
					for (var i in slides.slice(0, currentSlide + 1)) {
 | 
			
		||||
						allocated += timings[i];
 | 
			
		||||
					}
 | 
			
		||||
					return allocated;
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * 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' ),
 | 
			
		||||
					pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
 | 
			
		||||
					pacingEl = timeEl.querySelector( '.pacing' ),
 | 
			
		||||
					pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
 | 
			
		||||
					pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
 | 
			
		||||
					pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
 | 
			
		||||
 | 
			
		||||
					var timings = getTimings();
 | 
			
		||||
					if (timings !== null) {
 | 
			
		||||
						pacingTitleEl.style.removeProperty('display');
 | 
			
		||||
						pacingEl.style.removeProperty('display');
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					function _displayTime( hrEl, minEl, secEl, time) {
 | 
			
		||||
 | 
			
		||||
						var sign = Math.sign(time) == -1 ? "-" : "";
 | 
			
		||||
						time = Math.abs(Math.round(time / 1000));
 | 
			
		||||
						var seconds = time % 60;
 | 
			
		||||
						var minutes = Math.floor( time / 60 ) % 60 ;
 | 
			
		||||
						var hours = Math.floor( time / ( 60 * 60 )) ;
 | 
			
		||||
						hrEl.innerHTML = sign + zeroPadInteger( hours );
 | 
			
		||||
						if (hours == 0) {
 | 
			
		||||
							hrEl.classList.add( 'mute' );
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
							hrEl.classList.remove( 'mute' );
 | 
			
		||||
						}
 | 
			
		||||
						minEl.innerHTML = ':' + zeroPadInteger( minutes );
 | 
			
		||||
						if (hours == 0 && minutes == 0) {
 | 
			
		||||
							minEl.classList.add( 'mute' );
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
							minEl.classList.remove( 'mute' );
 | 
			
		||||
						}
 | 
			
		||||
						secEl.innerHTML = ':' + zeroPadInteger( seconds );
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					function _updateTimer() {
 | 
			
		||||
 | 
			
		||||
						var diff, hours, minutes, seconds,
 | 
			
		||||
						now = new Date();
 | 
			
		||||
 | 
			
		||||
						diff = now.getTime() - start.getTime();
 | 
			
		||||
 | 
			
		||||
						clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
 | 
			
		||||
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
 | 
			
		||||
						if (timings !== null) {
 | 
			
		||||
							_updatePacing(diff);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					function _updatePacing(diff) {
 | 
			
		||||
 | 
			
		||||
						var slideEndTiming = getTimeAllocated(timings) * 1000;
 | 
			
		||||
						var currentSlide = Reveal.getSlidePastCount();
 | 
			
		||||
						var currentSlideTiming = timings[currentSlide] * 1000;
 | 
			
		||||
						var timeLeftCurrentSlide = slideEndTiming - diff;
 | 
			
		||||
						if (timeLeftCurrentSlide < 0) {
 | 
			
		||||
							pacingEl.className = 'pacing behind';
 | 
			
		||||
						}
 | 
			
		||||
						else if (timeLeftCurrentSlide < currentSlideTiming) {
 | 
			
		||||
							pacingEl.className = 'pacing on-track';
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
							pacingEl.className = 'pacing ahead';
 | 
			
		||||
						}
 | 
			
		||||
						_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
 | 
			
		||||
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Update once directly
 | 
			
		||||
					_updateTimer();
 | 
			
		||||
 | 
			
		||||
					// Then update every second
 | 
			
		||||
					setInterval( _updateTimer, 1000 );
 | 
			
		||||
 | 
			
		||||
					function _resetTimer() {
 | 
			
		||||
 | 
			
		||||
						if (timings == null) {
 | 
			
		||||
							start = new Date();
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
							// Reset timer to beginning of current slide
 | 
			
		||||
							var slideEndTiming = getTimeAllocated(timings) * 1000;
 | 
			
		||||
							var currentSlide = Reveal.getSlidePastCount();
 | 
			
		||||
							var currentSlideTiming = timings[currentSlide] * 1000;
 | 
			
		||||
							var previousSlidesTiming = slideEndTiming - currentSlideTiming;
 | 
			
		||||
							var now = new Date();
 | 
			
		||||
							start = new Date(now.getTime() - previousSlidesTiming);
 | 
			
		||||
						}
 | 
			
		||||
						_updateTimer();
 | 
			
		||||
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					timeEl.addEventListener( 'click', function() {
 | 
			
		||||
						_resetTimer();
 | 
			
		||||
						return false;
 | 
			
		||||
					} );
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * Sets up the speaker view layout and layout selector.
 | 
			
		||||
				 */
 | 
			
		||||
				function setupLayout() {
 | 
			
		||||
 | 
			
		||||
					layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
 | 
			
		||||
					layoutLabel = document.querySelector( '.speaker-layout-label' );
 | 
			
		||||
 | 
			
		||||
					// Render the list of available layouts
 | 
			
		||||
					for( var id in SPEAKER_LAYOUTS ) {
 | 
			
		||||
						var option = document.createElement( 'option' );
 | 
			
		||||
						option.setAttribute( 'value', id );
 | 
			
		||||
						option.textContent = SPEAKER_LAYOUTS[ id ];
 | 
			
		||||
						layoutDropdown.appendChild( option );
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Monitor the dropdown for changes
 | 
			
		||||
					layoutDropdown.addEventListener( 'change', function( event ) {
 | 
			
		||||
 | 
			
		||||
						setLayout( layoutDropdown.value );
 | 
			
		||||
 | 
			
		||||
					}, false );
 | 
			
		||||
 | 
			
		||||
					// Restore any currently persisted layout
 | 
			
		||||
					setLayout( getLayout() );
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * Sets a new speaker view layout. The layout is persisted
 | 
			
		||||
				 * in local storage.
 | 
			
		||||
				 */
 | 
			
		||||
				function setLayout( value ) {
 | 
			
		||||
 | 
			
		||||
					var title = SPEAKER_LAYOUTS[ value ];
 | 
			
		||||
 | 
			
		||||
					layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
 | 
			
		||||
					layoutDropdown.value = value;
 | 
			
		||||
 | 
			
		||||
					document.body.setAttribute( 'data-speaker-layout', value );
 | 
			
		||||
 | 
			
		||||
					// Persist locally
 | 
			
		||||
					if( supportsLocalStorage() ) {
 | 
			
		||||
						window.localStorage.setItem( 'reveal-speaker-layout', value );
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/**
 | 
			
		||||
				 * Returns the ID of the most recently set speaker layout
 | 
			
		||||
				 * or our default layout if none has been set.
 | 
			
		||||
				 */
 | 
			
		||||
				function getLayout() {
 | 
			
		||||
 | 
			
		||||
					if( supportsLocalStorage() ) {
 | 
			
		||||
						var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
 | 
			
		||||
						if( layout ) {
 | 
			
		||||
							return layout;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Default to the first record in the layouts hash
 | 
			
		||||
					for( var id in SPEAKER_LAYOUTS ) {
 | 
			
		||||
						return id;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				function supportsLocalStorage() {
 | 
			
		||||
 | 
			
		||||
					try {
 | 
			
		||||
						localStorage.setItem('test', 'test');
 | 
			
		||||
						localStorage.removeItem('test');
 | 
			
		||||
						return true;
 | 
			
		||||
					}
 | 
			
		||||
					catch( e ) {
 | 
			
		||||
						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>
 | 
			
		||||
							
								
								
									
										147
									
								
								plugin/notes/notes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								plugin/notes/notes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Handles opening of and synchronization with the reveal.js
 | 
			
		||||
 * 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() {
 | 
			
		||||
 | 
			
		||||
	function openNotes( notesFilePath ) {
 | 
			
		||||
 | 
			
		||||
		if( !notesFilePath ) {
 | 
			
		||||
			var jsFileLocation = document.querySelector('script[src$="notes.js"]').src;  // this js file path
 | 
			
		||||
			jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, '');   // the js folder path
 | 
			
		||||
			notesFilePath = jsFileLocation + 'notes.html';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' );
 | 
			
		||||
 | 
			
		||||
		if( !notesPopup ) {
 | 
			
		||||
			alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' );
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Allow popup window access to Reveal API
 | 
			
		||||
		notesPopup.Reveal = window.Reveal;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 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 + window.location.search,
 | 
			
		||||
					state: Reveal.getState()
 | 
			
		||||
				} ), '*' );
 | 
			
		||||
			}, 500 );
 | 
			
		||||
 | 
			
		||||
			window.addEventListener( 'message', function( event ) {
 | 
			
		||||
				var data = JSON.parse( event.data );
 | 
			
		||||
				if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
 | 
			
		||||
					clearInterval( connectInterval );
 | 
			
		||||
					onConnected();
 | 
			
		||||
				}
 | 
			
		||||
			} );
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Posts the current slide data to the notes window
 | 
			
		||||
		 */
 | 
			
		||||
		function post( event ) {
 | 
			
		||||
 | 
			
		||||
			var slideElement = Reveal.getCurrentSlide(),
 | 
			
		||||
				notesElement = slideElement.querySelector( 'aside.notes' ),
 | 
			
		||||
				fragmentElement = slideElement.querySelector( '.current-fragment' );
 | 
			
		||||
 | 
			
		||||
			var messageData = {
 | 
			
		||||
				namespace: 'reveal-notes',
 | 
			
		||||
				type: 'state',
 | 
			
		||||
				notes: '',
 | 
			
		||||
				markdown: false,
 | 
			
		||||
				whitespace: 'normal',
 | 
			
		||||
				state: Reveal.getState()
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			// Look for notes defined in a slide attribute
 | 
			
		||||
			if( slideElement.hasAttribute( 'data-notes' ) ) {
 | 
			
		||||
				messageData.notes = slideElement.getAttribute( 'data-notes' );
 | 
			
		||||
				messageData.whitespace = 'pre-wrap';
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Look for notes defined in a fragment
 | 
			
		||||
			if( fragmentElement ) {
 | 
			
		||||
				var fragmentNotes = fragmentElement.querySelector( 'aside.notes' );
 | 
			
		||||
				if( fragmentNotes ) {
 | 
			
		||||
					notesElement = fragmentNotes;
 | 
			
		||||
				}
 | 
			
		||||
				else if( fragmentElement.hasAttribute( 'data-notes' ) ) {
 | 
			
		||||
					messageData.notes = fragmentElement.getAttribute( 'data-notes' );
 | 
			
		||||
					messageData.whitespace = 'pre-wrap';
 | 
			
		||||
 | 
			
		||||
					// In case there are slide notes
 | 
			
		||||
					notesElement = null;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Look for notes defined in an aside element
 | 
			
		||||
			if( notesElement ) {
 | 
			
		||||
				messageData.notes = notesElement.innerHTML;
 | 
			
		||||
				messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			notesPopup.postMessage( JSON.stringify( messageData ), '*' );
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 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();
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		connect();
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if( !/receiver/i.test( window.location.search ) ) {
 | 
			
		||||
 | 
			
		||||
		// If the there's a 'notes' query set, open directly
 | 
			
		||||
		if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
 | 
			
		||||
			openNotes();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Open the notes when the 's' key is hit
 | 
			
		||||
		Reveal.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() {
 | 
			
		||||
			openNotes();
 | 
			
		||||
		} );
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return { open: openNotes };
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										67
									
								
								plugin/print-pdf/print-pdf.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								plugin/print-pdf/print-pdf.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
/**
 | 
			
		||||
 * phantomjs script for printing presentations to PDF.
 | 
			
		||||
 *
 | 
			
		||||
 * Example:
 | 
			
		||||
 * phantomjs print-pdf.js "http://revealjs.com?print-pdf" reveal-demo.pdf
 | 
			
		||||
 *
 | 
			
		||||
 * @author Manuel Bieh (https://github.com/manuelbieh)
 | 
			
		||||
 * @author Hakim El Hattab (https://github.com/hakimel)
 | 
			
		||||
 * @author Manuel Riezebosch (https://github.com/riezebosch)
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// html2pdf.js
 | 
			
		||||
var system = require( 'system' );
 | 
			
		||||
 | 
			
		||||
var probePage = new WebPage();
 | 
			
		||||
var printPage = new WebPage();
 | 
			
		||||
 | 
			
		||||
var inputFile = system.args[1] || 'index.html?print-pdf';
 | 
			
		||||
var outputFile = system.args[2] || 'slides.pdf';
 | 
			
		||||
 | 
			
		||||
if( outputFile.match( /\.pdf$/gi ) === null ) {
 | 
			
		||||
	outputFile += '.pdf';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
console.log( 'Export PDF: Reading reveal.js config [1/4]' );
 | 
			
		||||
 | 
			
		||||
probePage.open( inputFile, function( status ) {
 | 
			
		||||
 | 
			
		||||
	console.log( 'Export PDF: Preparing print layout [2/4]' );
 | 
			
		||||
 | 
			
		||||
	var config = probePage.evaluate( function() {
 | 
			
		||||
		return Reveal.getConfig();
 | 
			
		||||
	} );
 | 
			
		||||
 | 
			
		||||
	if( config ) {
 | 
			
		||||
 | 
			
		||||
		printPage.paperSize = {
 | 
			
		||||
			width: Math.floor( config.width * ( 1 + config.margin ) ),
 | 
			
		||||
			height: Math.floor( config.height * ( 1 + config.margin ) ),
 | 
			
		||||
			border: 0
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		printPage.open( inputFile, function( status ) {
 | 
			
		||||
			console.log( 'Export PDF: Preparing pdf [3/4]')
 | 
			
		||||
			printPage.evaluate( function() {
 | 
			
		||||
				Reveal.isReady() ? window.callPhantom() : Reveal.addEventListener( 'pdf-ready', window.callPhantom );
 | 
			
		||||
			} );
 | 
			
		||||
		} );
 | 
			
		||||
 | 
			
		||||
		printPage.onCallback = function( data ) {
 | 
			
		||||
			// For some reason we need to "jump the queue" for syntax highlighting to work.
 | 
			
		||||
			// See: http://stackoverflow.com/a/3580132/129269
 | 
			
		||||
			setTimeout( function() {
 | 
			
		||||
				console.log( 'Export PDF: Writing file [4/4]' );
 | 
			
		||||
				printPage.render( outputFile );
 | 
			
		||||
				console.log( 'Export PDF: Finished successfully!' );
 | 
			
		||||
				phantom.exit();
 | 
			
		||||
			}, 0 );
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
 | 
			
		||||
		console.log( 'Export PDF: Unable to read reveal.js config. Make sure the input address points to a reveal.js page.' );
 | 
			
		||||
		phantom.exit( 1 );
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
} );
 | 
			
		||||
							
								
								
									
										206
									
								
								plugin/search/search.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								plugin/search/search.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,206 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
 | 
			
		||||
 * by navigatating to that slide and highlighting it.
 | 
			
		||||
 *
 | 
			
		||||
 * By Jon Snyder <snyder.jon@gmail.com>, February 2013
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
var RevealSearch = (function() {
 | 
			
		||||
 | 
			
		||||
	var matchedSlides;
 | 
			
		||||
	var currentMatchedIndex;
 | 
			
		||||
	var searchboxDirty;
 | 
			
		||||
	var myHilitor;
 | 
			
		||||
 | 
			
		||||
// Original JavaScript code by Chirp Internet: www.chirp.com.au
 | 
			
		||||
// Please acknowledge use of this code by including this header.
 | 
			
		||||
// 2/2013 jon: modified regex to display any match, not restricted to word boundaries.
 | 
			
		||||
 | 
			
		||||
function Hilitor(id, tag)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	var targetNode = document.getElementById(id) || document.body;
 | 
			
		||||
	var hiliteTag = tag || "EM";
 | 
			
		||||
	var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM)$");
 | 
			
		||||
	var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"];
 | 
			
		||||
	var wordColor = [];
 | 
			
		||||
	var colorIdx = 0;
 | 
			
		||||
	var matchRegex = "";
 | 
			
		||||
	var matchingSlides = [];
 | 
			
		||||
 | 
			
		||||
	this.setRegex = function(input)
 | 
			
		||||
	{
 | 
			
		||||
		input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
 | 
			
		||||
		matchRegex = new RegExp("(" + input + ")","i");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.getRegex = function()
 | 
			
		||||
	{
 | 
			
		||||
		return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " ");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// recursively apply word highlighting
 | 
			
		||||
	this.hiliteWords = function(node)
 | 
			
		||||
	{
 | 
			
		||||
		if(node == undefined || !node) return;
 | 
			
		||||
		if(!matchRegex) return;
 | 
			
		||||
		if(skipTags.test(node.nodeName)) return;
 | 
			
		||||
 | 
			
		||||
		if(node.hasChildNodes()) {
 | 
			
		||||
			for(var i=0; i < node.childNodes.length; i++)
 | 
			
		||||
				this.hiliteWords(node.childNodes[i]);
 | 
			
		||||
		}
 | 
			
		||||
		if(node.nodeType == 3) { // NODE_TEXT
 | 
			
		||||
			if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) {
 | 
			
		||||
				//find the slide's section element and save it in our list of matching slides
 | 
			
		||||
				var secnode = node;
 | 
			
		||||
				while (secnode != null && secnode.nodeName != 'SECTION') {
 | 
			
		||||
					secnode = secnode.parentNode;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var slideIndex = Reveal.getIndices(secnode);
 | 
			
		||||
				var slidelen = matchingSlides.length;
 | 
			
		||||
				var alreadyAdded = false;
 | 
			
		||||
				for (var i=0; i < slidelen; i++) {
 | 
			
		||||
					if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) {
 | 
			
		||||
						alreadyAdded = true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (! alreadyAdded) {
 | 
			
		||||
					matchingSlides.push(slideIndex);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(!wordColor[regs[0].toLowerCase()]) {
 | 
			
		||||
					wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length];
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var match = document.createElement(hiliteTag);
 | 
			
		||||
				match.appendChild(document.createTextNode(regs[0]));
 | 
			
		||||
				match.style.backgroundColor = wordColor[regs[0].toLowerCase()];
 | 
			
		||||
				match.style.fontStyle = "inherit";
 | 
			
		||||
				match.style.color = "#000";
 | 
			
		||||
 | 
			
		||||
				var after = node.splitText(regs.index);
 | 
			
		||||
				after.nodeValue = after.nodeValue.substring(regs[0].length);
 | 
			
		||||
				node.parentNode.insertBefore(match, after);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// remove highlighting
 | 
			
		||||
	this.remove = function()
 | 
			
		||||
	{
 | 
			
		||||
		var arr = document.getElementsByTagName(hiliteTag);
 | 
			
		||||
		while(arr.length && (el = arr[0])) {
 | 
			
		||||
			el.parentNode.replaceChild(el.firstChild, el);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// start highlighting at target node
 | 
			
		||||
	this.apply = function(input)
 | 
			
		||||
	{
 | 
			
		||||
		if(input == undefined || !input) return;
 | 
			
		||||
		this.remove();
 | 
			
		||||
		this.setRegex(input);
 | 
			
		||||
		this.hiliteWords(targetNode);
 | 
			
		||||
		return matchingSlides;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	function openSearch() {
 | 
			
		||||
		//ensure the search term input dialog is visible and has focus:
 | 
			
		||||
		var inputboxdiv = document.getElementById("searchinputdiv");
 | 
			
		||||
		var inputbox = document.getElementById("searchinput");
 | 
			
		||||
		inputboxdiv.style.display = "inline";
 | 
			
		||||
		inputbox.focus();
 | 
			
		||||
		inputbox.select();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function closeSearch() {
 | 
			
		||||
		var inputboxdiv = document.getElementById("searchinputdiv");
 | 
			
		||||
		inputboxdiv.style.display = "none";
 | 
			
		||||
		if(myHilitor) myHilitor.remove();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function toggleSearch() {
 | 
			
		||||
		var inputboxdiv = document.getElementById("searchinputdiv");
 | 
			
		||||
		if (inputboxdiv.style.display !== "inline") {
 | 
			
		||||
			openSearch();
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			closeSearch();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function doSearch() {
 | 
			
		||||
		//if there's been a change in the search term, perform a new search:
 | 
			
		||||
		if (searchboxDirty) {
 | 
			
		||||
			var searchstring = document.getElementById("searchinput").value;
 | 
			
		||||
 | 
			
		||||
			if (searchstring === '') {
 | 
			
		||||
				if(myHilitor) myHilitor.remove();
 | 
			
		||||
				matchedSlides = null;
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				//find the keyword amongst the slides
 | 
			
		||||
				myHilitor = new Hilitor("slidecontent");
 | 
			
		||||
				matchedSlides = myHilitor.apply(searchstring);
 | 
			
		||||
				currentMatchedIndex = 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (matchedSlides) {
 | 
			
		||||
			//navigate to the next slide that has the keyword, wrapping to the first if necessary
 | 
			
		||||
			if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) {
 | 
			
		||||
				currentMatchedIndex = 0;
 | 
			
		||||
			}
 | 
			
		||||
			if (matchedSlides.length > currentMatchedIndex) {
 | 
			
		||||
				Reveal.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v);
 | 
			
		||||
				currentMatchedIndex++;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var dom = {};
 | 
			
		||||
	dom.wrapper = document.querySelector( '.reveal' );
 | 
			
		||||
 | 
			
		||||
	if( !dom.wrapper.querySelector( '.searchbox' ) ) {
 | 
			
		||||
			var searchElement = document.createElement( 'div' );
 | 
			
		||||
			searchElement.id = "searchinputdiv";
 | 
			
		||||
			searchElement.classList.add( 'searchdiv' );
 | 
			
		||||
			searchElement.style.position = 'absolute';
 | 
			
		||||
			searchElement.style.top = '10px';
 | 
			
		||||
			searchElement.style.right = '10px';
 | 
			
		||||
			searchElement.style.zIndex = 10;
 | 
			
		||||
			//embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/:
 | 
			
		||||
			searchElement.innerHTML = '<span><input type="search" id="searchinput" class="searchinput" style="vertical-align: top;"/><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJiSURBVHjatFZNaxNBGH5md+Mmu92NVdKDRipSAyqCghgQD4L4cRe86UUtAQ+eFCxoa4/25EXBFi8eBE+eRPoDhB6KgiiixdAPCEkx2pjvTXadd9yNsflwuyUDD/O+u8PzzDPvzOwyx3EwyCZhwG3gAkp7MnpjgbopjsltcD4gjuXZZKeAR348MYLYTm3LzOs/y3j3JTfZxgXWXmTuwPHIc4VmoOmv5IrI53+AO2DdHLjkDWQ3GoEEVFXtXQOvkSnPWcyUceviLhwbDYv8/XIVj97kse7TodLvZXxYxrPUHkQ1ufXs3FEdybEIxucySOesoNvUgWU1cP3MkCBfTFdw9fGaAMVmRELq7LBw2Q3/FaAxxWIRpw+ZIr/7IouPqzUBiqmdHAv7EuhRAwf1er2Vy4x1jW3b2d5Jfvu5IPp7l2LYbcgCFFNb+FoJ7oBqEAqFMPNqFcmEgVMJDfMT+1tvN0pNjERlMS6QA5pFOKxiKVPFhakPeL3It+WGJUDxt2wFR+JhzI7v5ctkd8DXOZAkCYYxhO+lKm4+Xfqz/rIixBuNBl7eOYzkQQNzqX249mRl6zUgEcYkaJrGhUwBinVdh6IouPzwE6/DL5w4oLkH8y981aDf+uq6hlKpJESiUdNfDZi7/ehG9K6KfiA3pml0PLcsq+cSMTj2NL9ukc4UOmz7AZ3+crkC4mHujFvXNaMFB3bEr8xPS6p5O+jXxq4VZtaen7/PwzrntjcLUE0iHPS1Ud1cdiEJl/8WivZk0wXd7zWOMkeF8s0CcAmkNrC2nvXZDbbbN73ccYnZoH9bfgswAFzAe9/h3dbKAAAAAElFTkSuQmCC" id="searchbutton" class="searchicon" style="vertical-align: top; margin-top: -1px;"/></span>';
 | 
			
		||||
			dom.wrapper.appendChild( searchElement );
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	document.getElementById( 'searchbutton' ).addEventListener( 'click', function(event) {
 | 
			
		||||
		doSearch();
 | 
			
		||||
	}, false );
 | 
			
		||||
 | 
			
		||||
	document.getElementById( 'searchinput' ).addEventListener( 'keyup', function( event ) {
 | 
			
		||||
		switch (event.keyCode) {
 | 
			
		||||
			case 13:
 | 
			
		||||
				event.preventDefault();
 | 
			
		||||
				doSearch();
 | 
			
		||||
				searchboxDirty = false;
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				searchboxDirty = true;
 | 
			
		||||
		}
 | 
			
		||||
	}, false );
 | 
			
		||||
 | 
			
		||||
	document.addEventListener( 'keydown', function( event ) {
 | 
			
		||||
		if( event.key == "F" && (event.ctrlKey || event.metaKey) ) { //Control+Shift+f
 | 
			
		||||
			event.preventDefault();
 | 
			
		||||
			toggleSearch();
 | 
			
		||||
		}
 | 
			
		||||
	}, false );
 | 
			
		||||
	if( window.Reveal ) Reveal.registerKeyboardShortcut( 'Ctrl-Shift-F', 'Search' );
 | 
			
		||||
	closeSearch();
 | 
			
		||||
	return { open: openSearch };
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										272
									
								
								plugin/zoom-js/zoom.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								plugin/zoom-js/zoom.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,272 @@
 | 
			
		||||
// Custom reveal.js integration
 | 
			
		||||
(function(){
 | 
			
		||||
	var revealElement = document.querySelector( '.reveal' );
 | 
			
		||||
	if( revealElement ) {
 | 
			
		||||
 | 
			
		||||
		revealElement.addEventListener( 'mousedown', function( event ) {
 | 
			
		||||
			var defaultModifier = /Linux/.test( window.navigator.platform ) ? 'ctrl' : 'alt';
 | 
			
		||||
 | 
			
		||||
			var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : defaultModifier ) + 'Key';
 | 
			
		||||
			var zoomLevel = ( Reveal.getConfig().zoomLevel ? Reveal.getConfig().zoomLevel : 2 );
 | 
			
		||||
 | 
			
		||||
			if( event[ modifier ] && !Reveal.isOverview() ) {
 | 
			
		||||
				event.preventDefault();
 | 
			
		||||
 | 
			
		||||
				zoom.to({
 | 
			
		||||
					x: event.clientX,
 | 
			
		||||
					y: event.clientY,
 | 
			
		||||
					scale: zoomLevel,
 | 
			
		||||
					pan: false
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		} );
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * zoom.js 0.3 (modified for use with reveal.js)
 | 
			
		||||
 * http://lab.hakim.se/zoom-js
 | 
			
		||||
 * MIT licensed
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
 | 
			
		||||
 */
 | 
			
		||||
var zoom = (function(){
 | 
			
		||||
 | 
			
		||||
	// The current zoom level (scale)
 | 
			
		||||
	var level = 1;
 | 
			
		||||
 | 
			
		||||
	// The current mouse position, used for panning
 | 
			
		||||
	var mouseX = 0,
 | 
			
		||||
		mouseY = 0;
 | 
			
		||||
 | 
			
		||||
	// Timeout before pan is activated
 | 
			
		||||
	var panEngageTimeout = -1,
 | 
			
		||||
		panUpdateInterval = -1;
 | 
			
		||||
 | 
			
		||||
	// Check for transform support so that we can fallback otherwise
 | 
			
		||||
	var supportsTransforms = 	'WebkitTransform' in document.body.style ||
 | 
			
		||||
								'MozTransform' in document.body.style ||
 | 
			
		||||
								'msTransform' in document.body.style ||
 | 
			
		||||
								'OTransform' in document.body.style ||
 | 
			
		||||
								'transform' in document.body.style;
 | 
			
		||||
 | 
			
		||||
	if( supportsTransforms ) {
 | 
			
		||||
		// The easing that will be applied when we zoom in/out
 | 
			
		||||
		document.body.style.transition = 'transform 0.8s ease';
 | 
			
		||||
		document.body.style.OTransition = '-o-transform 0.8s ease';
 | 
			
		||||
		document.body.style.msTransition = '-ms-transform 0.8s ease';
 | 
			
		||||
		document.body.style.MozTransition = '-moz-transform 0.8s ease';
 | 
			
		||||
		document.body.style.WebkitTransition = '-webkit-transform 0.8s ease';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Zoom out if the user hits escape
 | 
			
		||||
	document.addEventListener( 'keyup', function( event ) {
 | 
			
		||||
		if( level !== 1 && event.keyCode === 27 ) {
 | 
			
		||||
			zoom.out();
 | 
			
		||||
		}
 | 
			
		||||
	} );
 | 
			
		||||
 | 
			
		||||
	// Monitor mouse movement for panning
 | 
			
		||||
	document.addEventListener( 'mousemove', function( event ) {
 | 
			
		||||
		if( level !== 1 ) {
 | 
			
		||||
			mouseX = event.clientX;
 | 
			
		||||
			mouseY = event.clientY;
 | 
			
		||||
		}
 | 
			
		||||
	} );
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Applies the CSS required to zoom in, prefers the use of CSS3
 | 
			
		||||
	 * transforms but falls back on zoom for IE.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param {Object} rect
 | 
			
		||||
	 * @param {Number} scale
 | 
			
		||||
	 */
 | 
			
		||||
	function magnify( rect, scale ) {
 | 
			
		||||
 | 
			
		||||
		var scrollOffset = getScrollOffset();
 | 
			
		||||
 | 
			
		||||
		// Ensure a width/height is set
 | 
			
		||||
		rect.width = rect.width || 1;
 | 
			
		||||
		rect.height = rect.height || 1;
 | 
			
		||||
 | 
			
		||||
		// Center the rect within the zoomed viewport
 | 
			
		||||
		rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
 | 
			
		||||
		rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;
 | 
			
		||||
 | 
			
		||||
		if( supportsTransforms ) {
 | 
			
		||||
			// Reset
 | 
			
		||||
			if( scale === 1 ) {
 | 
			
		||||
				document.body.style.transform = '';
 | 
			
		||||
				document.body.style.OTransform = '';
 | 
			
		||||
				document.body.style.msTransform = '';
 | 
			
		||||
				document.body.style.MozTransform = '';
 | 
			
		||||
				document.body.style.WebkitTransform = '';
 | 
			
		||||
			}
 | 
			
		||||
			// Scale
 | 
			
		||||
			else {
 | 
			
		||||
				var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
 | 
			
		||||
					transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';
 | 
			
		||||
 | 
			
		||||
				document.body.style.transformOrigin = origin;
 | 
			
		||||
				document.body.style.OTransformOrigin = origin;
 | 
			
		||||
				document.body.style.msTransformOrigin = origin;
 | 
			
		||||
				document.body.style.MozTransformOrigin = origin;
 | 
			
		||||
				document.body.style.WebkitTransformOrigin = origin;
 | 
			
		||||
 | 
			
		||||
				document.body.style.transform = transform;
 | 
			
		||||
				document.body.style.OTransform = transform;
 | 
			
		||||
				document.body.style.msTransform = transform;
 | 
			
		||||
				document.body.style.MozTransform = transform;
 | 
			
		||||
				document.body.style.WebkitTransform = transform;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			// Reset
 | 
			
		||||
			if( scale === 1 ) {
 | 
			
		||||
				document.body.style.position = '';
 | 
			
		||||
				document.body.style.left = '';
 | 
			
		||||
				document.body.style.top = '';
 | 
			
		||||
				document.body.style.width = '';
 | 
			
		||||
				document.body.style.height = '';
 | 
			
		||||
				document.body.style.zoom = '';
 | 
			
		||||
			}
 | 
			
		||||
			// Scale
 | 
			
		||||
			else {
 | 
			
		||||
				document.body.style.position = 'relative';
 | 
			
		||||
				document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
 | 
			
		||||
				document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
 | 
			
		||||
				document.body.style.width = ( scale * 100 ) + '%';
 | 
			
		||||
				document.body.style.height = ( scale * 100 ) + '%';
 | 
			
		||||
				document.body.style.zoom = scale;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		level = scale;
 | 
			
		||||
 | 
			
		||||
		if( document.documentElement.classList ) {
 | 
			
		||||
			if( level !== 1 ) {
 | 
			
		||||
				document.documentElement.classList.add( 'zoomed' );
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				document.documentElement.classList.remove( 'zoomed' );
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Pan the document when the mosue cursor approaches the edges
 | 
			
		||||
	 * of the window.
 | 
			
		||||
	 */
 | 
			
		||||
	function pan() {
 | 
			
		||||
		var range = 0.12,
 | 
			
		||||
			rangeX = window.innerWidth * range,
 | 
			
		||||
			rangeY = window.innerHeight * range,
 | 
			
		||||
			scrollOffset = getScrollOffset();
 | 
			
		||||
 | 
			
		||||
		// Up
 | 
			
		||||
		if( mouseY < rangeY ) {
 | 
			
		||||
			window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
 | 
			
		||||
		}
 | 
			
		||||
		// Down
 | 
			
		||||
		else if( mouseY > window.innerHeight - rangeY ) {
 | 
			
		||||
			window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Left
 | 
			
		||||
		if( mouseX < rangeX ) {
 | 
			
		||||
			window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
 | 
			
		||||
		}
 | 
			
		||||
		// Right
 | 
			
		||||
		else if( mouseX > window.innerWidth - rangeX ) {
 | 
			
		||||
			window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function getScrollOffset() {
 | 
			
		||||
		return {
 | 
			
		||||
			x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
 | 
			
		||||
			y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		/**
 | 
			
		||||
		 * Zooms in on either a rectangle or HTML element.
 | 
			
		||||
		 *
 | 
			
		||||
		 * @param {Object} options
 | 
			
		||||
		 *   - element: HTML element to zoom in on
 | 
			
		||||
		 *   OR
 | 
			
		||||
		 *   - x/y: coordinates in non-transformed space to zoom in on
 | 
			
		||||
		 *   - width/height: the portion of the screen to zoom in on
 | 
			
		||||
		 *   - scale: can be used instead of width/height to explicitly set scale
 | 
			
		||||
		 */
 | 
			
		||||
		to: function( options ) {
 | 
			
		||||
 | 
			
		||||
			// Due to an implementation limitation we can't zoom in
 | 
			
		||||
			// to another element without zooming out first
 | 
			
		||||
			if( level !== 1 ) {
 | 
			
		||||
				zoom.out();
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				options.x = options.x || 0;
 | 
			
		||||
				options.y = options.y || 0;
 | 
			
		||||
 | 
			
		||||
				// If an element is set, that takes precedence
 | 
			
		||||
				if( !!options.element ) {
 | 
			
		||||
					// Space around the zoomed in element to leave on screen
 | 
			
		||||
					var padding = 20;
 | 
			
		||||
					var bounds = options.element.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
					options.x = bounds.left - padding;
 | 
			
		||||
					options.y = bounds.top - padding;
 | 
			
		||||
					options.width = bounds.width + ( padding * 2 );
 | 
			
		||||
					options.height = bounds.height + ( padding * 2 );
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// If width/height values are set, calculate scale from those values
 | 
			
		||||
				if( options.width !== undefined && options.height !== undefined ) {
 | 
			
		||||
					options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if( options.scale > 1 ) {
 | 
			
		||||
					options.x *= options.scale;
 | 
			
		||||
					options.y *= options.scale;
 | 
			
		||||
 | 
			
		||||
					magnify( options, options.scale );
 | 
			
		||||
 | 
			
		||||
					if( options.pan !== false ) {
 | 
			
		||||
 | 
			
		||||
						// Wait with engaging panning as it may conflict with the
 | 
			
		||||
						// zoom transition
 | 
			
		||||
						panEngageTimeout = setTimeout( function() {
 | 
			
		||||
							panUpdateInterval = setInterval( pan, 1000 / 60 );
 | 
			
		||||
						}, 800 );
 | 
			
		||||
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Resets the document zoom state to its default.
 | 
			
		||||
		 */
 | 
			
		||||
		out: function() {
 | 
			
		||||
			clearTimeout( panEngageTimeout );
 | 
			
		||||
			clearInterval( panUpdateInterval );
 | 
			
		||||
 | 
			
		||||
			magnify( { x: 0, y: 0 }, 1 );
 | 
			
		||||
 | 
			
		||||
			level = 1;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// Alias
 | 
			
		||||
		magnify: function( options ) { this.to( options ) },
 | 
			
		||||
		reset: function() { this.out() },
 | 
			
		||||
 | 
			
		||||
		zoomLevel: function() {
 | 
			
		||||
			return level;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
		Reference in New Issue
	
	Block a user