Duplicate the header of code blocks as a footer to give a second copy code button
< Feedback on Copy Code in Code Block Footer
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 = '✔ 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);
}
})();
perfect update
thanks
keep it up
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
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 = '✔ 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);
}
})();
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
You're welcome! That's nice! Also, feel free to update it whenever you feel like it :)
Thanks for creating this!
I've updated the code since it's currently broken by replacing:
with
I've made a few additional changes:
Here's the full code: