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