Greasy Fork

Greasy Fork is available in English.

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

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

当前为 2024-03-31 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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