Old Reddit Better Codeblocks

re-render markdown with marked.js and highlight codeblocks with highlight.js

  1. // ==UserScript==
  2. // @name Old Reddit Better Codeblocks
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4.2
  5. // @license MIT
  6. // @description re-render markdown with marked.js and highlight codeblocks with highlight.js
  7. // @author cultab
  8. // @match http*://*.reddit.com/*
  9. // @exclude http*://new.reddit.com/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=reddit.com
  11. // @grant GM_addStyle
  12. // @grant GM_getResourceText
  13. // @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
  14. // @require https://cdn.jsdelivr.net/npm/marked@10.0.0/lib/marked.umd.min.js
  15. // @require https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js
  16. // @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js
  17. // @resource hljs https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css
  18. // ==/UserScript==
  19.  
  20.  
  21. /* global VM hljs DOMPurify marked */
  22.  
  23. const DEBUG = false;
  24.  
  25. function sleep(ms) {
  26. return new Promise(resolve => setTimeout(resolve, ms));
  27. }
  28.  
  29. function until(conditionFunction) {
  30. const poll = resolve => {
  31. if (conditionFunction()) {
  32. resolve();
  33. } else {
  34. setTimeout(_ => poll(resolve), 400);
  35. }
  36. }
  37.  
  38. return new Promise(poll);
  39. }
  40.  
  41. function parse(md) {
  42. log(md)
  43. md = md.replace(/(https?:\/\/.*\.(jpeg|jpg|png|gif|webp).*(\?[A-z])?)/g, "$1:\n\n![]($1)");
  44. log(md)
  45. md = md.replace('/ /g', ' ');
  46. md = DOMPurify.sanitize(marked.parse(md));
  47. md = md.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
  48. return md;
  49. }
  50.  
  51. function log(...vars) {
  52. if (DEBUG) {
  53. console.log(...vars);
  54. }
  55. }
  56.  
  57. const skipme_marker = "orbc_skip";
  58. const source_marker = "orbc_source";
  59.  
  60. const stoyle = GM_getResourceText("hljs");
  61. GM_addStyle(stoyle);
  62.  
  63. const content = document.body.querySelector("div.content");
  64. log(content);
  65.  
  66. let base = 0;
  67.  
  68. const genRanHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
  69.  
  70. const disconnect = VM.observe(content, () => {
  71. const id = genRanHex(6);
  72.  
  73. // all posts/comments
  74. const entries = document.getElementsByClassName("entry");
  75.  
  76. // barrier for callback completion
  77. let barrier = entries.length;
  78. log(id, " Start with #", barrier);
  79.  
  80. let changed = false;
  81. for (const entry of entries) {
  82. // const eid = genRanHex(4);
  83.  
  84. // if it contains an expando we need to check some stuff :)
  85. let expando_btn = entry.querySelector(".expando-button")
  86. if (expando_btn) {
  87. if (expando_btn.classList.contains("collapsed") || !expando_btn.classList.contains("selftext")) {
  88. entry.classList.remove(skipme_marker);
  89. barrier--;
  90. continue;
  91. } else {
  92. }
  93. }
  94.  
  95. // if we proccesed this entry, skip it
  96. if (entry.classList.contains(skipme_marker)) {
  97. barrier--;
  98. continue;
  99. }
  100.  
  101. // find view source button and if exists click it twice
  102. let btn = entry.querySelector(".viewSource");
  103. if (!btn) {
  104. barrier--;
  105. continue;
  106. }
  107. btn.children[0].click(); // yes twice
  108. btn.children[0].click();
  109.  
  110. // when source is loaded, use it to replace post content with marked.js markdown
  111. const fn = () => { /* intended shared reference to barrier and changed*/
  112. let source = entry.querySelector("textarea").innerHTML;
  113. if (!source) {
  114. log(id, "no source yet");
  115. return false;
  116. }
  117. entry.classList.add(source_marker);
  118. entry.classList.add(skipme_marker);
  119. let post = entry.querySelector(".usertext-body");
  120. if (post) {
  121. changed = true;
  122. post.children[0].innerHTML = parse(source);
  123. } else {
  124. log("null post for id: ", id);
  125. }
  126. barrier--;
  127. return true
  128. }
  129.  
  130. // if view source has loaded run
  131. if (entry.classList.contains(source_marker)) {
  132. fn();
  133. } else { // else wait for it to load
  134. VM.observe(entry, fn);
  135. }
  136.  
  137. }
  138. (async () => {
  139. // wait until all posts have been re-parsed
  140. await until(() => {
  141. log(id, "barrier", barrier);
  142. return barrier == 0;
  143. });
  144. log(id, " Exit, changed: ", changed);
  145. if (!changed) {
  146. return false;
  147. }
  148.  
  149. // create a div with class hlhs
  150. let hl = document.createElement("div");
  151. hl.classList.add('hljs');
  152. hl.setAttribute("id", "hljshack");
  153. document.querySelector("body").append(hl);
  154.  
  155. // use it to get the style of hljs classes
  156. let ready_and_styled = document.getElementById("hljshack");
  157. let wanted_styles = window.getComputedStyle(ready_and_styled);
  158.  
  159. // force hljs styles on code and pre blocks
  160. let code_blocks = document.querySelectorAll("code");
  161. for (let blk of code_blocks) {
  162. // highlight block
  163. blk.innerHTML = hljs.highlightAuto(blk.innerText).value;
  164. blk.style.backgroundColor = wanted_styles.backgroundColor;
  165. blk.style.color = wanted_styles.color;
  166. }
  167. let pre_elems = document.querySelectorAll("pre");
  168. for (let pre of pre_elems) {
  169. pre.style.backgroundColor = wanted_styles.backgroundColor;
  170. pre.style.borderColor = wanted_styles.color;
  171. }
  172. })();
  173.  
  174.  
  175. return false;
  176. });
  177.  
  178. content.append(document.createElement("span"));