Fix kbin Code Blocks

Fix for kbin code blocks federated from Lemmy. Strips out the weird <span> tags on each line.

  1. // ==UserScript==
  2. // @name Fix kbin Code Blocks
  3. // @namespace pamasich-kbin
  4. // @version 2.0.1
  5. // @description Fix for kbin code blocks federated from Lemmy. Strips out the weird <span> tags on each line.
  6. // @author Pamasich
  7. // @match https://kbin.social/*
  8. // @match https://kbin.earth/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=kbin.social
  10. // @license MIT
  11. // @grant none
  12. // ==/UserScript==
  13. /*
  14. This script fixes code blocks on kbin that originate from Lemmy.
  15. When federating code blocks, Lemmy includes additional <span> tags
  16. which kbin currently does not expect, so it just renders them in plaintext.
  17. The script removes those tags from code blocks.
  18. */
  19.  
  20. /** @type {String} */
  21. const STYLEPATTERN = "((font-style:italic|font-weight:bold);)?color:#[0-9a-fA-F]{6};";
  22.  
  23. function setup () {
  24. getCodeBlocks().forEach((code) => fix(code));
  25. }
  26.  
  27. /**
  28. * Repairs a given code block.
  29. * @param {HTMLElement} original The code block that needs to be fixed
  30. */
  31. function fix (original) {
  32. if (!isErroneousCode(original)) return;
  33. const fixed = document.createElement("code");
  34. original.after(fixed);
  35.  
  36. const start = new RegExp(`^\\n?<span style="${STYLEPATTERN}">`);
  37. const end = new RegExp(`\\n<\\/span>\\n?$`);
  38. const combined = new RegExp(`<\\/span><span style="${STYLEPATTERN}">`, "g");
  39.  
  40. fixed.textContent = original.textContent
  41. .replace(start, "")
  42. .replaceAll(combined, "")
  43. .replace(end, "");
  44.  
  45. original.style.display = "none";
  46. }
  47.  
  48. /**
  49. * Checks whether a given code block needs to be fixed.
  50. * @param {HTMLElement} code
  51. * @returns {Boolean}
  52. */
  53. function isErroneousCode (code) {
  54. const pattern = new RegExp(`^\\n?<span style="${STYLEPATTERN}">(.+\\n)+<\\/span>\\n?$`);
  55. return pattern.test(code.textContent);
  56. }
  57.  
  58. /**
  59. * @returns {HTMLElement[]} A list of all the code blocks on the page
  60. */
  61. function getCodeBlocks () {
  62. return Array.from(document.querySelectorAll("pre code"));
  63. }
  64.  
  65. setup();
  66.  
  67. const observer = new MutationObserver((mutations) => {
  68. // the filter ensures the script only looks for new code blocks to fix if new comments were added or the user navigated to a different thread
  69. const codeBlocks = mutations.flatMap((mutation) => Array.from(mutation.addedNodes))
  70. .filter((node) => node.nodeName == "BLOCKQUOTE" || node.nodeName == "BODY")
  71. .flatMap((node) => Array.from(node.querySelectorAll("pre code")));
  72. // codeBlocks at this point might contain the same node twice, so the Set constructor is used to get unique nodes
  73. new Set(codeBlocks).forEach(fix);
  74. });
  75. // observing the entire document because of turbo mode
  76. observer.observe(document, { childList: true, subtree: true });