Add Copy Button to Chat Messages on Gtihub Copilot web page

Adds a "Copy" button to chat message elements to easily copy their content.

当前为 2024-12-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Add Copy Button to Chat Messages on Gtihub Copilot web page
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Adds a "Copy" button to chat message elements to easily copy their content.
  6. // @author aspen138
  7. // @match *://github.com/copilot/c/*
  8. // @match *://github.com/copilot/
  9. // @match *://github.com/copilot/*
  10. // @grant none
  11. // @run-at document-end
  12. // @icon https://github.com/favicons/favicon-copilot.svg
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16.  
  17.  
  18.  
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23. // Update these if class names change
  24. const MESSAGE_CONTENT_CLASS = 'UserMessage-module__container--cAvvK';
  25. const CHAT_MESSAGE_CONTENT_CLASS = 'ChatMessage-module__content--MYneF';
  26.  
  27. /**
  28. * Creates and returns a copy button element.
  29. */
  30. function createCopyButton() {
  31. const button = document.createElement('button');
  32. button.innerText = 'Copy';
  33. button.classList.add('copy-button');
  34.  
  35. // Use sticky positioning to keep it visible while the element is in view
  36. button.style.position = 'sticky';
  37. button.style.top = '10px';
  38. button.style.right = '10px';
  39. button.style.backgroundColor = '#4CAF50';
  40. button.style.color = '#fff';
  41. button.style.border = 'none';
  42. button.style.borderRadius = '4px';
  43. button.style.padding = '5px 10px';
  44. button.style.cursor = 'pointer';
  45. button.style.fontSize = '0.9em';
  46. button.style.zIndex = '1000';
  47. button.style.boxShadow = '0 2px 6px rgba(0,0,0,0.2)';
  48. button.style.marginLeft = 'auto';
  49. button.style.float = 'right';
  50. button.style.display = 'inline-block';
  51. // Ensure parent or relevant ancestor allows sticky to function
  52. // For sticky to work, the ancestor should have no overflow constraints that break it.
  53.  
  54. button.addEventListener('mouseenter', () => {
  55. button.style.backgroundColor = '#45a049';
  56. });
  57. button.addEventListener('mouseleave', () => {
  58. button.style.backgroundColor = '#4CAF50';
  59. });
  60.  
  61. return button;
  62. }
  63.  
  64. /**
  65. * Adds a copy button to a chat message element.
  66. * @param {HTMLElement} messageElement
  67. */
  68. function addCopyButton(messageElement) {
  69. // Prevent adding multiple buttons
  70. if (messageElement.querySelector('.copy-button')) return;
  71.  
  72. const messageContent = messageElement.querySelector(`.${MESSAGE_CONTENT_CLASS}`);
  73. if (!messageContent) return;
  74.  
  75. // Ensure the parent is a block-level container that supports sticky
  76. messageElement.style.position = 'relative';
  77. messageElement.style.display = 'block';
  78.  
  79. const copyButton = createCopyButton();
  80.  
  81. copyButton.addEventListener('click', () => {
  82. const textToCopy = messageContent.innerText.trim();
  83. navigator.clipboard.writeText(textToCopy).then(() => {
  84. copyButton.innerText = 'Copied!';
  85. copyButton.style.backgroundColor = '#388E3C';
  86. setTimeout(() => {
  87. copyButton.innerText = 'Copy';
  88. copyButton.style.backgroundColor = '#4CAF50';
  89. }, 2000);
  90. }).catch(err => {
  91. console.error('Failed to copy text: ', err);
  92. });
  93. });
  94.  
  95. messageElement.appendChild(copyButton);
  96. }
  97.  
  98. /**
  99. * Processes all existing chat messages on page load.
  100. */
  101. function processExistingMessages() {
  102. const messageElements = document.querySelectorAll(`.${CHAT_MESSAGE_CONTENT_CLASS}`);
  103. messageElements.forEach(messageElement => addCopyButton(messageElement));
  104. }
  105.  
  106. /**
  107. * Observes newly added messages dynamically.
  108. */
  109. function observeNewMessages() {
  110. const targetNode = document.body;
  111. const config = { childList: true, subtree: true };
  112.  
  113. const callback = (mutationsList) => {
  114. for (const mutation of mutationsList) {
  115. if (mutation.type === 'childList') {
  116. mutation.addedNodes.forEach(node => {
  117. if (node.nodeType === Node.ELEMENT_NODE) {
  118. if (node.classList && node.classList.contains(CHAT_MESSAGE_CONTENT_CLASS)) {
  119. addCopyButton(node);
  120. }
  121. const nestedMessages = node.querySelectorAll(`.${CHAT_MESSAGE_CONTENT_CLASS}`);
  122. nestedMessages.forEach(nestedNode => addCopyButton(nestedNode));
  123. }
  124. });
  125. }
  126. }
  127. };
  128.  
  129. const observer = new MutationObserver(callback);
  130. observer.observe(targetNode, config);
  131. }
  132.  
  133. function init() {
  134. processExistingMessages();
  135. observeNewMessages();
  136. }
  137.  
  138. if (document.readyState === 'loading') {
  139. document.addEventListener('DOMContentLoaded', init);
  140. } else {
  141. init();
  142. }
  143.  
  144. })();
  145.  
  146.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址