aboutsummaryrefslogtreecommitdiff
path: root/js/lib/html.js
blob: 3c1ed4c668a499e6f08135de20824dd1835a8798 (plain)
  1. "use strict";
  2. // Helper function to produce an HTML tag.
  3. var tag = function(name, attribs, selfclosing) {
  4. var result = '<' + name;
  5. if (attribs) {
  6. var i = 0;
  7. var attrib;
  8. while ((attrib = attribs[i]) !== undefined) {
  9. result = result.concat(' ', attrib[0], '="', attrib[1], '"');
  10. i++;
  11. }
  12. }
  13. if (selfclosing) {
  14. result += ' /';
  15. }
  16. result += '>';
  17. return result;
  18. };
  19. var renderNodes = function(block) {
  20. var attrs;
  21. var info_words;
  22. var tagname;
  23. var walker = block.walker();
  24. var event, node, entering;
  25. var buffer = [];
  26. var disableTags = 0;
  27. var grandparent;
  28. var out = function(s) {
  29. if (disableTags > 0) {
  30. buffer.push(s.replace(/\<[^>]*\>/g, ''));
  31. } else {
  32. buffer.push(s);
  33. }
  34. };
  35. var esc = this.escape;
  36. var cr = function() {
  37. if (buffer.length > 0 && buffer[buffer.length - 1] !== '\n') {
  38. out('\n');
  39. }
  40. };
  41. while ((event = walker.next())) {
  42. entering = event.entering;
  43. node = event.node;
  44. switch (node.t) {
  45. case 'Text':
  46. out(esc(node.c));
  47. break;
  48. case 'Softbreak':
  49. out(this.softbreak);
  50. break;
  51. case 'Hardbreak':
  52. out(tag('br', [], true));
  53. cr();
  54. break;
  55. case 'Emph':
  56. out(tag(entering ? 'em' : '/em'));
  57. break;
  58. case 'Strong':
  59. out(tag(entering ? 'strong' : '/strong'));
  60. break;
  61. case 'Emph':
  62. out(tag(entering ? 'strong' : '/strong'));
  63. break;
  64. case 'Html':
  65. out(node.c);
  66. break;
  67. case 'Link':
  68. if (entering) {
  69. attrs = [['href', esc(node.destination, true)]];
  70. if (node.title) {
  71. attrs.push(['title', esc(node.title, true)]);
  72. }
  73. out(tag('a', attrs));
  74. } else {
  75. out(tag('/a'));
  76. }
  77. break;
  78. case 'Image':
  79. if (entering) {
  80. if (disableTags === 0) {
  81. out('<img src="' + esc(node.destination, true) +
  82. '" alt="');
  83. }
  84. disableTags += 1;
  85. } else {
  86. disableTags -= 1;
  87. if (disableTags === 0) {
  88. if (node.title) {
  89. out('" title="' + esc(node.title, true));
  90. }
  91. out('" />');
  92. }
  93. }
  94. break;
  95. case 'Code':
  96. out(tag('code') + esc(node.c) + tag('/code'));
  97. break;
  98. case 'Document':
  99. break;
  100. case 'Paragraph':
  101. grandparent = node.parent.parent;
  102. if (grandparent !== null &&
  103. grandparent.t === 'List') {
  104. if (grandparent.list_data.tight) {
  105. break;
  106. }
  107. }
  108. if (entering) {
  109. cr();
  110. out(tag('p'));
  111. } else {
  112. out(tag('/p'));
  113. cr();
  114. }
  115. break;
  116. case 'BlockQuote':
  117. if (entering) {
  118. cr();
  119. out(tag('blockquote'));
  120. cr();
  121. } else {
  122. cr();
  123. out(tag('/blockquote'));
  124. cr();
  125. }
  126. break;
  127. case 'ListItem':
  128. if (entering) {
  129. out(tag('li'));
  130. } else {
  131. out(tag('/li'));
  132. cr();
  133. }
  134. break;
  135. case 'List':
  136. tagname = node.list_data.type === 'Bullet' ? 'ul' : 'ol';
  137. if (entering) {
  138. attrs = (!node.list_data.start || node.list_data.start === 1) ?
  139. [] : [['start', node.list_data.start.toString()]];
  140. cr();
  141. out(tag(tagname, attrs));
  142. cr();
  143. } else {
  144. cr();
  145. out(tag('/' + tagname));
  146. cr();
  147. }
  148. break;
  149. case 'Header':
  150. tagname = 'h' + node.level;
  151. if (entering) {
  152. cr();
  153. out(tag(tagname));
  154. } else {
  155. out(tag('/' + tagname));
  156. cr();
  157. }
  158. break;
  159. case 'CodeBlock':
  160. info_words = node.info ? node.info.split(/ +/) : [];
  161. attrs = (info_words.length === 0 || info_words[0].length === 0)
  162. ? [] : [['class', 'language-' + esc(info_words[0], true)]];
  163. cr();
  164. out(tag('pre') + tag('code', attrs));
  165. out(this.escape(node.string_content));
  166. out(tag('/code') + tag('/pre'));
  167. cr();
  168. break;
  169. case 'HtmlBlock':
  170. cr();
  171. out(node.string_content);
  172. cr();
  173. break;
  174. case 'HorizontalRule':
  175. cr();
  176. out(tag('hr', [], true));
  177. cr();
  178. break;
  179. case 'ReferenceDef':
  180. break;
  181. default:
  182. console.log("Unknown node type " + node.t);
  183. }
  184. }
  185. return buffer.join('');
  186. };
  187. // The HtmlRenderer object.
  188. function HtmlRenderer(){
  189. return {
  190. // default options:
  191. blocksep: '\n', // space between blocks
  192. innersep: '\n', // space between block container tag and contents
  193. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  194. // set to "<br />" to make them hard breaks
  195. // set to " " if you want to ignore line wrapping in source
  196. escape: function(s, preserve_entities) {
  197. if (preserve_entities) {
  198. return s.replace(/[&](?![#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)/gi, '&amp;')
  199. .replace(/[<]/g, '&lt;')
  200. .replace(/[>]/g, '&gt;')
  201. .replace(/["]/g, '&quot;');
  202. } else {
  203. return s.replace(/[&]/g, '&amp;')
  204. .replace(/[<]/g, '&lt;')
  205. .replace(/[>]/g, '&gt;')
  206. .replace(/["]/g, '&quot;');
  207. }
  208. },
  209. render: renderNodes
  210. };
  211. }
  212. module.exports = HtmlRenderer;