Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Render Latex v3
- // @namespace Violentmonkey Scripts
- // @match https://mail.google.com/mail/*
- // @grant GM_registerMenuCommand
- // @grant GM_addElement
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_setValues
- // @grant GM_listValues
- // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js
- // @version 3.0
- // @author -
- // @description
- // ==/UserScript==
- GM_addElement('link', {
- rel: "stylesheet",
- src: "https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.css"
- });
- function escapeRegExpSpecialCharacters(regexString) {
- return regexString.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
- }
- function renderLatex() {
- document.querySelectorAll("#\\:1 > .nH .aHU.hx > [role='list'] > [role='listitem'][aria-expanded='true']").forEach(message => {
- let subportion = message.querySelector("[data-message-id]"); // need to select a subportion of [role='listitem'] to stop rendering latex in the textinput
- if(subportion) {
- message = subportion;
- }
- if(GM_listValues().length < 2) {
- GM_setValues({
- "[;...;]": "off",
- "\\(...\\)": "off",
- "$...$": "off",
- "\\begin{math}...\\end{math}": "off",
- "[(;...;)]": "off",
- "\\[...\\]": "off",
- "$$...$$": "off",
- "\\begin{displaymath}...\\end{displaymath}": "off",
- "\\begin{equation}...\\end{equation}": "off"
- });
- }
- // Have to find "$$...$$" before "$...$". This moves it to the front of the array so that it isn't done first.
- let delims = GM_listValues();
- let tempVar = delims[0];
- let doubleDollarLocation = delims.indexOf("$$...$$");
- delims[0] = "$$...$$";
- delims[doubleDollarLocation] = tempVar;
- for(delim of delims) {
- if(delim !== "automatic" && GM_getValue(delim, "off") !== "off") {
- let delimRegex = new RegExp(escapeRegExpSpecialCharacters(delim.split("...")[0]) + "(.+?)" + escapeRegExpSpecialCharacters(delim.split("...")[1]), "g");
- message.innerHTML = message.innerHTML.replace(delimRegex, (match, p1) => {
- p1 = p1.replaceAll("<wbr>", "");
- let htmlEntityConversions = {
- "&": "&",
- """: "\"",
- "'": "'",
- "<": "<",
- ">": ">"
- }
- for(let entity in htmlEntityConversions) {
- p1 = p1.replaceAll(entity, htmlEntityConversions[entity]);
- }
- return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: GM_getValue(delim, "off") === "display" });
- });
- }
- }
- });
- }
- GM_registerMenuCommand('Render Latex', () => {
- renderLatex();
- });
- function waitForElement(queryString) {
- let count = 0;
- return new Promise((resolve, reject) => {
- let findInterval = setInterval(() => {
- let waitElement = document.querySelector(queryString);
- if(waitElement) {
- clearInterval(findInterval);
- resolve(waitElement);
- } else if(count > 20) {
- clearInterval(findInterval);
- reject(`Couldn't find waitElement: ${queryString}.`);
- } else {
- count += 1;
- }
- }, 100);
- });
- }
- /**
- * Creates a MutationObserver that will watch the element messagesDiv in order to tell when either an email is opened
- * or a reply in an email chain is expanded. If either of those things happen, renderLatex() will be called.
- * @param Element messagesDiv - the div we get from document.querySelector("#\\:1 > .nH")
- * @return MutationObserver - returns the MutationObserver that is watching messagesDiv so that we can call observe or disconnect on it.
- */
- function createMailObserver(messagesDiv) {
- let mailObserver = new MutationObserver((mutationRecords, observerElement) => {
- mutationRecords.forEach(mutationRecord => {
- switch(mutationRecord.type) {
- case "childList":
- mutationRecord.addedNodes.forEach(addedNode => {
- if(addedNode.tagName === "DIV" && addedNode.getAttribute("role") === "listitem") {
- renderLatex();
- }
- });
- break;
- case "attributes":
- if(mutationRecord.target.tagName === "DIV" && mutationRecord.target.getAttribute("role") === "listitem" && mutationRecord.attributeName === "aria-expanded") {
- renderLatex();
- }
- }
- });
- });
- return mailObserver;
- }
- /**
- * Creates a popup window that will let the user change settings about how this script will handle delimiters and whether or not the latex
- * will attempt to render automatically.
- * @param MutationObserver mailObserver - the observer that checks when mail is opened or replies are expanded.
- * @param messagesDiv - the div that mailObserver is watching so that we can reconnect it again
- * @return Element latexOptionsWindow - a div that represents the window that lets the user change script settings.
- */
- function createLatexOptionsWindow(mailObserver, messagesDiv) {
- let latexOptionsWindow = document.createElement("DIV");
- latexOptionsWindow.classList.add("latex-window");
- latexOptionsWindow.innerHTML = `
- <h2 class="latex-window-header">Inline Delimiters</h2>
- <div>
- <span class="latex-delimiter">[;...;]</span>
- <input data-display-type="inline" type="checkbox">
- </div>
- <div>
- <span class="latex-delimiter">\\(...\\)</span>
- <input data-display-type="inline" type="checkbox">
- </div>
- <div>
- <span class="latex-delimiter">$...$</span>
- <input data-display-type="inline" type="checkbox">
- </div>
- <div>
- <span class="latex-delimiter">\\begin{math}...\\end{math}</span>
- <input data-display-type="inline" type="checkbox">
- </div>
- <h2 class="latex-window-header">Display Delimiters</h2>
- <div>
- <span class="latex-delimiter">[(;...;)]</span>
- <input type="checkbox">
- </div>
- <div>
- <span class="latex-delimiter">\\[...\\]</span>
- <input type="checkbox">
- </div>
- <div>
- <span class="latex-delimiter">$$...$$</span>
- <input type="checkbox">
- </div>
- <div>
- <span class="latex-delimiter">\\begin{displaymath}...\\end{displaymath}</span>
- <input type="checkbox">
- </div>
- <div>
- <span class="latex-delimiter">\\begin{equation}...\\end{equation}</span>
- <input type="checkbox">
- </div>
- <h4>Automatically Attempt to Render Latex</h4>
- <input class="automatic-latex" type="checkbox">
- <div>
- <button class="latex-window-cancel" style="margin-left: auto;">Cancel</button>
- <button class="latex-window-save" style="margin-left: 1em;">Save</button>
- </div>
- `;
- latexOptionsWindow.querySelector(".latex-window-cancel").addEventListener('click', () => {
- latexOptionsWindow.style.display = "none";
- });
- latexOptionsWindow.querySelector(".latex-window-save").addEventListener('click', () => {
- latexOptionsWindow.querySelectorAll(".latex-delimiter").forEach(delimiter => {
- let delimiterKey = delimiter.innerText;
- let delimiterValue;
- if(delimiter.nextElementSibling.checked) {
- delimiterValue = delimiter.nextElementSibling.hasAttribute("data-display-type") ? "inline" : "display";
- } else {
- delimiterValue = "off";
- }
- GM_setValue(delimiterKey, delimiterValue);
- });
- if(latexOptionsWindow.querySelector(".automatic-latex").checked) {
- renderLatex();
- mailObserver.observe(messagesDiv, { childList: true, subtree: true, attributes: true, attributeOldValue: true});
- GM_setValue("automatic", true);
- } else {
- mailObserver.disconnect();
- GM_setValue("automatic", false);
- }
- latexOptionsWindow.style.display = "none";
- });
- document.body.prepend(latexOptionsWindow);
- return latexOptionsWindow;
- }
- /**
- * Creates a button and adds it to the menu in the header. The button will show the latexOptionsWindow when the user clicks on it, and if
- * the user clicks on the button while shift is held, it will call renderLatex() to manually attempt to render latex in whatever mail is
- * currently open.
- * @param Element latexOptionsWindow - a div that represents the window that lets the user change script settings.
- */
- function createLatexOptionsButton(latexOptionsWindow) {
- waitForElement("header [data-tooltip='Support']").then(supportButton => {
- let latexButton = document.createElement("DIV");
- latexButton.classList.add("zo");
- latexButton.style.cursor = "pointer";
- latexButton.style.backgroundImage = "url('https://www.latex-project.org/about/logos/latex-project-logo_288x288.svg')";
- latexButton.style.backgroundSize = "100%";
- latexButton.setAttribute("data-tooltip", "Latex");
- let supportButtonStyles = getComputedStyle(supportButton);
- latexButton.style.width = supportButtonStyles.width;
- latexButton.style.height = supportButtonStyles.height;
- latexButton.addEventListener('click', (e) => {
- if(e.button === 0 && e.shiftKey) {
- renderLatex();
- } else if(e.button === 0) {
- latexOptionsWindow.querySelectorAll(".latex-delimiter").forEach(delimiter => {
- if(GM_getValue(delimiter.innerText, "off") !== "off") {
- delimiter.nextElementSibling.checked = true;
- }
- });
- if(GM_getValue("automatic", false)) {
- latexOptionsWindow.querySelector(".automatic-latex").checked = true;
- }
- if(latexOptionsWindow.style.display !== "flex") {
- latexOptionsWindow.style.display = "flex";
- } else {
- latexOptionsWindow.style.display = "none";
- }
- }
- });
- supportButton.parentElement.prepend(latexButton);
- });
- }
- function addStyles() {
- let latexWindowStyle = `
- .latex-window {
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- position: absolute;
- background-color: white;
- z-index: 5000;
- display: none;
- flex-direction: column;
- border: 2px solid #555555;
- padding: 50px;
- border-radius: 50px;
- }
- .latex-window-header:first-child {
- margin-top: 0;
- }
- .latex-window-header {
- margin-bottom: 0;
- }
- .latex-window > div {
- display: flex;
- }
- .latex-window input {
- margin-left: auto;
- }
- .latex-window h4 {
- margin-bottom: 0;
- }
- .automatic-latex {
- margin-bottom: 20px;
- }
- `;
- document.head.prepend(Object.assign(document.createElement("STYLE"), { innerHTML: latexWindowStyle }));
- }
- window.addEventListener('load', () => {
- addStyles();
- waitForElement("#\\:1 > .nH").then(messagesDiv => {
- let mailObserver = createMailObserver(messagesDiv)
- if(GM_listValues().length === 0) {
- GM_setValue("automatic", false);
- }
- if(GM_getValue("automatic", false)) {
- mailObserver.observe(messagesDiv, { childList: true, subtree: true, attributes: true, attributeOldValue: true});
- }
- let latexOptionsWindow = createLatexOptionsWindow(mailObserver, messagesDiv);
- createLatexOptionsButton(latexOptionsWindow);
- });
- });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement