// ==UserScript== // @name gemini 代码底部增加复制按钮 // @namespace http://tampermonkey.net/ // @version 0.2 // @description gemini Adds a "Copy Code" button to the bottom of specified code block structures. // @author Your Name // @match https://gemini.google.com/app/* // @grant GM_addStyle // @grant GM_setClipboard // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; const CUSTOM_BUTTON_CLASS = 'custom-bottom-copy-button'; const FOOTER_CLASS = 'custom-code-block-footer'; const PROCESSED_MARKER_CLASS = 'custom-bottom-copy-added'; function createAndAddButton(codeBlockElement) { // Check if already processed if (codeBlockElement.classList.contains(PROCESSED_MARKER_CLASS)) { return; } // Try to find the code content element // Prioritize the specific data-test-id if available, then fall back to pre code const codeContentElement = codeBlockElement.querySelector('code[data-test-id="code-content"], pre code'); if (!codeContentElement) { // console.warn('[Custom Copy Button] Code content element not found in:', codeBlockElement); return; } // Create the button const copyButton = document.createElement('button'); copyButton.textContent = '复制代码'; // "Copy Code" copyButton.className = CUSTOM_BUTTON_CLASS; copyButton.setAttribute('aria-label', '复制代码'); copyButton.addEventListener('click', async (event) => { event.stopPropagation(); // Prevent event bubbling const codeText = codeContentElement.innerText; // Use innerText to get the rendered text const originalButtonText = copyButton.textContent; try { await navigator.clipboard.writeText(codeText); copyButton.textContent = '已复制!'; // console.log('[Custom Copy Button] Code copied via navigator.clipboard.'); } catch (err) { // console.error('[Custom Copy Button] Navigator.clipboard failed:', err); try { GM_setClipboard(codeText); // Fallback to GM_setClipboard copyButton.textContent = '已复制 (GM)!'; // console.log('[Custom Copy Button] Code copied via GM_setClipboard.'); } catch (gmErr) { // console.error('[Custom Copy Button] GM_setClipboard failed:', gmErr); copyButton.textContent = '复制失败'; alert('无法复制代码到剪贴板。请手动复制。'); } } setTimeout(() => { copyButton.textContent = originalButtonText; }, 2500); }); // Create a footer element to hold the button let footerDiv = codeBlockElement.querySelector('.' + FOOTER_CLASS); if (!footerDiv) { footerDiv = document.createElement('div'); footerDiv.className = FOOTER_CLASS; codeBlockElement.appendChild(footerDiv); // Append footer to the code block } footerDiv.appendChild(copyButton); codeBlockElement.classList.add(PROCESSED_MARKER_CLASS); } function processAllCodeBlocks() { const codeBlocks = document.querySelectorAll('div.code-block'); codeBlocks.forEach(block => { createAndAddButton(block); }); } // Add styles for the new button and footer GM_addStyle(` .${FOOTER_CLASS} { display: flex; justify-content: flex-end; /* Aligns button to the right */ padding: 8px; margin-top: 5px; /* Space between code and footer */ border-top: 1px solid #e0e0e0; /* Optional separator */ background-color: #f9f9f9; /* Slight background for the footer */ } .${CUSTOM_BUTTON_CLASS} { background-color: #4CAF50; /* Green */ color: white; border: none; padding: 8px 16px; text-align: center; text-decoration: none; display: inline-block; font-size: 13px; border-radius: 4px; cursor: pointer; transition: background-color 0.3s ease; } .${CUSTOM_BUTTON_CLASS}:hover { background-color: #45a049; /* Darker Green */ } .${CUSTOM_BUTTON_CLASS}:active { background-color: #3e8e41; } `); // Initial run processAllCodeBlocks(); // Use MutationObserver to handle dynamically loaded code blocks const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { // Check if the added node is a code block itself if (node.matches && node.matches('div.code-block')) { createAndAddButton(node); } // Check if the added node contains code blocks node.querySelectorAll('div.code-block').forEach(createAndAddButton); } }); } } }); observer.observe(document.body, { childList: true, subtree: true }); // You can expose the function for debugging if needed: // window.customProcessCodeBlocks = processAllCodeBlocks; })();