Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

278 lines
9.9 KiB

  1. const pandoc = require('node-pandoc');
  2. const utils = require('../utils/utils.js');
  3. const sql = require('../utils/sql');
  4. const argsFull = '--from markdown-markdown_in_html_blocks+raw_html --toc --toc-depth=3 -N --mathjax -t html5 --no-highlight';
  5. const argsPreview = '--mathjax -t html5';
  6. module.exports=
  7. {
  8. /**
  9. * Renders the entire blog post based on the sql data pulled
  10. * from the database.
  11. *
  12. * @param post sql data which has title, date, and header img location
  13. * @param blocks number of blocks to display for a preview or -1 for
  14. * all the blocks
  15. * @returns {Promise} async call which renders the entire blog post.
  16. */
  17. generateBlogPost: function(post, blocks)
  18. {
  19. return new Promise(function(resolve, reject)
  20. {
  21. Promise.all([module.exports.generateBlogPostHeader(post),
  22. module.exports.generateBlogPostBody(post, blocks)])
  23. .then(function()
  24. {
  25. resolve(post);
  26. }).catch(function(error)
  27. {
  28. reject(error);
  29. })
  30. });
  31. },
  32. /**
  33. * Renders the header of the blog post which contains the header image, and date
  34. * published.
  35. *
  36. * @param post sql data
  37. * @returns {string}
  38. */
  39. generateBlogPostHeader: function(post)
  40. {
  41. if(post.picture_url !== "n/a")
  42. post.hasPicture = true;
  43. post.published = post.published.toDateString();
  44. return;
  45. },
  46. /**
  47. * Method which renders the body of the blog post. This is responsible for getting
  48. * the contents of the markdown/latex file and rendering it into beautiful html.
  49. *
  50. * @param post stuff from the SQL table
  51. * @param blocks
  52. * @returns {Promise}
  53. */
  54. generateBlogPostBody: function(post, blocks)
  55. {
  56. return new Promise(function(resolve, reject)
  57. {
  58. sql.getCategory(post.category_id).then(function(category)
  59. {
  60. module.exports.generateBlogPostComponent(category[0].url, post.url, blocks).then(function(html)
  61. {
  62. post.categoryURL = category[0].url;
  63. post.blogBody = html;
  64. resolve();
  65. });
  66. });
  67. })
  68. },
  69. /**
  70. * Decomposition from Generate Blog Post used for the
  71. * blog previewer.
  72. *
  73. * @param categoryURL
  74. * @param postURL
  75. * @param blocks
  76. * @returns {Promise}
  77. */
  78. generateBlogPostComponent: function(categoryURL, postURL, blocks)
  79. {
  80. return new Promise(function(resolve, reject)
  81. {
  82. const pathName = "content/posts/" + categoryURL + "/"
  83. + postURL + ".md";
  84. var markDown = utils.getFileContents(pathName).toString();
  85. markDown = markDown.split("(media/").join("(" + "../content/posts/"
  86. + categoryURL + "/media/");
  87. module.exports.convertToHTML(markDown, blocks).then(function(result)
  88. {
  89. // hackey stuff to fix this open issue on pandoc https://github.com/jgm/pandoc/issues/3858
  90. //search for pattern <pre class="LANG"><code> and replace with <code class="language-LANG">
  91. var re = /\<pre class=".*?"><code>/;
  92. while (result.search(re) != -1)
  93. {
  94. var preTag = result.match(/\<pre class=".*?"><code>/g)[0];
  95. var finishIndex = preTag.split('"', 2).join('"').length;
  96. lang = preTag.substring(12, finishIndex);
  97. var newHTML = `<pre><code class="language-${lang}">`
  98. var original = `<pre class="${lang}"><code>`;
  99. result = result.split(original).join(newHTML);
  100. }
  101. result = result.split("<figcaption>").join("<figcaption style=\"visibility: hidden;\">");
  102. //this line prevents older versions of pandoc from including invalid cdm scripts
  103. result = result.split("<script src=\"https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_CHTML-full\" type=\"text/javascript\"></script>").join("");
  104. result = result.split("<script src=\"https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML\" type=\"text/javascript\"></script>").join("");
  105. //stuff for youtube videos
  106. var re = /\<youtube .*?>/;
  107. //<youtube src="" />
  108. while (result.search(re) != -1)
  109. {
  110. var ytid = result.substring(result.search(re) + 14, result.search(re)+ 11 + 14);
  111. var youtubeHTML = `
  112. <div class="wrapper">
  113. <div class="youtube" data-embed="${ytid}">
  114. <div class="play-button"></div>
  115. </div>
  116. </div>`;
  117. var original = `<youtube src="${ytid}" />`;
  118. result = result.split(original).join(youtubeHTML);
  119. }
  120. var regExp = /\<customHTML .*?>/;
  121. while (result.search(regExp) != -1)
  122. {
  123. const pathName = "content/posts/" + categoryURL + "/html/"
  124. + postURL + ".html";
  125. var htmlContent = utils.getFileContents(pathName).toString();
  126. result = result.split("<customHTML />").join(htmlContent);
  127. }
  128. if(blocks == -1)
  129. resolve(result);
  130. const htmlBlocks = result.split("<p>");
  131. var html = "";
  132. for(var i = 0; i < blocks; i++)
  133. {
  134. html += "<p>" + htmlBlocks[i];
  135. }
  136. resolve(html);
  137. }).catch(function(error)
  138. {
  139. reject(error);
  140. })
  141. })
  142. },
  143. /**
  144. * Converts markdown into html.
  145. *
  146. * @param markdownContents
  147. * @param type
  148. * @returns {Promise}
  149. */
  150. convertToHTML: function(markdownContents, type)
  151. {
  152. if(type == -1)
  153. {
  154. return module.exports.pandocWrapper(markdownContents, argsFull);
  155. }
  156. else
  157. {
  158. return module.exports.pandocWrapper(markdownContents, argsFull);
  159. }
  160. },
  161. pandocWrapper: function(markdownContents, pandocArgs)
  162. {
  163. return new Promise((resolve, reject)=>
  164. {
  165. // Set your callback function
  166. callback = function (err, html)
  167. {
  168. if (err)
  169. {
  170. reject(err);
  171. }
  172. if(html === undefined)
  173. {
  174. resolve("");
  175. }
  176. else
  177. {
  178. html = html.split("<img").join("<img style=\"max-width: 100%;\" ");
  179. // html = html.split("<code>").join("<code class='hljs cpp'>");
  180. resolve(html);
  181. }
  182. };
  183. pandoc(markdownContents, pandocArgs, callback);
  184. });
  185. },
  186. /**
  187. * Renders a bunch of blog post previews to the user
  188. *
  189. * @param baseURL-- url of the page
  190. * @param posts -- sql data about the blog to render
  191. * @param currentPage -- the current page to render
  192. * @param numOfPosts -- number of blog to render
  193. * @returns {Promise} renders the html of the blog
  194. */
  195. renderBatchOfPosts: function(baseURL, posts, currentPage, numOfPosts, templateContext)
  196. {
  197. if(typeof currentPage == "undefined")
  198. {
  199. currentPage = 1;
  200. }
  201. else
  202. {
  203. currentPage = Number(currentPage);
  204. }
  205. return new Promise(function(resolve, reject)
  206. {
  207. const promises = [];
  208. for(var i = (currentPage-1) * numOfPosts; i < (currentPage-1) * numOfPosts + numOfPosts; i++)
  209. {
  210. if(i < posts.length)
  211. {
  212. promises.push(new Promise(function(res, rej)
  213. {
  214. module.exports.generateBlogPost(posts[i], posts.length === 1 ? -1: 3).then(function(tempContext)
  215. {
  216. if(posts.length != 1)
  217. {
  218. templateContext.preview = true
  219. }
  220. res(tempContext);
  221. }).catch(function(error)
  222. {
  223. rej();
  224. })
  225. }));
  226. }
  227. }
  228. Promise.all(promises).then(function(posts)
  229. {
  230. templateContext.posts = posts;
  231. if(posts.length == 1)
  232. templateContext.title = posts[0].name;
  233. else if(currentPage != 1 && baseURL === "/")
  234. templateContext.title = "page " + currentPage;
  235. resolve();
  236. }).catch(function(error)
  237. {
  238. reject(error);
  239. });
  240. });
  241. }
  242. };