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