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 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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'] });
    }

})();