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