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 }));