Gemini &Gemini & chatgpt

Widescreen for Claude/ChatGPT/Gemini, Claude font fix & menu button. Gemini: code block copy & collapse buttons (header & footer). | 扩展三平台布局,Claude字体及菜单按钮。Gemini:代码块复制及头部/页脚折叠按钮。

当前为 2025-05-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Gemini &Gemini & chatgpt
// @namespace    https://example.com/aiHelp
// @match        *://claude.ai/*
// @match        *://chatgpt.com/*
// @match        *://gemini.google.com/*
// @match        https://gemini.google.com/app/*
// @version      1.3.2
// @author       cores
// @license      MIT
// @description  Widescreen for Claude/ChatGPT/Gemini, Claude font fix & menu button. Gemini: code block copy & collapse buttons (header & footer). | 扩展三平台布局,Claude字体及菜单按钮。Gemini:代码块复制及头部/页脚折叠按钮。
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    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');

    const style = document.createElement('style');
    const commonFontStyles = `
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important;
        }
    `;

    let platformSpecificStyles = '';
    let geminiFeaturesStyles = '';
    let claudeSpecificJsExecuted = false;

    // --- Platform Specific Styles (保持不变) ---
    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\\] { height: auto !important; }`;
    }

    // --- Gemini Features (Copy Button & Collapse Buttons) ---
    if (isGemini) {
        const GEMINI_CODE_BLOCK_SELECTOR = 'div.code-block';
        const GEMINI_CODE_CONTENT_SELECTOR = 'code[data-test-id="code-content"], pre code';
        const GEMINI_CUSTOM_COPY_BUTTON_CLASS = 'gemini-custom-md-icon-copy-button'; // For custom copy button in footer
        const GEMINI_FOOTER_CLASS = 'gemini-custom-code-block-footer-centered';
        const ATTR_GEMINI_COPY_BUTTON_PROCESSED = 'data-gemini-copy-button-processed';

        const GEMINI_CODE_HEADER_SELECTOR = 'div.code-block-decoration.header-formatted.gds-title-s';
        const GEMINI_ORIGINAL_BUTTONS_CONTAINER_SELECTOR = 'div.buttons[class*="ng-star-inserted"]';
        const GEMINI_COLLAPSIBLE_PANEL_SELECTOR = '.formatted-code-block-internal-container';

        const ATTR_HEADER_COLLAPSE_PROCESSED = 'data-gemini-header-collapse-processed';
        const ATTR_FOOTER_COLLAPSE_PROCESSED = 'data-gemini-footer-collapse-processed';
        const CLASS_HEADER_COLLAPSE_BTN = 'userscript-gemini-header-collapse-btn';
        const CLASS_FOOTER_COLLAPSE_BTN = 'userscript-gemini-footer-collapse-btn';


        geminiFeaturesStyles = `
            .${GEMINI_FOOTER_CLASS} { display: flex; justify-content: center; align-items: center; padding: 8px 0px; margin-top: 8px; gap: 8px; /* Added gap for buttons in footer */ }
            .${GEMINI_CUSTOM_COPY_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; flex-wrap: wrap; transition: background-color 0.2s ease, color 0.2s ease, transform 0.1s ease; outline: none; }
            .${GEMINI_CUSTOM_COPY_BUTTON_CLASS} .mat-icon { font-size: 24px; display: flex; align-items: center; justify-content: center; line-height: 1;flex-wrap:wrap; }
            .${GEMINI_CUSTOM_COPY_BUTTON_CLASS}:hover { background-color: rgba(0, 0, 0, 0.08); color: #202124; }
            .${GEMINI_CUSTOM_COPY_BUTTON_CLASS}:active { background-color: rgba(0, 0, 0, 0.12); transform: scale(0.95); }

            .${CLASS_HEADER_COLLAPSE_BTN} { margin-right: 4px; } /* Space for header collapse button */
            /* Footer collapse button will use GEMINI_CUSTOM_COPY_BUTTON_CLASS for base style, then its own class for identification */
            .${CLASS_FOOTER_COLLAPSE_BTN}.mat-mdc-icon-button { /* Ensure it matches other footer buttons if it were generic mdc */ }

            .userscript-collapsed-panel { display: none !important; }
        `;

        function createMaterialIconElement(iconName) {
            const iconElement = document.createElement('mat-icon');
            iconElement.setAttribute('role', 'img');
            iconElement.setAttribute('fonticon', iconName);
            iconElement.className = 'mat-icon notranslate google-symbols mat-ligature-font mat-icon-no-color';
            iconElement.setAttribute('aria-hidden', 'true');
            iconElement.setAttribute('data-mat-icon-type', 'font');
            iconElement.setAttribute('data-mat-icon-name', iconName);
            iconElement.textContent = iconName;
            return iconElement;
        }

        function updateSingleCollapseButtonIcon(buttonElement, isPanelCollapsed) {
            if (!buttonElement) return;
            while (buttonElement.firstChild) buttonElement.removeChild(buttonElement.firstChild);
            if (isPanelCollapsed) {
                buttonElement.appendChild(createMaterialIconElement('keyboard_arrow_down'));
                buttonElement.setAttribute('aria-label', '展开代码块');
                buttonElement.setAttribute('mattooltip', '展开代码块');
            } else {
                buttonElement.appendChild(createMaterialIconElement('keyboard_arrow_up'));
                buttonElement.setAttribute('aria-label', '收起代码块');
                buttonElement.setAttribute('mattooltip', '收起代码块');
            }
        }

        function syncRelatedCollapseButtons(codeBlockElement, panelIsCollapsed) {
            const headerBtn = codeBlockElement.querySelector(`.${CLASS_HEADER_COLLAPSE_BTN}`);
            const footerBtn = codeBlockElement.querySelector(`.${CLASS_FOOTER_COLLAPSE_BTN}`);
            updateSingleCollapseButtonIcon(headerBtn, panelIsCollapsed);
            updateSingleCollapseButtonIcon(footerBtn, panelIsCollapsed);
        }

        function addGeminiCustomCopyButton(codeBlockElement) {
            if (codeBlockElement.getAttribute(ATTR_GEMINI_COPY_BUTTON_PROCESSED) === 'true') {
                const existingFooter = codeBlockElement.querySelector('.' + GEMINI_FOOTER_CLASS);
                const existingButton = existingFooter ? existingFooter.querySelector('.' + GEMINI_CUSTOM_COPY_BUTTON_CLASS) : null;
                return { copyButton: existingButton, footerDiv: existingFooter };
            }

            const codeContentElement = codeBlockElement.querySelector(GEMINI_CODE_CONTENT_SELECTOR);
            if (!codeContentElement) return { copyButton: null, footerDiv: null };

            const copyButton = document.createElement('button');
            copyButton.className = GEMINI_CUSTOM_COPY_BUTTON_CLASS;
            copyButton.setAttribute('aria-label', '复制代码');
            copyButton.setAttribute('title', '复制代码 (Userscript)');
            copyButton.appendChild(createMaterialIconElement('content_copy'));

            copyButton.addEventListener('click', async (event) => {
                event.stopPropagation();
                const codeText = codeContentElement.innerText;
                try {
                    await navigator.clipboard.writeText(codeText);
                    while (copyButton.firstChild) copyButton.removeChild(copyButton.firstChild);
                    copyButton.appendChild(createMaterialIconElement('check'));
                } catch (err) {
                    alert('无法复制代码。\nCould not copy code.');
                }
                setTimeout(() => {
                    if (copyButton.isConnected) {
                        while (copyButton.firstChild) copyButton.removeChild(copyButton.firstChild);
                        copyButton.appendChild(createMaterialIconElement('content_copy'));
                    }
                }, 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); // Append copy button first
            codeBlockElement.setAttribute(ATTR_GEMINI_COPY_BUTTON_PROCESSED, 'true');
            return { copyButton: copyButton, footerDiv: footerDiv };
        }

        function addGeminiHeaderCollapseButton(codeBlockElement, panelToCollapse) {
            if (codeBlockElement.getAttribute(ATTR_HEADER_COLLAPSE_PROCESSED) === 'true' || !panelToCollapse) return;

            const headerDiv = codeBlockElement.querySelector(GEMINI_CODE_HEADER_SELECTOR);
            if (!headerDiv) return;

            const existingButtonsDiv = headerDiv.querySelector(GEMINI_ORIGINAL_BUTTONS_CONTAINER_SELECTOR);
            const collapseButton = document.createElement('button');
            collapseButton.className = `mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger ${CLASS_HEADER_COLLAPSE_BTN}`;
            collapseButton.setAttribute('mat-icon-button', '');

            collapseButton.addEventListener('click', (event) => {
                event.stopPropagation();
                const isCurrentlyCollapsed = panelToCollapse.classList.toggle('userscript-collapsed-panel');
                syncRelatedCollapseButtons(codeBlockElement, isCurrentlyCollapsed);
            });

            if (existingButtonsDiv && existingButtonsDiv.parentNode === headerDiv) {
                headerDiv.insertBefore(collapseButton, existingButtonsDiv);
            } else {
                headerDiv.prepend(collapseButton);
            }
            codeBlockElement.setAttribute(ATTR_HEADER_COLLAPSE_PROCESSED, 'true');
            // Initial icon will be set by a call to syncRelatedCollapseButtons later
        }

        function addGeminiFooterCollapseButton(codeBlockElement, panelToCollapse, footerDiv, copyButtonRef) {
            if (codeBlockElement.getAttribute(ATTR_FOOTER_COLLAPSE_PROCESSED) === 'true' || !panelToCollapse || !footerDiv) return;

            const collapseButton = document.createElement('button');
            // Use GEMINI_CUSTOM_COPY_BUTTON_CLASS as base for footer buttons for visual consistency
            collapseButton.className = `${GEMINI_CUSTOM_COPY_BUTTON_CLASS} ${CLASS_FOOTER_COLLAPSE_BTN}`;
            // No need for mat-icon-button attribute if GEMINI_CUSTOM_COPY_BUTTON_CLASS doesn't imply it and handles styling

            collapseButton.addEventListener('click', (event) => {
                event.stopPropagation();
                const isCurrentlyCollapsed = panelToCollapse.classList.toggle('userscript-collapsed-panel');
                syncRelatedCollapseButtons(codeBlockElement, isCurrentlyCollapsed);
            });

            // Insert footer collapse button to the left of the copy button
            if (copyButtonRef && copyButtonRef.parentNode === footerDiv) {
                footerDiv.insertBefore(collapseButton, copyButtonRef);
            } else {
                footerDiv.appendChild(collapseButton); // Fallback: append if copy button isn't found in footer
            }
            codeBlockElement.setAttribute(ATTR_FOOTER_COLLAPSE_PROCESSED, 'true');
            // Initial icon will be set by a call to syncRelatedCollapseButtons later
        }


        function initializeGeminiCodeBlockFeatures(codeBlockElement) {
            const panelToCollapse = codeBlockElement.querySelector(GEMINI_COLLAPSIBLE_PANEL_SELECTOR);

            const { copyButton, footerDiv } = addGeminiCustomCopyButton(codeBlockElement);

            if (panelToCollapse) {
                addGeminiHeaderCollapseButton(codeBlockElement, panelToCollapse);
                if (footerDiv && copyButton) { // Ensure footer and copy button exist before adding footer collapse
                    addGeminiFooterCollapseButton(codeBlockElement, panelToCollapse, footerDiv, copyButton);
                }
                // Set initial state for all collapse buttons after they are potentially added
                syncRelatedCollapseButtons(codeBlockElement, panelToCollapse.classList.contains('userscript-collapsed-panel'));
            }
        }

        function observeGeminiCodeBlocks() {
            document.querySelectorAll(GEMINI_CODE_BLOCK_SELECTOR).forEach(initializeGeminiCodeBlockFeatures);
            const geminiCodeBlockObserver = new MutationObserver((mutationsList) => {
                mutationsList.forEach(mutation => {
                    if (mutation.type === 'childList') {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === Node.ELEMENT_NODE) {
                                if (node.matches && node.matches(GEMINI_CODE_BLOCK_SELECTOR)) {
                                    initializeGeminiCodeBlockFeatures(node);
                                }
                                node.querySelectorAll(GEMINI_CODE_BLOCK_SELECTOR).forEach(initializeGeminiCodeBlockFeatures);
                            }
                        });
                    }
                });
            });
            geminiCodeBlockObserver.observe(document.body, { childList: true, subtree: true });
        }

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', observeGeminiCodeBlocks);
        } else {
            observeGeminiCodeBlocks();
        }
    }

    // --- Claude Specific JavaScript (保持不变) ---
    if (isClaude && !claudeSpecificJsExecuted) {
        const CLAUDE_USER_MENU_BUTTON_ID = 'userscript-claude-custom-button';
        const CLAUDE_SETTINGS_BUTTON_SELECTOR = 'button[data-testid="user-menu-settings"]';
        function addCustomButtonToClaudeMenu() {
            const settingsButton = document.querySelector(CLAUDE_SETTINGS_BUTTON_SELECTOR);
            if (!settingsButton) return;
            const menu = settingsButton.closest('div[role="menu"], div[data-radix-menu-content]');
            if (!menu || document.getElementById(CLAUDE_USER_MENU_BUTTON_ID)) return;
            const newButton = document.createElement('button');
            newButton.id = CLAUDE_USER_MENU_BUTTON_ID;
            newButton.className = settingsButton.className;
            newButton.setAttribute('role', 'menuitem');
            newButton.setAttribute('tabindex', '-1');
            newButton.setAttribute('data-orientation', 'vertical');
            newButton.textContent = "recents";
            newButton.addEventListener('click', () => { window.location.href = 'https://claude.ai/recents'; });
            if (settingsButton.parentNode) settingsButton.parentNode.insertBefore(newButton, settingsButton.nextSibling);
        }
        const claudeMenuObserver = new MutationObserver(addCustomButtonToClaudeMenu);
        claudeMenuObserver.observe(document.body, { childList: true, subtree: true });
        let claudeInputObserver;
        function startClaudeInputObserver() {
            if (claudeInputObserver) return;
            claudeInputObserver = new MutationObserver(function() {
                if (claudeInputObserver.timeoutId) return;
                claudeInputObserver.timeoutId = setTimeout(function() {
                    document.querySelectorAll('textarea, [role="textbox"], div[contenteditable="true"]').forEach(el => {
                        if (el && !el.dataset.widthFixedForWide) { el.style.maxWidth = '100%'; el.dataset.widthFixedForWide = 'true'; }
                    });
                    delete claudeInputObserver.timeoutId;
                }, 500);
            });
            const claudeForm = document.querySelector('form[enctype="multipart/form-data"], main form, body');
            if (claudeForm) claudeInputObserver.observe(claudeForm, { childList: true, subtree: true, attributes: false });
        }
        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', startClaudeInputObserver);
        else startClaudeInputObserver();
        claudeSpecificJsExecuted = true;
    }

    // --- Append all styles to head ---
    style.textContent = commonFontStyles + platformSpecificStyles + geminiFeaturesStyles;
    document.head.appendChild(style);

    // --- Widescreen Logic for inline styles (Gemini - 保持不变) ---
    function applyWideModeToInlineStyles() {
        if (!isGemini) return;
        const elements = document.querySelectorAll('[style*="max-width"]');
        elements.forEach(el => {
            if (el.classList.contains('side-panel') || el.classList.contains('navigation-panel')) return;
            if (typeof GEMINI_FOOTER_CLASS !== 'undefined' && el.closest(`.${GEMINI_FOOTER_CLASS}`)) return;
            el.style.maxWidth = '95%';
        });
    }
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', applyWideModeToInlineStyles);
    else applyWideModeToInlineStyles();
    if (isGemini) {
        const geminiWidescreenObserver = new MutationObserver(applyWideModeToInlineStyles);
        geminiWidescreenObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
    }

})();