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