Advertisement
hnOsmium0001

Custom version of MathCord (working 2020-5-12) v2.1

May 13th, 2020
230
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Mathcord
  3. // @namespace    http://tampermonkey.net/
  4. // @version      0.3
  5. // @description  Typeset equations in Discord messages.
  6. // @author       Till Hoffmann
  7. // @match        https://discordapp.com/*
  8. // @match        https://discord.com/*
  9. // @resource     katexCSS https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css
  10. // @require      https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js
  11. // @require      https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js
  12. // @grant        GM_addStyle
  13. // @grant        GM_getResourceText
  14. // ==/UserScript==
  15.  
  16. /**
  17.  * Evaluate whether an element has a certain class prefix.
  18.  */
  19. function hasClassPrefix(element, prefix) {
  20.     var classes = (element.getAttribute("class") || "").split();
  21.     return classes.some(x => x.startsWith(prefix));
  22. }
  23.  
  24. (function() {
  25.     'use strict';
  26.  
  27.     if (!renderMathInElement) throw "Katex did not load correctly!";
  28.  
  29.     // Declare rendering options (see https://katex.org/docs/autorender.html#api for details)
  30.     const options = {
  31.         delimiters: [
  32.             {left: "$$", right: "$$", display: true},
  33.             {left: "\\(", right: "\\)", display: false},
  34.             {left: "\\[", right: "\\]", display: true},
  35.             // Needs to come last to prevent over-eager matching of delimiters
  36.             {left: "$", right: "$", display: false},
  37.         ],
  38.     };
  39.  
  40.     // We need to download the CSS, modify any relative urls to be absolute, and inject the CSS
  41.     let katexCSS = GM_getResourceText("katexCSS");
  42.     let pattern = /url\((.*?)\)/gi;
  43.     katexCSS = katexCSS.replace(pattern, 'url(https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/$1)');
  44.     GM_addStyle(katexCSS);
  45.  
  46.     class ChildrenSelector {
  47.         constructor(elm) {
  48.             this.elm = elm;
  49.         }
  50.  
  51.         andThenTag(tag, alternativeElm) {
  52.             if (!this.elm) {
  53.                 this.elm = alternativeElm;
  54.                 return this;
  55.             }
  56.  
  57.             for (const child of this.elm.childNodes) {
  58.                 if (child.tagName === tag) {
  59.                     this.elm = child;
  60.                     return this;
  61.                 }
  62.             }
  63.             this.elm = alternativeElm;
  64.             return this;
  65.         }
  66.  
  67.         andThenClass(prefix, alternativeElm) {
  68.             if (!this.elm) {
  69.                 this.elm = alternativeElm;
  70.                 return this;
  71.             }
  72.  
  73.             for (const child of this.elm.childNodes) {
  74.                 if (hasClassPrefix(child, prefix)) {
  75.                     this.elm = child;
  76.                     return this;
  77.                 }
  78.             }
  79.             // Failed to find a matching children
  80.             this.elm = alternativeElm;
  81.             return this;
  82.         }
  83.  
  84.         accept(successful, failed) {
  85.             if (this.elm) {
  86.                 successful(this.elm);
  87.             } else {
  88.                 failed();
  89.             }
  90.         }
  91.     }
  92.  
  93.     // Monitor the document for changes and render math as necessary
  94.     var observer = new MutationObserver(function(mutations, observer) {
  95.         for (const mutation of mutations) {
  96.             const target = mutation.target;
  97.             // Respond to newly loaded messages
  98.             if (target.tagName === "DIV" && hasClassPrefix(target, "scroller")) {
  99.                 // Iterate over all messages added to the scroller and typeset them
  100.                 for (const added of mutation.addedNodes) {
  101.                     if (added.tagName === "DIV" && hasClassPrefix(added, "message")) {
  102.                         renderMathInElement(added, options);
  103.                     }
  104.                 }
  105.             }
  106.             // Respond to edited messages
  107.             else if (target.tagName === "DIV" && hasClassPrefix(target, "contents") &&
  108.                        hasClassPrefix(target.parentNode, "message")) {
  109.                 for (const added of mutation.addedNodes) {
  110.                     // Do not typeset the interactive edit container
  111.                     if (added.tagName === "DIV" && !added.getAttribute("class")) {
  112.                         continue;
  113.                     }
  114.                     // Hack to get around Discord's slight delay between confirm edit and edit displayed
  115.                     setTimeout(_ => renderMathInElement(added, options), 1000);
  116.                 }
  117.             }
  118.             /*// Hack to respond to loading cached messages. These mutations are only triggered when the user mouse hovers over them
  119.             else if (target.tagName === "DIV" && hasClassPrefix(target, "message")) {
  120.                 renderMathInElement(target, options);
  121.             }*/
  122.             // Respond to reloading cached messages
  123.             else if (target.tagName === "DIV" && hasClassPrefix(target, "content")) {
  124.                 for (const added of mutation.addedNodes) {
  125.                     if (!hasClassPrefix(added, "chat")) continue;
  126.                     //renderMathInElement(added, options);
  127.                     // We expect this element to be a "chat-xxxxx" one
  128.                     new ChildrenSelector(added)
  129.                         .andThenClass("content")
  130.                         .andThenTag("MAIN")
  131.                         .andThenClass("messagesWrapper")
  132.                         .andThenClass("scrollerWrap")
  133.                         .andThenClass("scroller")
  134.                         .andThenClass("scrollerInner")
  135.                         .accept(
  136.                             scroller => {
  137.                                 for (const candidate of scroller.children) {
  138.                                     if (candidate.tagName === "DIV" && hasClassPrefix(candidate, "message")) {
  139.                                         renderMathInElement(candidate, options);
  140.                                     }
  141.                                 }
  142.                             },
  143.                             () => {
  144.                                 throw "Failed to find 'scrollerInner' element on content change (reloading cached meesages)";
  145.                             }
  146.                         )
  147.                 }
  148.             }
  149.         }
  150.     });
  151.     observer.observe(document.body, { childList: true, subtree: true });
  152. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement