Copy LaTeX Formula 1.1

复制网页上的 LaTeX 公式(CSDN,zhihu,wiki)

// ==UserScript==
// @name         Copy LaTeX Formula 1.1
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  复制网页上的 LaTeX 公式(CSDN,zhihu,wiki)
// @author       shezhao
// @match        *://*/*
// @match        https://www.zhihu.com/question/*
// @match        https://zhuanlan.zhihu.com/p/*
// @match        https://blog.csdn.net/*/article/*
// @match        https://*.wikipedia.org/*
// @match        https://www.wikiwand.com/*
// @license      MIT
// @grant        none
// ==/UserScript==
// 鸣谢
// https://greasyfork.org/zh-CN/scripts/397740
// 参考了wiki部分 https://github.com/flaribbit/click-to-copy-equations
(function() {
    'use strict';
    const host = document.location.host;
    class zhihuLaTeXFormulaCopier {
        constructor(elementSelector = 'span.ztext-math') {
            this.elementSelector = elementSelector;
            this.contextMenu = this.createContextMenu();
            this.addEventListeners();
        }

        createContextMenu() {
            const contextMenu = document.createElement('div');
            contextMenu.style.display = 'none';
            contextMenu.style.position = 'absolute';
            contextMenu.style.backgroundColor = 'white';
            contextMenu.style.border = '1px solid black';
            contextMenu.style.padding = '5px';
            contextMenu.style.zIndex = '10000';

            const copyOption = document.createElement('div');
            copyOption.textContent = 'Copy LaTeX Formula';
            copyOption.style.cursor = 'pointer';
            copyOption.style.padding = '5px';
            copyOption.addEventListener('click', () => {
                const formula = this.getSelectedFormula();
                if (formula) {
                    console.log('Formula to be copied:', formula);
                    this.copyToClipboard(formula);
                } else {
                    console.log('No formula found');
                }
                this.hideContextMenu();
            });

            contextMenu.appendChild(copyOption);
            document.body.appendChild(contextMenu);

            return contextMenu;
        }

        showContextMenu(x, y) {
            if (this.contextMenu) {
                this.contextMenu.style.left = `${x}px`;
                this.contextMenu.style.top = `${y}px`;
                this.contextMenu.style.display = 'block';
                console.log('Context menu shown at:', x, y);
            }
        }

        hideContextMenu() {
            if (this.contextMenu) {
                this.contextMenu.style.display = 'none';
                console.log('Context menu hidden');
            }
        }

        getSelectedFormula() {
            const selection = window.getSelection();
            if (selection.rangeCount > 0) {
                const range = selection.getRangeAt(0);
                const startNode = range.startContainer;
                const endNode = range.endContainer;

                const latexElements = document.querySelectorAll(this.elementSelector);
                console.log('Found latex elements:', latexElements.length);
                for (const element of latexElements) {
                    if (element.contains(startNode) && element.contains(endNode)) {
                        const formula = element.getAttribute('data-tex');
                        console.log('Selected formula:', formula);
                        return '$' + formula + '$'; // 在这里添加 $ 符号
                    }
                }
            }
            console.log('No formula selected');
            return null;
        }

        copyToClipboard(text) {
            navigator.clipboard.writeText(text)
                .then(() => {
                    console.log('Copied to clipboard:', text);
                    if (!DEFAULT_COPY){
                        alert('已复制到剪贴板');
                    }
                })
                .catch((error) => {
                    console.error('Failed to copy to clipboard:', error);
                    if (!DEFAULT_COPY){
                        alert('复制失败');
                    }
                });
        }

        addEventListeners() {
            document.addEventListener('contextmenu', (event) => {
                event.preventDefault();
                const clickedElement = event.target;
                this.highlightElement(clickedElement);
                const latexElement = this.findClosestLatexElement(clickedElement);
                if (latexElement) {

                    const formula = latexElement.getAttribute('data-tex');
                    if (formula) {
                        console.log('Formula found in clicked element:', formula);
                        if (!DEFAULT_COPY) {
                            let shouldCopy = window.confirm('是否要复制这个公式?');
                            if (shouldCopy) {
                                this.copyToClipboard('$' + formula + '$'); // 在这里添加 $ 符号
                            }
                        } else{
                            this.copyToClipboard('$' + formula + '$'); // 在这里添加 $ 符号
                        }

                    } else {
                        console.log('No formula found in clicked element');

                    }

                } else {
                    console.log('No ztext-math element found in clicked area');
                }
                this.showContextMenu(event.pageX, event.pageY);
            });


            document.addEventListener('click', () => {
                this.hideContextMenu();
                this.removeHighlight();
            });
        }

        findClosestLatexElement(element) {
            let currentElement = element;
            while (currentElement) {
                if (currentElement.classList && currentElement.classList.contains('ztext-math')) {
                    return currentElement;
                }
                currentElement = currentElement.parentElement;
            }
            return null;
        }
         highlightElement(element) {
            this.removeHighlight();
            element.style.border = '2px solid red';
            this.highlightedElement = element;
        }

        removeHighlight() {
            if (this.highlightedElement) {
                this.highlightedElement.style.border = '';
                this.highlightedElement = null;
            }
        }
    }
    class csdnKatexFormulaCopier {
        constructor(elementSelector = 'span.katex-mathml') {
            this.elementSelector = elementSelector;
            this.contextMenu = this.createContextMenu();
            this.addEventListeners();

        }

        createContextMenu() {
            const contextMenu = document.createElement('div');
            contextMenu.style.display = 'none';
            contextMenu.style.position = 'absolute';
            contextMenu.style.backgroundColor = 'white';
            contextMenu.style.border = '1px solid black';
            contextMenu.style.padding = '5px';
            contextMenu.style.zIndex = '10000';

            const copyOption = document.createElement('div');
            copyOption.textContent = 'Copy LaTeX Formula';
            copyOption.style.cursor = 'pointer';
            copyOption.style.padding = '5px';
            copyOption.addEventListener('click', () => {
                const formula = this.getSelectedFormula();
                if (formula) {
                    console.log('Formula to be copied:', formula);
                    this.copyToClipboard(formula);
                } else {
                    console.log('No formula found');
                }
                this.hideContextMenu();
            });

            contextMenu.appendChild(copyOption);
            document.body.appendChild(contextMenu);

            return contextMenu;
        }

        showContextMenu(x, y) {
            if (this.contextMenu) {
                this.contextMenu.style.left = `${x}px`;
                this.contextMenu.style.top = `${y}px`;
                this.contextMenu.style.display = 'block';
                console.log('Context menu shown at:', x, y);
            }
        }

        hideContextMenu() {
            if (this.contextMenu) {
                this.contextMenu.style.display = 'none';
                console.log('Context menu hidden');
            }
        }

        getSelectedFormula() {
            const selection = window.getSelection();
            if (selection.rangeCount > 0) {
                const range = selection.getRangeAt(0);
                const startNode = range.startContainer;
                const endNode = range.endContainer;

                const katexElements = document.querySelectorAll(this.elementSelector);
                console.log('Found katex elements:', katexElements.length);
                for (const element of katexElements) {
                    if (element.contains(startNode) && element.contains(endNode)) {
                        const formula = element.textContent;
                        // 处理公式 以换行符分隔,获取最后一个公式
                        const formulas = formula.split('\n');
                        formula = formulas[formulas.length - 1];
                        if (!formula) {
                            if (formulas.length < 2) {
                                console.log('No formula found');
                                return null;
                            }
                            formula = formulas[formulas.length - 2];
                        }
                        console.log('Selected formula:', formula);
                        return '$' + formula + '$';
                    }
                }
            }
            console.log('No formula selected');
            return null;
        }

        copyToClipboard(text) {
            navigator.clipboard.writeText(text)
                .then(() => {
                    console.log('Copied to clipboard:', text);
                    if (!DEFAULT_COPY) {
                        alert('已复制到剪贴板');
                    }


                })
                .catch((error) => {
                    console.error('Failed to copy to clipboard:', error);
                    if (!DEFAULT_COPY) {
                        alert('复制失败');
                    }
                });
        }

        addEventListeners() {
            document.addEventListener('contextmenu', (event) => {
                event.preventDefault();
                const clickedElement = event.target;
                const katexElement = this.findClosestKatexElement(clickedElement);

                if (katexElement) {

                    const formula = katexElement.textContent;

                    if (formula) {
                        console.log(typeof formula);
                        // 将公式按换行符分割
                        const formulas_origin = formula.split("\n");
                        let formula_text = "";
                        let maxLength = 0;

                        for (let i = 0; i < formulas_origin.length; i++) {
                            // 修剪每个公式的首尾空白
                            const trimmedFormula = formulas_origin[i].trim();
                            // 检查修剪后的公式是否不为空且长度大于当前最大长度
                            if (trimmedFormula.length > 0 && trimmedFormula.length > maxLength) {
                                formula_text = trimmedFormula;
                                maxLength = trimmedFormula.length;
                            }
                        }

                        console.log('在点击的元素中找到的公式:', formula_text);
                        if(!DEFAULT_COPY) {
                            let shouldCopy = window.confirm('是否要复制这个公式?');
                            if (shouldCopy) {
                                this.copyToClipboard('$' + formula_text + '$');
                            }
                        }
                        else {
                            this.copyToClipboard('$' + formula_text + '$');
                        }
                    } else {
                        console.log('No formula found in clicked element');
                    }
                } else {
                    console.log('No katex-mathml element found in clicked area');
                }
                this.showContextMenu(event.pageX, event.pageY);
            });

            document.addEventListener('click', () => {
                this.hideContextMenu();
            });
        }

        findClosestKatexElement(element) {
            let currentElement = element;
            while (currentElement) {
                if (currentElement.classList && currentElement.classList.contains('katex-mathml')) {
                    return currentElement;
                }
                // 检查父元素的同级元素
                let sibling = currentElement.previousElementSibling;
                while (sibling) {
                    if (sibling.classList && sibling.classList.contains('katex-mathml')) {
                        return sibling;
                    }
                    sibling = sibling.previousElementSibling;
                }
                sibling = currentElement.nextElementSibling;
                while (sibling) {
                    if (sibling.classList && sibling.classList.contains('katex-mathml')) {
                        return sibling;
                    }
                    sibling = sibling.nextElementSibling;
                }
                currentElement = currentElement.parentElement;
            }
            return null;
        }

    }
    // 用于复制维基百科和 Wiki 上的公式
    class WikiTeXFormulaCopier {
        constructor() {
            this.init();
        }

        init() {
            if (host.search('wikipedia') >= 0) {
                this.setupWikipedia();
            } else if (host.search('wikiwand') >= 0) {
                this.setupWikiwand();
            }
        }

        clearAnimation(event) {
            event.target.style.animation = '';
        }

        setupWikipedia() {
            const copyTex = function () {
                if(!DEFAULT_COPY) {
                    if (confirm('是否复制该公式?')) {
                        navigator.clipboard.writeText('$' + this.alt + '$');
                        this.style.animation = 'aniclick .2s';
                    }
                }
                else {
                    navigator.clipboard.writeText('$' + this.alt + '$');
                    this.style.animation = 'aniclick .2s';
                }

            }
            const eqs = document.querySelectorAll('.mwe-math-fallback-image-inline, .mwe-math-fallback-image-display');
            for (let i = 0; i < eqs.length; i++) {
                eqs[i].onclick = copyTex;
                eqs[i].addEventListener('animationend', this.clearAnimation);
                eqs[i].title = '点击即可复制公式';
            }
        }

        setupWikiwand() {
            const copyTex = function () {
                const tex = this.getElementsByTagName('math')[0].getAttribute("alttext");
                if(!DEFAULT_COPY) {
                    if (confirm('是否复制该公式?')) {
                        navigator.clipboard.writeText('$' + tex + '$');
                        this.style.animation = 'aniclick .2s';
                    }
                }
                else {
                    navigator.clipboard.writeText('$' + tex + '$');
                    this.style.animation = 'aniclick .2s';
                }

            }
            const check_equations = (mutationList, observer) => {
                const eqs = document.querySelectorAll('.mwe-math-element');
                for (let i = 0; i < eqs.length; i++) {
                    eqs[i].onclick = copyTex;
                    eqs[i].addEventListener('animationend', this.clearAnimation);
                    eqs[i].title = '点击即可复制公式';
                }
            }
            const targetNode = document.getElementsByTagName('article')[0];
            const config = { attributes: false, childList: true, subtree: true };
            const observer = new MutationObserver(check_equations);
            observer.observe(targetNode, config);
        }
    }
    const DEFAULT_COPY = true

    if (host.search('zhihu.com') >= 0||host.search('blog.csdn') >= 0||host.search('wikipedia') >= 0||host.search('wikiwand') >= 0)
    {
        // 默认复制到剪贴板
        const DEFAULT_COPY = window.confirm('是否默认复制到剪贴板?');
        // 网址包含 zhihu.com 的页面
        if (host.search('zhihu.com') >= 0) {
            new zhihuLaTeXFormulaCopier();
        }
        // 网址包含 csdn.net 的页面
        else if (host.search('blog.csdn') >= 0) {
            new csdnKatexFormulaCopier();
        }
        else if (host.search('wikipedia') >= 0 || host.search('wikiwand') >= 0) {
            new WikiTeXFormulaCopier();
        }
    }


})();