chatgpt-export

Add a copy button next to LaTeX formulas

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==UserScript==
// @name         chatgpt-export
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Add a copy button next to LaTeX formulas
// @author       You
// @match        https://chat.openai.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    let debounceTimer;

    const addCopyButtons = () => {
        const mathDivs = document.querySelectorAll('.math, .math-inline, .math-display');
        const copiedButtonHtml = `<button
        class="flex ml-auto gap-2 rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400"><svg
            stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round"
            stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
            <polyline points="20 6 9 17 4 12"></polyline>
        </svg></button>`
        const copyButtonHtml = `<button
        class="flex ml-auto gap-2 rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400"><svg
            stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round"
            stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
            <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
            <rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
        </svg></button>`

        mathDivs.forEach(div => {
            if (div.getAttribute('data-copy-button-added') === 'true') return;
            div.setAttribute('data-copy-button-added', 'true');
    
            const annotation = div.querySelector('annotation[encoding="application/x-tex"]');
            if (annotation) {
                let latexText = annotation.textContent;
                const copyButton = document.createElement('span');
                copyButton.className = 'copy-button text-gray-400 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400';
                copyButton.innerHTML = copyButtonHtml;
                copyButton.style.verticalAlign = 'middle'; // 确保与行内文本对齐
                copyButton.style.display = 'inline-flex';  // 使用 flex 布局保持行内
    
                if (div.classList.contains('math-display')) {
                    latexText = `$$ ${latexText} $$`
                    copyButton.style.position = 'absolute';
                    copyButton.style.top = '0';
                    copyButton.style.right = '0';
                    copyButton.style.zIndex = '1';
                    div.style.position = 'relative';
                    div.appendChild(copyButton);
                } else if (div.classList.contains('math-inline')) {
                    latexText = `$ ${latexText} $`
                    copyButton.style.marginLeft = '5px';
                    div.parentNode.insertBefore(copyButton, div.nextSibling);
                }
    
                copyButton.addEventListener('click', () => {
                    navigator.clipboard.writeText(latexText).then(() => {
                        copyButton.innerHTML = copiedButtonHtml;
                        setTimeout(() => {
                            copyButton.innerHTML = copyButtonHtml;
                        }, 2000);
                    }).catch(err => {
                        console.error('复制失败:', err);
                    });
                });
            }
        });
    };
    
    const debounceAddCopyButtons = () => {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(addCopyButtons, 300);
    };

    addCopyButtons();  // Run once initially

    const observer = new MutationObserver(() => {
        console.log("DOM has changed, rechecking...");
        debounceAddCopyButtons();
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();