// ==UserScript== // @name LaTeX for LIHKG // @namespace http://tampermonkey.net/ // @version 0.2 // @description This is to convert LaTeX to image in LIHKG posts // @author CY Fung // @match https://lihkg.com/thread/* // @icon https://avatars.githubusercontent.com/u/431808?s=48&v=4 // @grant none // @start-at document-start // @license Apache 2.0 // @downloadURL none // ==/UserScript== 'use strict'; (function() { 'use strict'; /* * Copyright (C) 2023 culefa. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ // Check if the current hostname ends with 'lihkg.com' if (!location.hostname.endsWith('lihkg.com')) return; // Create an image element for LaTeX rendering const createLatexNode = (t) => { const img = document.createElement('img'); img.className = 'lihkg-userscript-latex'; img.loading = 'lazy'; img.src = `https://math.now.sh?bgcolor=auto&from=${encodeURIComponent(t)}`; img.setAttribute('alt', `[latex]${t}[/latex]`); return img; }; // Process a text node to replace LaTeX tags with image elements const processTextNode = (textNode) => { if (!textNode) return; const textContent = textNode.textContent.trim(); // Check if the text content is long enough and has LaTeX tags if (typeof textContent === 'string' && textContent.length > 15) { const split = textContent.split(/\[latex\]((?:(?!\[latex\]|\[\/latex\]).)*)\[\/latex\]/g); // Check if the split array has an odd length (latex tags are found) if (split.length >= 3 && (split.length % 2) === 1) { const newNodes = split.map((t, j) => ((j % 2) === 0 ? document.createTextNode(t) : createLatexNode(t))); textNode.replaceWith(...newNodes); } } }; // Check a div element and process its text nodes const checkDivDOM = (div) => { let textNode = div.firstChild; while (textNode) { if (textNode.nodeType === Node.TEXT_NODE) { processTextNode(textNode); } textNode = textNode.nextSibling; } } // Check a post div for LaTeX tags and process its children divs const checkPostDiv = (postDiv) => { const html = postDiv.innerHTML; if (html.indexOf('[latex]') >= 0) { const divs = postDiv.querySelectorAll('div[class]:not(:empty)'); if (divs.length >= 1) { for (const div of divs) { checkDivDOM(div); } } else { checkDivDOM(postDiv); } } }; // Delayed check for processing post divs function delayedCheck(arr) { window.requestAnimationFrame(() => { for (const s of arr) { checkPostDiv(s); } }); } // Preview related variables let previewCid = 0; let previewCFunc = function() { let elm = document.querySelector('._3rEWfQ3U63bl18JSaUvRX7 [data-ast-root="true"]'); if (elm !== null) checkDivDOM(elm); } // Create an observer to check for new posts and previews const observer = new MutationObserver((mutations) => { let arr = []; for (const s of document.querySelectorAll('[data-post-id]:not(.y24Yt)')) { s.classList.add('y24Yt'); arr.push(s); } delayedCheck(arr); let divPreview = document.querySelector('._3rEWfQ3U63bl18JSaUvRX7 [data-ast-root="true"]'); if (divPreview && !previewCid) { previewCid = requestAnimationFrame(previewCFunc); } else if (!divPreview && previewCid) { cancelAnimationFrame(previewCid); previewCid = 0; } }); // Start observing the body element for changes observer.observe(document.body, { subtree: true, childList: true }); })();