Double-click Math Formula to Copy to Word

Double-click on a math formula on supported websites (Wikipedia, ChatGPT, Google AI Studio, Gemini, DeepSeek, Moonshot, Luogu, StackExchange, Zhihu, Doubao, OIWiki, IEEE Xplore, ChatBox AI) to copy it to the clipboard, then paste it into Microsoft Word

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Double-click Math Formula to Copy to Word
// @namespace    http://tampermonkey.net/
// @version      1.4
// @license      GPLv3
// @description  Double-click on a math formula on supported websites (Wikipedia, ChatGPT, Google AI Studio, Gemini, DeepSeek, Moonshot, Luogu, StackExchange, Zhihu, Doubao, OIWiki, IEEE Xplore, ChatBox AI) to copy it to the clipboard, then paste it into Microsoft Word
// @author       Bui Quoc Dung
// @match        *://*.wikipedia.org/*
// @match        *://chatgpt.com/*
// @match        *://gemini.google.com/*
// @match        *://aistudio.google.com/*
// @match        *://*.stackexchange.com/*
// @match        *://*.deepseek.com/*
// @match        *://*.zhihu.com/*
// @match        *://*.moonshot.cn/*
// @match        *://*.oiwiki.org/*
// @match        *://*.luogu.com.cn/*
// @match        *://*.luogu.com/*
// @match        *://*.doubao.com/*
// @match        *://*.chatboxai.app/*
// @match        *://ieeexplore.ieee.org/*

// ==/UserScript==

(function() {
    'use strict';
    const css = `
        .math-hover-effect {
            background-color: rgba(255, 215, 0, 0.25) !important;
            transition: background-color 0.2s ease;
            cursor: pointer !important;
        }
        .mathml-copy-success {
            position: absolute;
            background-color: rgba(0, 0, 0, 0.8);
            color: #fff;
            padding: 5px 12px;
            border-radius: 4px;
            font-size: 12px;
            font-family: sans-serif;
            z-index: 9999;
            opacity: 1;
            transition: opacity 0.5s;
            pointer-events: none;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        }
    `;
    const styleSheet = document.createElement("style");
    styleSheet.type = "text/css";
    styleSheet.innerText = css;
    document.head.appendChild(styleSheet);

    const siteConfig = [
        { name: "Wikipedia", match: /wikipedia\.org/, selector: 'span.mwe-math-element', type: 'mathml' },
        { name: "Zhihu", match: /zhihu\.com/, selector: 'span.ztext-math', type: 'mathml' },
        { name: "OIWIKI", match: /oiwiki\.org/, selector: 'mjx-container.MathJax', type: 'mathml' },
        { name: "Doubao", match: /doubao\.com/, selector: 'span.math-inline', type: 'mathml' },
        { name: "IEEE Xplore", match: /ieeexplore\.ieee\.org/, selector: 'span[id^="MathJax-Element-"][id$="-Frame"]', type: 'mathml' },
        { name: "StackExchange", match: /stackexchange\.com/, selector: 'span.math-container', type: 'mathml' },
        { name: "Gemini", match: /gemini\.google\.com/, selector: '[data-math]', type: 'latex' },
        { name: "Others", match: /(chatgpt\.com|aistudio\.google\.com|deepseek\.com|moonshot\.cn|luogu\.com(?:\.cn)?|chatboxai\.app)/, selector: 'span.katex', type: 'mathml' }
    ];
    function getCurrentSiteConfig(url) {
        return siteConfig.find(site => site.match.test(url)) || null;
    }

    function extractMathML(element) {
        let mathML = element.querySelector('math');
        return mathML ? mathML.outerHTML : null;
    }

    function extractLaTeX(element) {
        if (element.hasAttribute('data-math')) return element.getAttribute('data-math');
        return null;
    }

    function extractMathString(element, type) {
        if (type === 'mathml') return extractMathML(element);
        if (type === 'latex') return extractLaTeX(element);
        return null;
    }

    function showCopySuccessTooltip(event) {
        const copyTooltip = document.createElement("div");
        copyTooltip.className = "mathml-copy-success";
        copyTooltip.innerText = "Copied";
        document.body.appendChild(copyTooltip);

        const x = event.clientX;
        const y = event.clientY;

        copyTooltip.style.left = `${x}px`;
        copyTooltip.style.top = `${y - 30}px`;

        setTimeout(() => {
            copyTooltip.style.opacity = "0";
            setTimeout(() => {
                if (copyTooltip.parentNode) document.body.removeChild(copyTooltip);
            }, 500);
        }, 1000);
    }

    function attachHandlers() {
        const config = getCurrentSiteConfig(window.location.href);
        if (!config) return;

        document.querySelectorAll(config.selector).forEach(element => {
            if (element.dataset.mathCopyAttached) return;
            element.dataset.mathCopyAttached = "true";

            element.addEventListener('mouseenter', () => element.classList.add('math-hover-effect'));
            element.addEventListener('mouseleave', () => element.classList.remove('math-hover-effect'));

            element.ondblclick = (event) => {
                event.stopPropagation();
                event.preventDefault();

                const mathString = extractMathString(element, config.type);
                if (mathString) {
                    navigator.clipboard.writeText(mathString).then(() => showCopySuccessTooltip(event));
                } else {
                    console.warn("No math content found inside this element.");
                }
                window.getSelection().removeAllRanges();
            };
        });
    }

    document.addEventListener('DOMContentLoaded', attachHandlers);
    new MutationObserver(attachHandlers).observe(document.body, { childList: true, subtree: true });

})();