Copy Code in Code Block Footer

Duplicate the header of code blocks as a footer to give a second copy code button

< Відгуки до Copy Code in Code Block Footer

Question/comment

NWP
§
Опубліковано: 07.02.2024

Thanks for creating this!

I've updated the code since it's currently broken by replacing:

var codeBlocks = document.querySelectorAll('div[class^="bg-black"]');

with

const codeBlocks = document.querySelectorAll('div[class*="bg-black"]');

I've made a few additional changes:

  • added error handling
  • added modern variable naming
  • replaced copyToClipboard textarea method with userscript's Clipboard API

Here's the full code:

// ==UserScript==
// @name         Copy Code in Code Block Footer
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Duplicate the header of code blocks as a footer to give a second copy code button
// @author       Gavin Trutzenbach / @gaveroid
// @match        https://chat.openai.com/*
// @grant        GM_setClipboard
// ==/UserScript==

(function() {
    'use strict';

    // Helper function to copy text to clipboard using the Clipboard API
    function copyToClipboard(text) {
        GM_setClipboard(text, 'text');
    }

    // Function to process code blocks
    function processCodeBlocks() {
        try {
            const codeBlocks = document.querySelectorAll('div[class*="bg-black"]');
            codeBlocks.forEach(function(codeBlock) {
                const headerDiv = codeBlock.querySelector('div[class^="flex items-center"]');
                if (headerDiv && !codeBlock.querySelector('.duplicated-footer')) {
                    const footerDiv = headerDiv.cloneNode(true);
                    footerDiv.classList.add('duplicated-footer');
                    codeBlock.appendChild(footerDiv);

                    // Update copy button function
                    const updateCopyButtonFunction = function(button) {
                        try {
                            const originalInnerHTML = button.innerHTML; // Store the original inner HTML
                            const copyFunction = function() {
                                try {
                                    const codeText = codeBlock.querySelector('code').innerText;
                                    copyToClipboard(codeText);
                                    this.innerHTML = '&#10004; Copied!'; // ASCII icon for checkmark
                                    const btn = this;
                                    setTimeout(function() {
                                        btn.innerHTML = originalInnerHTML; // Revert to original inner HTML
                                    }, 3000); // Revert back after 3 seconds
                                } catch (err) {
                                    console.error("Error during copy operation:", err);
                                }
                            };
                            button.removeEventListener('click', copyFunction); // Remove old event listener
                            button.addEventListener('click', copyFunction); // Add new event listener
                        } catch (err) {
                            console.error("Failed to update copy button function:", err);
                        }
                    };

                    // Update copy buttons in header and footer
                    const copyButtonHeader = headerDiv.querySelector('button');
                    const copyButtonFooter = footerDiv.querySelector('button');
                    updateCopyButtonFunction(copyButtonHeader);
                    updateCopyButtonFunction(copyButtonFooter);
                }
            });
        } catch (err) {
            console.error("Failed to process code blocks:", err);
        }
    }

    // Create a MutationObserver to monitor the DOM for changes
    try {
        const observer = new MutationObserver(function() {
            processCodeBlocks();
        });

        // Start observing the document with the configured parameters
        observer.observe(document, { childList: true, subtree: true });
    } catch (err) {
        console.error("MutationObserver failed:", err);
    }
})();
NWP
§
Опубліковано: 08.02.2024

I made a mistake when updating the code, as the code above only works when Superpower ChatGPT extension/addon is enabled.

I replaced:

const codeBlocks = document.querySelectorAll('div[class*="bg-black"]');

with:

const codeBlocks = document.querySelectorAll('div[class*="bg-black"], div[class*="bg-gray-950"]');

This is the fix for the regular Chat GPT, which also works when Superpower ChatGPT is enabled:

// ==UserScript==
// @name         Copy Code in Code Block Footer
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Duplicate the header of code blocks as a footer to give a second copy code button
// @author       Gavin Trutzenbach / @gaveroid
// @match        https://chat.openai.com/*
// @grant        GM_setClipboard
// ==/UserScript==

(function() {
    'use strict';

    // Helper function to copy text to clipboard using the Clipboard API
    function copyToClipboard(text) {
        GM_setClipboard(text, 'text');
    }

    // Function to process code blocks
    function processCodeBlocks() {
        try {
            const codeBlocks = document.querySelectorAll('div[class*="bg-black"], div[class*="bg-gray-950"]');
            codeBlocks.forEach(function(codeBlock) {
                const headerDiv = codeBlock.querySelector('div[class^="flex items-center"]');
                if (headerDiv && !codeBlock.querySelector('.duplicated-footer')) {
                    const footerDiv = headerDiv.cloneNode(true);
                    footerDiv.classList.add('duplicated-footer');
                    codeBlock.appendChild(footerDiv);

                    // Update copy button function
                    const updateCopyButtonFunction = function(button) {
                        try {
                            const originalInnerHTML = button.innerHTML; // Store the original inner HTML
                            const copyFunction = function() {
                                try {
                                    const codeText = codeBlock.querySelector('code').innerText;
                                    copyToClipboard(codeText);
                                    this.innerHTML = '&#10004; Copied!'; // ASCII icon for checkmark
                                    const btn = this;
                                    setTimeout(function() {
                                        btn.innerHTML = originalInnerHTML; // Revert to original inner HTML
                                    }, 3000); // Revert back after 3 seconds
                                } catch (err) {
                                    console.error("Error during copy operation:", err);
                                }
                            };
                            button.removeEventListener('click', copyFunction); // Remove old event listener
                            button.addEventListener('click', copyFunction); // Add new event listener
                        } catch (err) {
                            console.error("Failed to update copy button function:", err);
                        }
                    };

                    // Update copy buttons in header and footer
                    const copyButtonHeader = headerDiv.querySelector('button');
                    const copyButtonFooter = footerDiv.querySelector('button');
                    updateCopyButtonFunction(copyButtonHeader);
                    updateCopyButtonFunction(copyButtonFooter);
                }
            });
        } catch (err) {
            console.error("Failed to process code blocks:", err);
        }
    }

    // Create a MutationObserver to monitor the DOM for changes
    try {
        const observer = new MutationObserver(function() {
            processCodeBlocks();
        });

        // Start observing the document with the configured parameters
        observer.observe(document, { childList: true, subtree: true });
    } catch (err) {
        console.error("MutationObserver failed:", err);
    }
})();
§
Опубліковано: 30.04.2024

perfect update
thanks
keep it up

NWP
§
Опубліковано: 09.05.2024

perfect update
thanks
keep it up

You're welcome! :)

I'm posting a new update below since the domain has changed now from chat.openai.com to chatgpt.com

NWP
§
Опубліковано: 09.05.2024

Updated domain from chat.openai.com to chatgpt.com

// ==UserScript==
// @name         Copy Code in Code Block Footer
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Duplicate the header of code blocks as a footer to give a second copy code button
// @author       Gavin Trutzenbach / @gaveroid
// @match        https://chat.openai.com/*
// @match        https://chatgpt.com/*
// @grant        GM_setClipboard
// ==/UserScript==

(function() {
    'use strict';

    // Helper function to copy text to clipboard using the Clipboard API
    function copyToClipboard(text) {
        GM_setClipboard(text, 'text');
    }

    // Function to process code blocks
    function processCodeBlocks() {
        try {
            const codeBlocks = document.querySelectorAll('div[class*="bg-black"], div[class*="bg-gray-950"]');
            codeBlocks.forEach(function(codeBlock) {
                const headerDiv = codeBlock.querySelector('div[class^="flex items-center"]');
                if (headerDiv && !codeBlock.querySelector('.duplicated-footer')) {
                    const footerDiv = headerDiv.cloneNode(true);
                    footerDiv.classList.add('duplicated-footer');
                    codeBlock.appendChild(footerDiv);

                    // Update copy button function
                    const updateCopyButtonFunction = function(button) {
                        try {
                            const originalInnerHTML = button.innerHTML; // Store the original inner HTML
                            const copyFunction = function() {
                                try {
                                    const codeText = codeBlock.querySelector('code').innerText;
                                    copyToClipboard(codeText);
                                    this.innerHTML = '&#10004; Copied!'; // ASCII icon for checkmark
                                    const btn = this;
                                    setTimeout(function() {
                                        btn.innerHTML = originalInnerHTML; // Revert to original inner HTML
                                    }, 3000); // Revert back after 3 seconds
                                } catch (err) {
                                    console.error("Error during copy operation:", err);
                                }
                            };
                            button.removeEventListener('click', copyFunction); // Remove old event listener
                            button.addEventListener('click', copyFunction); // Add new event listener
                        } catch (err) {
                            console.error("Failed to update copy button function:", err);
                        }
                    };

                    // Update copy buttons in header and footer
                    const copyButtonHeader = headerDiv.querySelector('button');
                    const copyButtonFooter = footerDiv.querySelector('button');
                    updateCopyButtonFunction(copyButtonHeader);
                    updateCopyButtonFunction(copyButtonFooter);
                }
            });
        } catch (err) {
            console.error("Failed to process code blocks:", err);
        }
    }

    // Create a MutationObserver to monitor the DOM for changes
    try {
        const observer = new MutationObserver(function() {
            processCodeBlocks();
        });

        // Start observing the document with the configured parameters
        observer.observe(document, { childList: true, subtree: true });
    } catch (err) {
        console.error("MutationObserver failed:", err);
    }
})();
§
Опубліковано: 11.05.2024

Thanks for keeping it updated.
I've noticed that too and I updated it myself, but I will keep getting emails if you ever post newer versions/comments on this page anyway

NWP
§
Опубліковано: 17.05.2024

You're welcome! That's nice! Also, feel free to update it whenever you feel like it :)

Опублікувати відповідь

Sign in to post a reply.