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