markdown.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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(){
  7. if( typeof marked === 'undefined' ) {
  8. throw 'The reveal.js Markdown plugin requires marked to be loaded';
  9. }
  10. if( typeof hljs !== 'undefined' ) {
  11. marked.setOptions({
  12. highlight: function( lang, code ) {
  13. return hljs.highlightAuto( lang, code ).value;
  14. }
  15. });
  16. }
  17. function stripLeadingWhitespace( section ) {
  18. var template = section.querySelector( 'script' );
  19. // strip leading whitespace so it isn't evaluated as code
  20. var text = ( template || section ).textContent;
  21. var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
  22. leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
  23. if( leadingTabs > 0 ) {
  24. text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
  25. }
  26. else if( leadingWs > 1 ) {
  27. text = text.replace( new RegExp('\\n? {' + leadingWs + '}','g'), '\n' );
  28. }
  29. return text;
  30. };
  31. function createSlide( data ) {
  32. var content = data.content || data;
  33. if( data.notes ) {
  34. content += '<aside class="notes" data-markdown>' + data.notes + '</aside>';
  35. }
  36. return '<script type="text/template">' + content + '</script>';
  37. };
  38. function getForwardedAttributes( section ) {
  39. var attributes = section.attributes;
  40. var result = [];
  41. for( var i = 0, len = attributes.length; i < len; i++ ) {
  42. var name = attributes[i].name,
  43. value = attributes[i].value;
  44. // disregard attributes that are used for markdown loading/parsing
  45. if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
  46. if( value ) {
  47. result.push( name + '=' + value );
  48. }
  49. else {
  50. result.push( name );
  51. }
  52. }
  53. return result.join( ' ' );
  54. };
  55. function slidifyMarkdown( markdown, separator, verticalSeparator, noteSeparator, attributes ) {
  56. separator = separator || '^\n---\n$';
  57. noteSeparator = noteSeparator || 'note:';
  58. var separatorRegex = new RegExp( separator + ( verticalSeparator ? '|' + verticalSeparator : '' ), 'mg' ),
  59. horizontalSeparatorRegex = new RegExp( separator ),
  60. notesSeparatorRegex = new RegExp( noteSeparator, 'mgi' ),
  61. matches,
  62. noteMatch,
  63. lastIndex = 0,
  64. isHorizontal,
  65. wasHorizontal = true,
  66. content,
  67. notes,
  68. slide,
  69. sectionStack = [],
  70. markdownSections = '';
  71. // iterate until all blocks between separators are stacked up
  72. while( matches = separatorRegex.exec( markdown ) ) {
  73. notes = null;
  74. // determine direction (horizontal by default)
  75. isHorizontal = horizontalSeparatorRegex.test( matches[0] );
  76. if( !isHorizontal && wasHorizontal ) {
  77. // create vertical stack
  78. sectionStack.push( [] );
  79. }
  80. // pluck slide content from markdown input
  81. content = markdown.substring( lastIndex, matches.index );
  82. noteMatch = content.split( notesSeparatorRegex );
  83. if( noteMatch.length === 2 ) {
  84. content = noteMatch[0];
  85. notes = noteMatch[1].trim();
  86. }
  87. slide = {
  88. content: content,
  89. notes: notes || ''
  90. };
  91. if( isHorizontal && wasHorizontal ) {
  92. // add to horizontal stack
  93. sectionStack.push( slide );
  94. } else {
  95. // add to vertical stack
  96. sectionStack[sectionStack.length-1].push( slide );
  97. }
  98. lastIndex = separatorRegex.lastIndex;
  99. wasHorizontal = isHorizontal;
  100. }
  101. // add the remaining slide
  102. ( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
  103. // flatten the hierarchical stack, and insert <section data-markdown> tags
  104. for( var k = 0, klen = sectionStack.length; k < klen; k++ ) {
  105. // vertical
  106. if( sectionStack[k].propertyIsEnumerable( length ) && typeof sectionStack[k].splice === 'function' ) {
  107. markdownSections += '<section '+ attributes +'>' +
  108. '<section data-markdown>' + sectionStack[k].map( createSlide ).join( '</section><section data-markdown>' ) + '</section>' +
  109. '</section>';
  110. } else {
  111. markdownSections += '<section '+ attributes +' data-markdown>' + createSlide( sectionStack[k] ) + '</section>';
  112. }
  113. }
  114. return markdownSections;
  115. };
  116. function queryExternalMarkdown() {
  117. var sections = document.querySelectorAll( '[data-markdown]'),
  118. section;
  119. for( var j = 0, jlen = sections.length; j < jlen; j++ ) {
  120. section = sections[j];
  121. if( section.getAttribute( 'data-markdown' ).length ) {
  122. var xhr = new XMLHttpRequest(),
  123. url = section.getAttribute( 'data-markdown' );
  124. datacharset = section.getAttribute( 'data-charset' );
  125. // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
  126. if( datacharset != null && datacharset != '' ) {
  127. xhr.overrideMimeType( 'text/html; charset=' + datacharset );
  128. }
  129. xhr.onreadystatechange = function() {
  130. if( xhr.readyState === 4 ) {
  131. if ( xhr.status >= 200 && xhr.status < 300 ) {
  132. section.outerHTML = slidifyMarkdown( xhr.responseText, section.getAttribute( 'data-separator' ), section.getAttribute( 'data-vertical' ), section.getAttribute( 'data-notes' ), getForwardedAttributes( section ) );
  133. }
  134. else {
  135. section.outerHTML = '<section data-state="alert">ERROR: The attempt to fetch ' + url + ' failed with the HTTP status ' + xhr.status +
  136. '. Check your browser\'s JavaScript console for more details.' +
  137. '<p>Remember that you need to serve the presentation HTML from a HTTP server and the Markdown file must be there too.</p></section>';
  138. }
  139. }
  140. };
  141. xhr.open( 'GET', url, false );
  142. try {
  143. xhr.send();
  144. }
  145. catch ( e ) {
  146. 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 );
  147. }
  148. } else if( section.getAttribute( 'data-separator' ) ) {
  149. var markdown = stripLeadingWhitespace( section );
  150. section.outerHTML = slidifyMarkdown( markdown, section.getAttribute( 'data-separator' ), section.getAttribute( 'data-vertical' ), section.getAttribute( 'data-notes' ), getForwardedAttributes( section ) );
  151. }
  152. }
  153. };
  154. function queryMarkdownSlides() {
  155. var sections = document.querySelectorAll( '[data-markdown]');
  156. for( var j = 0, jlen = sections.length; j < jlen; j++ ) {
  157. makeHtml( sections[j] );
  158. }
  159. };
  160. function makeHtml( section ) {
  161. var notes = section.querySelector( 'aside.notes' );
  162. var markdown = stripLeadingWhitespace( section );
  163. section.innerHTML = marked( markdown );
  164. if( notes ) {
  165. section.appendChild( notes );
  166. }
  167. };
  168. queryExternalMarkdown();
  169. queryMarkdownSlides();
  170. })();