Latexlive公式编辑器输出增强:转 Markdown 格式,适用于 Logseq 等

为中文文本中的公式添加 $$ 符号,以适应 Markdown 或 Latex 格式的需求。并修复常见的图片识别结果中的错误

目前為 2024-03-31 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Latexlive公式编辑器输出增强:转 Markdown 格式,适用于 Logseq 等
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  为中文文本中的公式添加 $$ 符号,以适应 Markdown 或 Latex 格式的需求。并修复常见的图片识别结果中的错误
// @author       Another_Ghost
// @match        https://*.latexlive.com/*
// @icon         https://img.icons8.com/?size=50&id=1759&format=png
// @grant         GM_registerMenuCommand
// @license      MIT
// ==/UserScript==


(function () {
    createButton('复制', copyOriginalText, '');
    createButton('转换后复制', convertFormulasToLaTeX, /\\boldsymbol/g);

    /**
     * 创建按钮并添加到指定容器中
     * @param {number} buttonName - 按钮的名字
     * @param {function} convert - 转换函数
     * @param {string} wordsToRemove - 需要移除的字符串
     */
    function createButton(buttonName, convert, wordsToRemove) {
        // 创建一个新按钮
        let button = document.createElement('button');
        button.innerHTML = `${buttonName}`;
        button.className = 'btn btn-light btn-outline-dark';
        //button.id = `copy-btn${buttonId}`;
        // add click handler
        button.onclick = function () {
            //选中输入文本框的所有文本
            var selected = document.querySelector('#txta_input'); 
            //先通过 convert 函数转换文本,再复制
            navigator.clipboard.writeText(convert(selected.value, wordsToRemove));
            displayAlertBox('已复制');
        };
        //输入框上方的容器
        var CONTAINER = "#wrap_immediate > row > div.col-5.col-sm-5.col-md-5.col-lg-5.col-xl-5";
        //等待容器出现并添加按钮
        var interval = setInterval(function () {
            var wrap = document.querySelector(CONTAINER);
            if (wrap) {
                wrap.appendChild(button);
                clearInterval(interval);
            }
        }, 200);
    }

    function displayAlertBox(text) {
        var alertBox = document.createElement('div');
        alertBox.innerHTML = text;
        //alertBox.style.display = none;
        alertBox.style.position = 'fixed';
        alertBox.style.bottom = `20px`;
        alertBox.style.left = `50%`;
        alertBox.style.transform = `translateX(-50%)`;
        alertBox.style.backgroundColor = `#4CAF50`;
        alertBox.style.color = `white`;
        alertBox.style.padding = `12px`;
        alertBox.style.borderRadius = `5px`;
        alertBox.style.zIndex = `1000`;
        alertBox.style.boxShadow = `0px 0px 10px rgba(0,0,0,0.5)`;
        alertBox.style.opacity = '0';
        alertBox.style.transition = 'opacity 0.3s';
        document.body.appendChild(alertBox);
        setTimeout(function () {
            alertBox.style.opacity = '1';
        }, 100);
        setTimeout(function () {
            alertBox.style.opacity = '0';
        }, 1100);
        setTimeout(function () {
            alertBox.remove();
        }, 1500);
    }

    function copyOriginalText(inStr, wordsToRemove = '') {
        return inStr;
    }

    let bRadical = true; //是否是更激进的转换方式
    
    let shortcutKey = null;
    GM_registerMenuCommand('切换激进转换', function (){
        bRadical = !bRadical;
        if(bRadical)
        {
            displayAlertBox("开启激进转换");
        }
        else
        {
            displayAlertBox("关闭激进转换");
        }
    }, shortcutKey);

    /**
     * 将字符串中的公式转换为LaTeX格式,用"$$"包围起来。
     */
    function convertFormulasToLaTeX(inStr, wordsToRemove = '') {
        // const menu_command_id_1 = GM_registerMenuCommand("Show Alert", function(event: MouseEvent | KeyboardEvent) {
        //     alert("Menu item selected");
        //   }, {
        //     accessKey: "a",
        //     autoClose: true
        //   });

        // 输入的预处理
        inStr = inStr.trim(); //删除字符串两端的空白字符
        if(bRadical)
        {
            inStr = inStr.replace(/\\begin{array}{[^{}]*}/g, '\\begin{aligned}');
            inStr = inStr.replace(/\\end{array}/g, '\\end{aligned}');
        }
        inStr = inStr.replace(wordsToRemove, '');
        inStr = inStr.replace(/ +/g, ' '); //将多个空格替换为一个空格
        inStr = inStr.replace(/\n+/g, '\n'); //去除重复换行符
        inStr = inStr.replace('输人', '输入'); 
        //inStr = inStr.replace(/\\text *{([^{}]*)}/g, '$1');
        let str = inStr.trim(); 

        let outStr = ""; //最终输出的字符串
        let equation = ""; //存储一个公式
        let bEquation = false; //是否在处理一个公式

        //处理并存储一个潜在公式
        let PushEquationToOutStr = (nextChar) => {
            if(/[\-<=>\\\^_{\|}\/\*\+\(]/.test(equation) || /^[a-zA-Z0-9]$/.test(equation.trim())){ //判断是否是真的公式
                outStr += ToMarkdownLatex(equation, nextChar);
            }
            else
            {
                outStr+=equation;
            }
            bEquation = false;
            equation = "";
        }

        //实际转换格式的函数
        function ToMarkdownLatex(equation, nextChar)
        {
            equationSymbol = "$";
            let prevChar = outStr[outStr.length-1];
            if((!nextChar || nextChar.match(/[\n\r]/)) && (!prevChar || prevChar.match(/[\n\r]/))) //判断是否为单行居中显示的公式
            {
                equationSymbol = "$$";
            }
            if(equation[equation.length-1] != '$' || equation[0] != '$$')
            {
                equation = insertCharAt(equation, equationSymbol, findLastNonWhitespaceChar(equation)+1); //在公式字符串的最后一个非空格字符的位置的后一个位置插入"$$")
            }
            if(equation[0] != '$' || equation[0] != '$$')
            {
                equation = equationSymbol + equation;
            }
            return equation;
        }

        //遍历字符串的主循环
        for(let i = 0; i < str.length; i++) {
            let c = str[i];
            //let nextChar = i < str.length - 1 ? str[i + 1] : '';
            if(!bEquation){
                if(c.match(/[!-~]/)) { //判断是否是非空格ASCII字符
                    if(!bEquation && !c.match(/[:,.]/)) //把开头的 ":" 排除在 $$ 外;因为之后一般会跟着换行符,所以没有写在 ToLatex 函数里
                    {
                        bEquation = true;
                    } 
                }
            }
            else{ //判断一个公式是否结束
                if((c.match(/[\n\r]/) && (!/\\begin/.test(equation) || /\\end/.test(equation))) //换行符且是不是在\begin{array}和\end{array}之间,则算作一个潜在公式
                || (!c.match(/[ -~]/) && !(/\\text *{([^}])*$/.test(equation)) && !c.match(/[\n\r]/)) //判断如果是非换行符的ASCII字符,且不在 \text{} 中,则算作一个潜在公式进行后续处理
                )
                {
                    PushEquationToOutStr(c);
                }
            }

            //循环中,只有此处存储字符
            if(bEquation){
                equation += c;
            }
            else{
                outStr += c;
            }
        }
        if(equation.length > 0) {
            PushEquationToOutStr('');
        }
        console.log(outStr);
        return outStr;

        /**
         * Insert a character at a specified index in the original string.
         * @param {string} originalString - The original string.
         * @param {string} charToInsert - The character to insert.
         * @param {number} index - The index to insert at.
         * @returns {string} - The new string with the inserted character.
         */
        function insertCharAt(originalString, charToInsert, index) {
            let firstPart = originalString.slice(0, index);
            let secondPart = originalString.slice(index);
            return `${firstPart}${charToInsert}${secondPart}`;
        }

        function findLastNonWhitespaceChar(str) {
            const match = str.match(/(\S)\s*$/);
            return match ? str.lastIndexOf(match[1]) : -1;
        }
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址