Texcord

Typeset (latex) equations in Discord. (uses KaTeX)

// ==UserScript==
// @name        Texcord
// @namespace   http://tampermonkey.net/
// @version     0.1.1
// @description Typeset (latex) equations in Discord. (uses KaTeX)
// @match       https://discord.com/*
// @resource    katexCSS https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js
// @grant       GM_getResourceText
// @grant       GM_addStyle
// @license     GPL
// ==/UserScript==

// Check if an element's class starts with a given prefix.
function hasClassPrefix(element, prefix) {
    var classes = (element.getAttribute("class") || "").split();
    return classes.some(x => x.startsWith(prefix));
}

(function() {
    'use strict';
    // Declare rendering options (see https://katex.org/docs/autorender.html#api for details)
    const options = {
        delimiters: [
            {left: "$$", right: "$$", display: true},  
            // Needs to come last to prevent over-eager matching of delimiters
            {left: "$", right: "$", display: false},
        ],
    };

    // Download CSS
    var katexCSS = GM_getResourceText("katexCSS");
    // Fix URLS  
    var pattern = /url\((.*?)\)/gi;
    katexCSS = katexCSS.replace(pattern, 'url(https://cdn.jsdelivr.net/npm/[email protected]/dist/$1)');
    // Inject CSS
    GM_addStyle(katexCSS);

    // Monitor the document for changes and render math as necessary
    var config = { childList: true, subtree: true };
    var observer = new MutationObserver(function(mutations, observer) {
        for (let mutation of mutations) {
            var target = mutation.target;
            // If the message list was updated, typeset all added nodes
            if (target.tagName == "OL" && hasClassPrefix(target, "scrollerInner")) {
                for (let added of mutation.addedNodes) {
                    if (added.tagName == "LI" && hasClassPrefix(added, "messageListItem")) {
                        renderMathInElement(added, options);
                    }
                }
            // If a message was changed, typeset it
            } else if (target.tagName == "DIV" && hasClassPrefix(target, "contents")) {    
                for (let added of mutation.addedNodes) {
                    // Hack to fix annoying bug
                    setTimeout(_ => renderMathInElement(added, options), 100);
                }
            }
        }
    });
    observer.observe(document.body, config);
})();