Greasy Fork

LaTeX for LIHKG

This is to convert LaTeX to image in LIHKG posts

目前为 2023-04-16 提交的版本。查看 最新版本

// ==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
// ==/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
    });

})();