OpenAI Chat Copy Code Button

Add a 'Copy Code' button to the bottom right hand of code blocks on chatgpt.com with animation

  1. // ==UserScript==
  2. // @name OpenAI Chat Copy Code Button
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description Add a 'Copy Code' button to the bottom right hand of code blocks on chatgpt.com with animation
  6. // @author Your Name
  7. // @match https://chatgpt.com/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const copyToClipboard = (text, button) => {
  16. navigator.clipboard.writeText(text).then(() => {
  17. console.log('Copied code to clipboard');
  18. button.innerHTML = 'Copied!';
  19. setTimeout(() => {
  20. button.innerHTML = ''; // Clear the text
  21. const svgIcon = getSVGIcon(); // Re-add the SVG icon
  22. button.appendChild(svgIcon);
  23. }, 2000);
  24. }, (err) => {
  25. console.error('Failed to copy code: ', err);
  26. });
  27. };
  28.  
  29. const getSVGIcon = () => {
  30. const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  31. svgIcon.setAttribute('width', '24');
  32. svgIcon.setAttribute('height', '24');
  33. svgIcon.setAttribute('fill', 'none');
  34. svgIcon.setAttribute('viewBox', '0 0 24 24');
  35. svgIcon.classList.add('icon-sm');
  36.  
  37. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  38. path.setAttribute('fill', 'currentColor');
  39. path.setAttribute('fill-rule', 'evenodd');
  40. path.setAttribute('d', 'M7 5a3 3 0 0 1 3-3h9a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-2v2a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3h2zm2 2h5a3 3 0 0 1 3 3v5h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-9a1 1 0 0 0-1 1zM5 9a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-9a1 1 0 0 0-1-1z');
  41. path.setAttribute('clip-rule', 'evenodd');
  42.  
  43. svgIcon.appendChild(path);
  44. return svgIcon;
  45. };
  46.  
  47. const addButton = (elem) => {
  48. const button = document.createElement('button');
  49. const svgIcon = getSVGIcon();
  50. button.appendChild(svgIcon);
  51.  
  52. button.style.position = 'absolute';
  53. button.style.bottom = '8px';
  54. button.style.right = '8px';
  55. button.style.fontSize = '12px';
  56. button.style.padding = '4px 8px';
  57. button.style.border = '1px solid #ccc';
  58. button.style.borderRadius = '3px';
  59. button.style.background = 'rgba(0,0,0,0.1)';
  60. button.style.color = 'white';
  61. button.style.cursor = 'pointer';
  62. button.style.zIndex = '10';
  63. button.style.transition = 'background-color 0.3s ease';
  64.  
  65. button.addEventListener('click', (e) => {
  66. e.stopPropagation();
  67. copyToClipboard(elem.querySelector('code').textContent, button);
  68. });
  69.  
  70. button.addEventListener('mouseover', () => {
  71. button.style.backgroundColor = 'rgba(0,0,0,0.2)';
  72. });
  73.  
  74. button.addEventListener('mouseout', () => {
  75. button.style.backgroundColor = 'rgba(0,0,0,0.1)';
  76. });
  77.  
  78. elem.style.position = 'relative';
  79. elem.appendChild(button);
  80. };
  81.  
  82. const observeCodeBlocks = () => {
  83. const codeBlocks = document.querySelectorAll('pre:not(.copy-code-processed)');
  84. if (codeBlocks.length) {
  85. codeBlocks.forEach(block => {
  86. addButton(block);
  87. block.classList.add('copy-code-processed');
  88. });
  89. }
  90. };
  91.  
  92. const observer = new MutationObserver(observeCodeBlocks);
  93. observer.observe(document.body, { childList: true, subtree: true });
  94.  
  95. observeCodeBlocks();
  96. })();