您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Makes Claude, ChatGPT and Gemini chat interfaces wider, and replace Claude's default font. Adds a copy button to Gemini code blocks. | 扩展 Claude、ChatGPT 和 Gemini 布局,替换 Claude 字体,并为 Gemini 代码块添加复制按钮。
当前为
// ==UserScript== // @name WideScreen & Gemini Copy Button // @namespace https://example.com/Gemini // @match *://claude.ai/* // @match *://chatgpt.com/* // @match *://gemini.google.com/* // @version 1.1 // @author cores // @license MIT // @description Makes Claude, ChatGPT and Gemini chat interfaces wider, and replace Claude's default font. Adds a copy button to Gemini code blocks. | 扩展 Claude、ChatGPT 和 Gemini 布局,替换 Claude 字体,并为 Gemini 代码块添加复制按钮。 // ==/UserScript== (function() { 'use strict'; // Detect which platform we're on const isGemini = window.location.hostname.includes('gemini.google.com'); const isClaude = window.location.hostname.includes('claude.ai'); const isChatGPT = window.location.hostname.includes('chatgpt.com'); // Create a style element const style = document.createElement('style'); // Common font styles for all platforms const commonFontStyles = ` /* Common normalized font styles */ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important; } `; let platformSpecificStyles = ''; let geminiCopyButtonFeatureStyles = ''; // Will hold CSS for Gemini's copy button // Set platform-specific CSS for widescreen if (isGemini) { platformSpecificStyles = ` /* Gemini wide screen CSS */ .chat-window, .chat-container, .conversation-container, .gemini-conversation-container { max-width: 95% !important; width: 95% !important; } .input-area-container, textarea, .prompt-textarea, .prompt-container { max-width: 95% !important; width: 95% !important; } textarea { width: 100% !important; } .max-w-3xl, .max-w-4xl, .max-w-screen-md { max-width: 95% !important; } .message-content, .user-message, .model-response { width: 100% !important; max-width: 100% !important; } .pre-fullscreen { height: auto !important; } .input-buttons-wrapper-top { right: 8px !important; } `; } else if (isClaude) { platformSpecificStyles = ` /* Claude wide screen CSS */ .max-w-screen-md, .max-w-3xl, .max-w-4xl { max-width: 95% !important; } .w-full.max-w-3xl, .w-full.max-w-4xl { max-width: 95% !important; width: 95% !important; } .w-full.max-w-3xl textarea { width: 100% !important; } .mx-auto { max-width: 95% !important; } [data-message-author-role] { width: 100% !important; } .absolute.right-0 { right: 10px !important; } /* Claude specific font fixes */ p, h1, h2, h3, h4, h5, h6, span, div, textarea, input, button { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important; font-weight: 400 !important; } pre, code, .font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; } [data-message-author-role] p { font-size: 16px !important; line-height: 1.5 !important; letter-spacing: normal !important; } h1, h2, h3, h4, h5, h6 { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important; } `; } else if (isChatGPT) { platformSpecificStyles = ` /* ChatGPT wide screen CSS */ .mx-auto { max-width: 100% !important; width: auto !important; } .h-full { height: 100% !important; } .w-full { width: 100% !important; } .message-input, .input-area input, .input-area textarea { width: 100% !important; } .h-\\[116px\\] { /* Tailwind specific class, adjust if it changes */ height: auto !important; } `; } // --- Gemini Copy Button Feature --- if (isGemini) { const GEMINI_BUTTON_CLASS = 'gemini-custom-md-icon-copy-button'; const GEMINI_FOOTER_CLASS = 'gemini-custom-code-block-footer-centered'; const GEMINI_PROCESSED_MARKER_CLASS = 'gemini-custom-md-icon-copy-added'; const ICON_HTML_COPY_GEMINI = '<mat-icon role="img" fonticon="content_copy" class="mat-icon notranslate google-symbols mat-ligature-font mat-icon-no-color" aria-hidden="true" data-mat-icon-type="font" data-mat-icon-name="content_copy"></mat-icon>'; const ICON_HTML_CHECK_GEMINI = '<mat-icon role="img" fonticon="check" class="mat-icon notranslate google-symbols mat-ligature-font mat-icon-no-color" aria-hidden="true" data-mat-icon-type="font" data-mat-icon-name="check"></mat-icon>'; geminiCopyButtonFeatureStyles = ` .${GEMINI_FOOTER_CLASS} { display: flex; justify-content: center; align-items: center; padding: 8px 0px; margin-top: 8px; } .${GEMINI_BUTTON_CLASS} { background-color: transparent; color: #5f6368; border: none; padding: 0; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background-color 0.2s ease, color 0.2s ease, transform 0.1s ease; outline: none; } .${GEMINI_BUTTON_CLASS} .mat-icon { font-size: 24px; display: flex; align-items: center; justify-content: center; line-height: 1; } .${GEMINI_BUTTON_CLASS}:hover { background-color: rgba(0, 0, 0, 0.08); color: #202124; } .${GEMINI_BUTTON_CLASS}:active { background-color: rgba(0, 0, 0, 0.12); transform: scale(0.95); } `; function createGeminiCopyButton(codeBlockElement) { if (codeBlockElement.classList.contains(GEMINI_PROCESSED_MARKER_CLASS)) { return; } // Gemini specific selector for code content, assuming 'div.code-block' is the container const codeContentElement = codeBlockElement.querySelector('code[data-test-id="code-content"], pre code'); if (!codeContentElement) { return; } const copyButton = document.createElement('button'); copyButton.innerHTML = ICON_HTML_COPY_GEMINI; copyButton.className = GEMINI_BUTTON_CLASS; copyButton.setAttribute('aria-label', '复制代码'); copyButton.addEventListener('click', async (event) => { event.stopPropagation(); const codeText = codeContentElement.innerText; try { await navigator.clipboard.writeText(codeText); copyButton.innerHTML = ICON_HTML_CHECK_GEMINI; } catch (err) { alert('无法复制代码。请在浏览器设置中允许剪贴板访问,或手动复制。'); console.warn("Clipboard write failed: ", err); } setTimeout(() => { copyButton.innerHTML = ICON_HTML_COPY_GEMINI; }, 2500); }); let footerDiv = codeBlockElement.querySelector('.' + GEMINI_FOOTER_CLASS); if (!footerDiv) { footerDiv = document.createElement('div'); footerDiv.className = GEMINI_FOOTER_CLASS; codeBlockElement.appendChild(footerDiv); } footerDiv.appendChild(copyButton); codeBlockElement.classList.add(GEMINI_PROCESSED_MARKER_CLASS); } function processGeminiCodeBlocks() { // Gemini specific selector for the code block container const codeBlocks = document.querySelectorAll('div.code-block'); codeBlocks.forEach(createGeminiCopyButton); } // Initial run & observer for Gemini copy buttons if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', processGeminiCodeBlocks); } else { processGeminiCodeBlocks(); } const geminiCodeBlockObserver = 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) { if (node.matches && node.matches('div.code-block')) { createGeminiCopyButton(node); } node.querySelectorAll('div.code-block').forEach(createGeminiCopyButton); } }); } } }); // Observe document.body as code blocks can appear anywhere. // Ensure this doesn't conflict with other observers if they are too broad. // This specific observer is fine as its callback is focused. geminiCodeBlockObserver.observe(document.body, { childList: true, subtree: true }); } // --- End Gemini Copy Button Feature --- // Combine all styles and append to head style.textContent = commonFontStyles + platformSpecificStyles + geminiCopyButtonFeatureStyles; document.head.appendChild(style); // --- Existing Widescreen Logic --- // Function to apply wide mode to inline styles (especially for Gemini) function applyWideModeToInlineStyles() { if (!isGemini) return; // Only needed for Gemini widescreen part const elements = document.querySelectorAll('[style*="max-width"]'); elements.forEach(el => { if (el.classList.contains('side-panel') || el.classList.contains('navigation-panel')) { return; } // Check if it's not part of the new copy button feature elements, to avoid interference if (el.closest(`.${GEMINI_FOOTER_CLASS}`)) { return; } el.style.maxWidth = '95%'; }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { applyWideModeToInlineStyles(); }); } else { applyWideModeToInlineStyles(); } if (isGemini) { // This is the original observer for Gemini widescreen inline styles const geminiWidescreenObserver = new MutationObserver(function(mutations) { // Consider debouncing or more targeted checks if performance issues arise applyWideModeToInlineStyles(); }); geminiWidescreenObserver.observe(document.body, { childList: true, subtree: true, attributes: true, // Observing attributes might be intensive attributeFilter: ['style', 'class'] // Filter helps }); } if (isClaude) { // Original observer for Claude input fixes const claudeObserver = new MutationObserver(function(mutations) { if (claudeObserver.timeoutId) { // Simple debounce return; } claudeObserver.timeoutId = setTimeout(function() { const inputElements = document.querySelectorAll('textarea, [role="textbox"], div[contenteditable="true"]'); inputElements.forEach(el => { if (el && !el.dataset.widthFixedForWide) { // Use a distinct dataset property // Ensure this doesn't conflict with other styles // This aims to make input areas wider // el.style.width = '100%'; // This might be too aggressive depending on parent el.style.maxWidth = '100%'; // Allow it to fill its now wider parent el.dataset.widthFixedForWide = 'true'; } }); delete claudeObserver.timeoutId; }, 500); }); // Observe a relevant part of the DOM for Claude. // Observing document.body might be too broad if form isn't always present. // Waiting for a more specific container might be better or using a more robust initial selector. function startClaudeObserver() { const claudeForm = document.querySelector('form[enctype="multipart/form-data"], main form, body'); // Try to find a form, or fall back to body if (claudeForm) { claudeObserver.observe(claudeForm, { childList: true, subtree: true, attributes: false // Less intensive }); } else { // Fallback if form isn't found immediately, maybe try again or observe body // For now, this means if the form isn't there, observer might not start effectively. } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startClaudeObserver); } else { startClaudeObserver(); } } })();