github.com/mweagle/Sparta@v1.15.0/docs_source/static/presentations/reveal.js-3.9.2/plugin/markdown/markdown.js (about)

     1  /**
     2   * The reveal.js markdown plugin. Handles parsing of
     3   * markdown inside of presentations as well as loading
     4   * of external markdown documents.
     5   */
     6  (function( root, factory ) {
     7  	if (typeof define === 'function' && define.amd) {
     8  		root.marked = require( './marked' );
     9  		root.RevealMarkdown = factory( root.marked );
    10  	} else if( typeof exports === 'object' ) {
    11  		module.exports = factory( require( './marked' ) );
    12  	} else {
    13  		// Browser globals (root is window)
    14  		root.RevealMarkdown = factory( root.marked );
    15  	}
    16  }( this, function( marked ) {
    17  
    18  	var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
    19  		DEFAULT_NOTES_SEPARATOR = 'notes?:',
    20  		DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
    21  		DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
    22  
    23  	var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
    24  
    25  
    26  	/**
    27  	 * Retrieves the markdown contents of a slide section
    28  	 * element. Normalizes leading tabs/whitespace.
    29  	 */
    30  	function getMarkdownFromSlide( section ) {
    31  
    32  		// look for a <script> or <textarea data-template> wrapper
    33  		var template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
    34  
    35  		// strip leading whitespace so it isn't evaluated as code
    36  		var text = ( template || section ).textContent;
    37  
    38  		// restore script end tags
    39  		text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
    40  
    41  		var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
    42  			leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
    43  
    44  		if( leadingTabs > 0 ) {
    45  			text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
    46  		}
    47  		else if( leadingWs > 1 ) {
    48  			text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
    49  		}
    50  
    51  		return text;
    52  
    53  	}
    54  
    55  	/**
    56  	 * Given a markdown slide section element, this will
    57  	 * return all arguments that aren't related to markdown
    58  	 * parsing. Used to forward any other user-defined arguments
    59  	 * to the output markdown slide.
    60  	 */
    61  	function getForwardedAttributes( section ) {
    62  
    63  		var attributes = section.attributes;
    64  		var result = [];
    65  
    66  		for( var i = 0, len = attributes.length; i < len; i++ ) {
    67  			var name = attributes[i].name,
    68  				value = attributes[i].value;
    69  
    70  			// disregard attributes that are used for markdown loading/parsing
    71  			if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
    72  
    73  			if( value ) {
    74  				result.push( name + '="' + value + '"' );
    75  			}
    76  			else {
    77  				result.push( name );
    78  			}
    79  		}
    80  
    81  		return result.join( ' ' );
    82  
    83  	}
    84  
    85  	/**
    86  	 * Inspects the given options and fills out default
    87  	 * values for what's not defined.
    88  	 */
    89  	function getSlidifyOptions( options ) {
    90  
    91  		options = options || {};
    92  		options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
    93  		options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
    94  		options.attributes = options.attributes || '';
    95  
    96  		return options;
    97  
    98  	}
    99  
   100  	/**
   101  	 * Helper function for constructing a markdown slide.
   102  	 */
   103  	function createMarkdownSlide( content, options ) {
   104  
   105  		options = getSlidifyOptions( options );
   106  
   107  		var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
   108  
   109  		if( notesMatch.length === 2 ) {
   110  			content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
   111  		}
   112  
   113  		// prevent script end tags in the content from interfering
   114  		// with parsing
   115  		content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
   116  
   117  		return '<script type="text/template">' + content + '</script>';
   118  
   119  	}
   120  
   121  	/**
   122  	 * Parses a data string into multiple slides based
   123  	 * on the passed in separator arguments.
   124  	 */
   125  	function slidify( markdown, options ) {
   126  
   127  		options = getSlidifyOptions( options );
   128  
   129  		var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
   130  			horizontalSeparatorRegex = new RegExp( options.separator );
   131  
   132  		var matches,
   133  			lastIndex = 0,
   134  			isHorizontal,
   135  			wasHorizontal = true,
   136  			content,
   137  			sectionStack = [];
   138  
   139  		// iterate until all blocks between separators are stacked up
   140  		while( matches = separatorRegex.exec( markdown ) ) {
   141  			notes = null;
   142  
   143  			// determine direction (horizontal by default)
   144  			isHorizontal = horizontalSeparatorRegex.test( matches[0] );
   145  
   146  			if( !isHorizontal && wasHorizontal ) {
   147  				// create vertical stack
   148  				sectionStack.push( [] );
   149  			}
   150  
   151  			// pluck slide content from markdown input
   152  			content = markdown.substring( lastIndex, matches.index );
   153  
   154  			if( isHorizontal && wasHorizontal ) {
   155  				// add to horizontal stack
   156  				sectionStack.push( content );
   157  			}
   158  			else {
   159  				// add to vertical stack
   160  				sectionStack[sectionStack.length-1].push( content );
   161  			}
   162  
   163  			lastIndex = separatorRegex.lastIndex;
   164  			wasHorizontal = isHorizontal;
   165  		}
   166  
   167  		// add the remaining slide
   168  		( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
   169  
   170  		var markdownSections = '';
   171  
   172  		// flatten the hierarchical stack, and insert <section data-markdown> tags
   173  		for( var i = 0, len = sectionStack.length; i < len; i++ ) {
   174  			// vertical
   175  			if( sectionStack[i] instanceof Array ) {
   176  				markdownSections += '<section '+ options.attributes +'>';
   177  
   178  				sectionStack[i].forEach( function( child ) {
   179  					markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
   180  				} );
   181  
   182  				markdownSections += '</section>';
   183  			}
   184  			else {
   185  				markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
   186  			}
   187  		}
   188  
   189  		return markdownSections;
   190  
   191  	}
   192  
   193  	/**
   194  	 * Parses any current data-markdown slides, splits
   195  	 * multi-slide markdown into separate sections and
   196  	 * handles loading of external markdown.
   197  	 */
   198  	function processSlides() {
   199  
   200  		return new Promise( function( resolve ) {
   201  
   202  			var externalPromises = [];
   203  
   204  			[].slice.call( document.querySelectorAll( '[data-markdown]') ).forEach( function( section, i ) {
   205  
   206  				if( section.getAttribute( 'data-markdown' ).length ) {
   207  
   208  					externalPromises.push( loadExternalMarkdown( section ).then(
   209  
   210  						// Finished loading external file
   211  						function( xhr, url ) {
   212  							section.outerHTML = slidify( xhr.responseText, {
   213  								separator: section.getAttribute( 'data-separator' ),
   214  								verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
   215  								notesSeparator: section.getAttribute( 'data-separator-notes' ),
   216  								attributes: getForwardedAttributes( section )
   217  							});
   218  						},
   219  
   220  						// Failed to load markdown
   221  						function( xhr, url ) {
   222  							section.outerHTML = '<section data-state="alert">' +
   223  								'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
   224  								'Check your browser\'s JavaScript console for more details.' +
   225  								'<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
   226  								'</section>';
   227  						}
   228  
   229  					) );
   230  
   231  				}
   232  				else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
   233  
   234  					section.outerHTML = slidify( getMarkdownFromSlide( section ), {
   235  						separator: section.getAttribute( 'data-separator' ),
   236  						verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
   237  						notesSeparator: section.getAttribute( 'data-separator-notes' ),
   238  						attributes: getForwardedAttributes( section )
   239  					});
   240  
   241  				}
   242  				else {
   243  					section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
   244  				}
   245  
   246  			});
   247  
   248  			Promise.all( externalPromises ).then( resolve );
   249  
   250  		} );
   251  
   252  	}
   253  
   254  	function loadExternalMarkdown( section ) {
   255  
   256  		return new Promise( function( resolve, reject ) {
   257  
   258  			var xhr = new XMLHttpRequest(),
   259  				url = section.getAttribute( 'data-markdown' );
   260  
   261  			datacharset = section.getAttribute( 'data-charset' );
   262  
   263  			// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
   264  			if( datacharset != null && datacharset != '' ) {
   265  				xhr.overrideMimeType( 'text/html; charset=' + datacharset );
   266  			}
   267  
   268  			xhr.onreadystatechange = function( section, xhr ) {
   269  				if( xhr.readyState === 4 ) {
   270  					// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
   271  					if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
   272  
   273  						resolve( xhr, url );
   274  
   275  					}
   276  					else {
   277  
   278  						reject( xhr, url );
   279  
   280  					}
   281  				}
   282  			}.bind( this, section, xhr );
   283  
   284  			xhr.open( 'GET', url, true );
   285  
   286  			try {
   287  				xhr.send();
   288  			}
   289  			catch ( e ) {
   290  				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 );
   291  				resolve( xhr, url );
   292  			}
   293  
   294  		} );
   295  
   296  	}
   297  
   298  	/**
   299  	 * Check if a node value has the attributes pattern.
   300  	 * If yes, extract it and add that value as one or several attributes
   301  	 * to the target element.
   302  	 *
   303  	 * You need Cache Killer on Chrome to see the effect on any FOM transformation
   304  	 * directly on refresh (F5)
   305  	 * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
   306  	 */
   307  	function addAttributeInElement( node, elementTarget, separator ) {
   308  
   309  		var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
   310  		var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
   311  		var nodeValue = node.nodeValue;
   312  		if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
   313  
   314  			var classes = matches[1];
   315  			nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
   316  			node.nodeValue = nodeValue;
   317  			while( matchesClass = mardownClassRegex.exec( classes ) ) {
   318  				elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
   319  			}
   320  			return true;
   321  		}
   322  		return false;
   323  	}
   324  
   325  	/**
   326  	 * Add attributes to the parent element of a text node,
   327  	 * or the element of an attribute node.
   328  	 */
   329  	function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
   330  
   331  		if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
   332  			previousParentElement = element;
   333  			for( var i = 0; i < element.childNodes.length; i++ ) {
   334  				childElement = element.childNodes[i];
   335  				if ( i > 0 ) {
   336  					j = i - 1;
   337  					while ( j >= 0 ) {
   338  						aPreviousChildElement = element.childNodes[j];
   339  						if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
   340  							previousParentElement = aPreviousChildElement;
   341  							break;
   342  						}
   343  						j = j - 1;
   344  					}
   345  				}
   346  				parentSection = section;
   347  				if( childElement.nodeName ==  "section" ) {
   348  					parentSection = childElement ;
   349  					previousParentElement = childElement ;
   350  				}
   351  				if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
   352  					addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
   353  				}
   354  			}
   355  		}
   356  
   357  		if ( element.nodeType == Node.COMMENT_NODE ) {
   358  			if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
   359  				addAttributeInElement( element, section, separatorSectionAttributes );
   360  			}
   361  		}
   362  	}
   363  
   364  	/**
   365  	 * Converts any current data-markdown slides in the
   366  	 * DOM to HTML.
   367  	 */
   368  	function convertSlides() {
   369  
   370  		var sections = document.querySelectorAll( '[data-markdown]:not([data-markdown-parsed])');
   371  
   372  		[].slice.call( sections ).forEach( function( section ) {
   373  
   374  			section.setAttribute( 'data-markdown-parsed', true )
   375  
   376  			var notes = section.querySelector( 'aside.notes' );
   377  			var markdown = getMarkdownFromSlide( section );
   378  
   379  			section.innerHTML = marked( markdown );
   380  			addAttributes( 	section, section, null, section.getAttribute( 'data-element-attributes' ) ||
   381  							section.parentNode.getAttribute( 'data-element-attributes' ) ||
   382  							DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
   383  							section.getAttribute( 'data-attributes' ) ||
   384  							section.parentNode.getAttribute( 'data-attributes' ) ||
   385  							DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
   386  
   387  			// If there were notes, we need to re-add them after
   388  			// having overwritten the section's HTML
   389  			if( notes ) {
   390  				section.appendChild( notes );
   391  			}
   392  
   393  		} );
   394  
   395  		return Promise.resolve();
   396  
   397  	}
   398  
   399  	// API
   400  	var RevealMarkdown = {
   401  
   402  		/**
   403  		 * Starts processing and converting Markdown within the
   404  		 * current reveal.js deck.
   405  		 *
   406  		 * @param {function} callback function to invoke once
   407  		 * we've finished loading and parsing Markdown
   408  		 */
   409  		init: function( callback ) {
   410  
   411  			if( typeof marked === 'undefined' ) {
   412  				throw 'The reveal.js Markdown plugin requires marked to be loaded';
   413  			}
   414  
   415  			if( typeof hljs !== 'undefined' ) {
   416  				marked.setOptions({
   417  					highlight: function( code, lang ) {
   418  						return hljs.highlightAuto( code, [lang] ).value;
   419  					}
   420  				});
   421  			}
   422  
   423  			// marked can be configured via reveal.js config options
   424  			var options = Reveal.getConfig().markdown;
   425  			if( options ) {
   426  				marked.setOptions( options );
   427  			}
   428  
   429  			return processSlides().then( convertSlides );
   430  
   431  		},
   432  
   433  		// TODO: Do these belong in the API?
   434  		processSlides: processSlides,
   435  		convertSlides: convertSlides,
   436  		slidify: slidify
   437  
   438  	};
   439  
   440  	// Register our plugin so that reveal.js will call our
   441  	// plugin 'init' method as part of the initialization
   442  	Reveal.registerPlugin( 'markdown', RevealMarkdown );
   443  
   444  	return RevealMarkdown;
   445  
   446  }));