Widescreen for Claude/ChatGPT/Gemini, Claude font fix & menu button. Gemini: code block copy & collapse buttons (header & footer). | 扩展三平台布局,Claude字体及菜单按钮。Gemini:代码块复制及头部/页脚折叠按钮。(重构版)
当前为
// ==UserScript==
// @name Gemini & Claude & ChatGPT
// @namespace https://example.com/aiHelp
// @match *://claude.ai/*
// @match *://chatgpt.com/*
// @match *://gemini.google.com/*
// @match https://gemini.google.com/app/*
// @version 1.4.0
// @author cores (Refactored by Gemini AI)
// @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 PLATFORM_HOSTS = {
GEMINI: 'gemini.google.com',
CLAUDE: 'claude.ai',
CHATGPT: 'chatgpt.com'
};
// Gemini 相关选择器和类名
const GEMINI_SELECTORS = {
CODE_BLOCK: 'div.code-block',
CODE_CONTENT: 'code[data-test-id="code-content"], pre code',
CODE_HEADER: 'div.code-block-decoration.header-formatted.gds-title-s',
ORIGINAL_BUTTONS_CONTAINER: 'div.buttons[class*="ng-star-inserted"]',
COLLAPSIBLE_PANEL: '.formatted-code-block-internal-container',
SIDE_PANEL: '.side-panel', // 用于排除宽屏调整
NAVIGATION_PANEL: '.navigation-panel' // 用于排除宽屏调整
};
const GEMINI_CLASSES = {
CUSTOM_COPY_BUTTON: 'userscript-gemini-custom-copy-button',
FOOTER_CONTAINER: 'userscript-gemini-code-block-footer',
HEADER_COLLAPSE_BUTTON: 'userscript-gemini-header-collapse-btn',
FOOTER_COLLAPSE_BUTTON: 'userscript-gemini-footer-collapse-btn',
COLLAPSED_PANEL: 'userscript-collapsed-panel'
};
const GEMINI_ATTRIBUTES = {
COPY_BUTTON_PROCESSED: 'data-userscript-copy-processed',
HEADER_COLLAPSE_PROCESSED: 'data-userscript-header-collapse-processed',
FOOTER_COLLAPSE_PROCESSED: 'data-userscript-footer-collapse-processed'
};
// Claude 相关选择器和ID
const CLAUDE_SELECTORS = {
SETTINGS_BUTTON: 'button[data-testid="user-menu-settings"]',
MENU_CONTAINER: 'div[role="menu"], div[data-radix-menu-content]',
TEXT_INPUT_ELEMENTS: 'textarea, [role="textbox"], div[contenteditable="true"]',
FORM_CONTAINER: 'form[enctype="multipart/form-data"], main form, body'
};
const CLAUDE_IDS = {
CUSTOM_MENU_BUTTON: 'userscript-claude-custom-recents-button'
};
const CLAUDE_ATTRIBUTES = {
WIDTH_FIXED: 'data-userscript-width-fixed'
};
// --- 工具函数 ---
const Utils = {
/**
* 创建 Material Design 图标元素 (用于 Gemini)
* @param {string} iconName - 图标名称 (例如 'content_copy')
* @returns {HTMLElement} mat-icon 元素
*/
createMaterialIconElement: function(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;
},
/**
* 向文档头部添加样式
* @param {string} cssString - 要添加的 CSS 字符串
* @returns {HTMLStyleElement} 添加的 style 元素
*/
addStylesToHead: function(cssString) {
const styleElement = document.createElement('style');
styleElement.textContent = cssString;
document.head.appendChild(styleElement);
return styleElement;
},
/**
* 观察 DOM 变化并执行回调
* @param {Node} targetNode - 被观察的 DOM 节点
* @param {MutationObserverInit} observerOptions - MutationObserver 的配置选项
* @param {Function} callback - 当检测到变化时执行的回调函数
* @returns {MutationObserver} 创建的 MutationObserver 实例
*/
observeDOMChanges: function(targetNode, observerOptions, callback) {
const observer = new MutationObserver(callback);
observer.observe(targetNode, observerOptions);
return observer;
}
};
// --- 样式管理器 ---
const StyleManager = {
commonStyles: `
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important;
}
`,
platformStyles: '',
featureStyles: '',
/**
* 添加平台特定的 CSS
* @param {string} css - 平台 CSS
*/
addPlatformStyles: function(css) {
this.platformStyles += css;
},
/**
* 添加功能特定的 CSS (例如 Gemini 按钮)
* @param {string} css - 功能 CSS
*/
addFeatureStyles: function(css) {
this.featureStyles += css;
},
/**
* 将所有收集到的样式应用到文档
*/
applyAllStyles: function() {
Utils.addStylesToHead(this.commonStyles + this.platformStyles + this.featureStyles);
}
};
// --- 平台模块 ---
// Gemini 模块
const GeminiModule = {
isCurrent: window.location.hostname.includes(PLATFORM_HOSTS.GEMINI),
copyButtonProcessedAttr: GEMINI_ATTRIBUTES.COPY_BUTTON_PROCESSED,
headerCollapseProcessedAttr: GEMINI_ATTRIBUTES.HEADER_COLLAPSE_PROCESSED,
footerCollapseProcessedAttr: GEMINI_ATTRIBUTES.FOOTER_COLLAPSE_PROCESSED,
/**
* 获取 Gemini 平台的特定样式
* @returns {string} CSS 字符串
*/
getStyles: function() {
return `
/* Gemini 宽屏 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; }
/* Gemini 代码块功能 CSS */
.${GEMINI_CLASSES.FOOTER_CONTAINER} {
display: flex;
justify-content: center;
align-items: center;
padding: 8px 0px;
margin-top: 8px;
gap: 8px; /* 页脚按钮之间的间距 */
}
.${GEMINI_CLASSES.CUSTOM_COPY_BUTTON} {
background-color: transparent;
color: #5f6368; /* Google 图标颜色 */
border: none;
padding: 0;
width: 40px; /* Material Design 图标按钮标准尺寸 */
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_CLASSES.CUSTOM_COPY_BUTTON} .mat-icon {
font-size: 24px; /* Material Design 图标标准尺寸 */
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
flex-wrap:wrap;
}
.${GEMINI_CLASSES.CUSTOM_COPY_BUTTON}:hover {
background-color: rgba(0, 0, 0, 0.08); /* Material Design 悬停效果 */
color: #202124; /* 深色图标悬停颜色 */
}
.${GEMINI_CLASSES.CUSTOM_COPY_BUTTON}:active {
background-color: rgba(0, 0, 0, 0.12); /* Material Design 点击效果 */
transform: scale(0.95);
}
.${GEMINI_CLASSES.HEADER_COLLAPSE_BUTTON} {
margin-right: 4px; /* 头部折叠按钮与原始按钮的间距 */
}
.${GEMINI_CLASSES.COLLAPSED_PANEL} {
display: none !important; /* 隐藏折叠的内容 */
}
`;
},
/**
* 更新单个折叠按钮的图标和提示文本
* @param {HTMLElement} buttonElement - 按钮元素
* @param {boolean} isPanelCollapsed - 面板是否已折叠
*/
updateSingleCollapseButtonIcon: function(buttonElement, isPanelCollapsed) {
if (!buttonElement) return;
while (buttonElement.firstChild) buttonElement.removeChild(buttonElement.firstChild); // 清空现有图标
const iconName = isPanelCollapsed ? 'keyboard_arrow_down' : 'keyboard_arrow_up';
const label = isPanelCollapsed ? '展开代码块 (Userscript)' : '收起代码块 (Userscript)';
buttonElement.appendChild(Utils.createMaterialIconElement(iconName));
buttonElement.setAttribute('aria-label', label);
buttonElement.setAttribute('mattooltip', label); // Gemini 使用 mattooltip
buttonElement.setAttribute('title', label); // 备用 title
},
/**
* 同步代码块头部和底部相关折叠按钮的图标状态
* @param {HTMLElement} codeBlockElement - 代码块元素
* @param {boolean} panelIsCollapsed - 面板是否已折叠
*/
syncRelatedCollapseButtons: function(codeBlockElement, panelIsCollapsed) {
const headerBtn = codeBlockElement.querySelector(`.${GEMINI_CLASSES.HEADER_COLLAPSE_BUTTON}`);
const footerBtn = codeBlockElement.querySelector(`.${GEMINI_CLASSES.FOOTER_COLLAPSE_BUTTON}`);
this.updateSingleCollapseButtonIcon(headerBtn, panelIsCollapsed);
this.updateSingleCollapseButtonIcon(footerBtn, panelIsCollapsed);
},
/**
* 为 Gemini 代码块添加自定义复制按钮
* @param {HTMLElement} codeBlockElement - 代码块元素
* @returns {{copyButton: HTMLElement|null, footerDiv: HTMLElement|null}} 包含复制按钮和页脚容器的对象
*/
addCustomCopyButton: function(codeBlockElement) {
if (codeBlockElement.getAttribute(this.copyButtonProcessedAttr) === 'true') {
const existingFooter = codeBlockElement.querySelector('.' + GEMINI_CLASSES.FOOTER_CONTAINER);
const existingButton = existingFooter ? existingFooter.querySelector('.' + GEMINI_CLASSES.CUSTOM_COPY_BUTTON) : null;
return { copyButton: existingButton, footerDiv: existingFooter };
}
const codeContentElement = codeBlockElement.querySelector(GEMINI_SELECTORS.CODE_CONTENT);
if (!codeContentElement) return { copyButton: null, footerDiv: null };
const copyButton = document.createElement('button');
copyButton.className = GEMINI_CLASSES.CUSTOM_COPY_BUTTON;
copyButton.setAttribute('aria-label', '复制代码 (Userscript)');
copyButton.setAttribute('title', '复制代码 (Userscript)');
copyButton.appendChild(Utils.createMaterialIconElement('content_copy'));
copyButton.addEventListener('click', async (event) => {
event.stopPropagation(); // 防止事件冒泡
const codeText = codeContentElement.innerText;
try {
await navigator.clipboard.writeText(codeText);
// 更改图标为 'check'
while (copyButton.firstChild) copyButton.removeChild(copyButton.firstChild);
copyButton.appendChild(Utils.createMaterialIconElement('check'));
} catch (err) {
console.error('Userscript: 无法复制代码', err);
// 可以考虑使用一个小的提示框代替 alert
alert('无法复制代码 (Userscript)');
// 出错时恢复图标
while (copyButton.firstChild) copyButton.removeChild(copyButton.firstChild);
copyButton.appendChild(Utils.createMaterialIconElement('content_copy'));
return; // 提前返回,不执行后续的图标恢复
}
// 2.5秒后恢复图标
setTimeout(() => {
if (copyButton.isConnected) { // 检查按钮是否仍在 DOM 中
while (copyButton.firstChild) copyButton.removeChild(copyButton.firstChild);
copyButton.appendChild(Utils.createMaterialIconElement('content_copy'));
}
}, 2500);
});
let footerDiv = codeBlockElement.querySelector('.' + GEMINI_CLASSES.FOOTER_CONTAINER);
if (!footerDiv) {
footerDiv = document.createElement('div');
footerDiv.className = GEMINI_CLASSES.FOOTER_CONTAINER;
// 将页脚添加到代码块的末尾,但在可折叠面板之后(如果存在)或直接添加
const panel = codeBlockElement.querySelector(GEMINI_SELECTORS.COLLAPSIBLE_PANEL);
if (panel && panel.nextSibling) {
panel.parentNode.insertBefore(footerDiv, panel.nextSibling);
} else if (panel) {
panel.parentNode.appendChild(footerDiv);
}
else {
codeBlockElement.appendChild(footerDiv);
}
}
footerDiv.appendChild(copyButton); // 复制按钮始终在页脚的最后
codeBlockElement.setAttribute(this.copyButtonProcessedAttr, 'true');
return { copyButton: copyButton, footerDiv: footerDiv };
},
/**
* 为 Gemini 代码块头部添加折叠/展开按钮
* @param {HTMLElement} codeBlockElement - 代码块元素
* @param {HTMLElement} panelToCollapse - 需要折叠/展开的面板元素
*/
addHeaderCollapseButton: function(codeBlockElement, panelToCollapse) {
if (codeBlockElement.getAttribute(this.headerCollapseProcessedAttr) === 'true' || !panelToCollapse) return;
const headerDiv = codeBlockElement.querySelector(GEMINI_SELECTORS.CODE_HEADER);
if (!headerDiv) return;
const collapseButton = document.createElement('button');
// 沿用 Gemini 原生按钮的样式类,并添加自定义类以供识别
collapseButton.className = `mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger ${GEMINI_CLASSES.HEADER_COLLAPSE_BUTTON}`;
collapseButton.setAttribute('mat-icon-button', ''); // Gemini 按钮需要的属性
collapseButton.addEventListener('click', (event) => {
event.stopPropagation();
const isCurrentlyCollapsed = panelToCollapse.classList.toggle(GEMINI_CLASSES.COLLAPSED_PANEL);
this.syncRelatedCollapseButtons(codeBlockElement, isCurrentlyCollapsed);
});
const existingButtonsDiv = headerDiv.querySelector(GEMINI_SELECTORS.ORIGINAL_BUTTONS_CONTAINER);
if (existingButtonsDiv && existingButtonsDiv.parentNode === headerDiv) {
// 插入到原生按钮组之前
headerDiv.insertBefore(collapseButton, existingButtonsDiv);
} else {
// 如果原生按钮组不存在,则添加到头部最前面
headerDiv.prepend(collapseButton);
}
codeBlockElement.setAttribute(this.headerCollapseProcessedAttr, 'true');
// 初始图标状态将在 initializeCodeBlockFeatures 中通过 syncRelatedCollapseButtons 设置
},
/**
* 为 Gemini 代码块页脚添加折叠/展开按钮
* @param {HTMLElement} codeBlockElement - 代码块元素
* @param {HTMLElement} panelToCollapse - 需要折叠/展开的面板元素
* @param {HTMLElement} footerDiv - 页脚容器元素
* @param {HTMLElement} copyButtonRef - 复制按钮的引用,用于定位
*/
addFooterCollapseButton: function(codeBlockElement, panelToCollapse, footerDiv, copyButtonRef) {
if (codeBlockElement.getAttribute(this.footerCollapseProcessedAttr) === 'true' || !panelToCollapse || !footerDiv) return;
const collapseButton = document.createElement('button');
// 页脚折叠按钮使用与复制按钮相同的基类以保持视觉一致性,并添加自己的标识类
collapseButton.className = `${GEMINI_CLASSES.CUSTOM_COPY_BUTTON} ${GEMINI_CLASSES.FOOTER_COLLAPSE_BUTTON}`;
collapseButton.addEventListener('click', (event) => {
event.stopPropagation();
const isCurrentlyCollapsed = panelToCollapse.classList.toggle(GEMINI_CLASSES.COLLAPSED_PANEL);
this.syncRelatedCollapseButtons(codeBlockElement, isCurrentlyCollapsed);
});
// 将页脚折叠按钮插入到复制按钮之前
if (copyButtonRef && copyButtonRef.parentNode === footerDiv) {
footerDiv.insertBefore(collapseButton, copyButtonRef);
} else {
// 如果复制按钮不存在于页脚中(理论上不应发生),则添加到页脚末尾
footerDiv.appendChild(collapseButton);
}
codeBlockElement.setAttribute(this.footerCollapseProcessedAttr, 'true');
},
/**
* 初始化单个 Gemini 代码块的功能 (复制、折叠)
* @param {HTMLElement} codeBlockElement - 要初始化的代码块元素
*/
initializeCodeBlockFeatures: function(codeBlockElement) {
const panelToCollapse = codeBlockElement.querySelector(GEMINI_SELECTORS.COLLAPSIBLE_PANEL);
// 始终先添加复制按钮,因为它会创建页脚容器(如果不存在)
const { copyButton, footerDiv } = this.addCustomCopyButton(codeBlockElement);
if (panelToCollapse) {
this.addHeaderCollapseButton(codeBlockElement, panelToCollapse);
// 确保页脚和复制按钮已成功创建后再添加页脚折叠按钮
if (footerDiv && copyButton) {
this.addFooterCollapseButton(codeBlockElement, panelToCollapse, footerDiv, copyButton);
}
// 在所有按钮都可能已添加后,设置所有折叠按钮的初始图标状态
this.syncRelatedCollapseButtons(codeBlockElement, panelToCollapse.classList.contains(GEMINI_CLASSES.COLLAPSED_PANEL));
}
},
/**
* 观察并初始化页面上所有 Gemini 代码块的功能
*/
observeAndInitializeCodeBlocks: function() {
// 初始化已存在的代码块
document.querySelectorAll(GEMINI_SELECTORS.CODE_BLOCK).forEach(block => this.initializeCodeBlockFeatures(block));
// 观察后续动态添加的代码块
Utils.observeDOMChanges(document.body, { childList: true, subtree: true }, (mutationsList) => {
mutationsList.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
// 检查节点本身是否是代码块
if (node.matches && node.matches(GEMINI_SELECTORS.CODE_BLOCK)) {
this.initializeCodeBlockFeatures(node);
}
// 检查节点的子元素中是否包含代码块
node.querySelectorAll(GEMINI_SELECTORS.CODE_BLOCK).forEach(block => this.initializeCodeBlockFeatures(block));
}
});
}
});
});
},
/**
* 应用宽屏模式到 Gemini 的内联样式元素
*/
applyWidescreenToInlineStyles: function() {
const elements = document.querySelectorAll('[style*="max-width"]');
elements.forEach(el => {
// 排除侧边栏和导航面板,以及我们自定义的页脚中的元素
if (el.closest(GEMINI_SELECTORS.SIDE_PANEL) ||
el.closest(GEMINI_SELECTORS.NAVIGATION_PANEL) ||
el.closest(`.${GEMINI_CLASSES.FOOTER_CONTAINER}`)) {
return;
}
// 仅修改那些看起来是主要内容区域限制的 max-width
// 这个判断可能需要根据实际情况调整,避免过度修改
const currentMaxWidth = el.style.maxWidth;
if (currentMaxWidth && (currentMaxWidth.includes('px') || currentMaxWidth.includes('rem') || currentMaxWidth.includes('em') || currentMaxWidth.includes('%'))) {
if (parseInt(currentMaxWidth, 10) < 1200 && !currentMaxWidth.includes('95%')) { // 避免重复设置
el.style.maxWidth = '95%';
}
}
});
},
/**
* 观察并应用宽屏模式到 Gemini 的内联样式元素
*/
observeAndApplyWidescreenInlineStyles: function() {
this.applyWidescreenToInlineStyles(); // 初始应用
Utils.observeDOMChanges(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class'] // 观察 style 和 class 变化
}, () => this.applyWidescreenToInlineStyles());
},
/**
* 初始化 Gemini 平台的所有特定功能
*/
init: function() {
StyleManager.addFeatureStyles(this.getStyles()); // 添加 Gemini 特定样式
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.observeAndInitializeCodeBlocks();
this.observeAndApplyWidescreenInlineStyles();
});
} else {
this.observeAndInitializeCodeBlocks();
this.observeAndApplyWidescreenInlineStyles();
}
}
};
// Claude 模块
const ClaudeModule = {
isCurrent: window.location.hostname.includes(PLATFORM_HOSTS.CLAUDE),
jsExecuted: false, // 防止重复执行 JS
/**
* 获取 Claude 平台的特定样式
* @returns {string} CSS 字符串
*/
getStyles: function() {
return `
/* Claude 宽屏 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 特定字体修复 (如果通用字体不能完全覆盖) */
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.6 !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;
}
`;
},
/**
* 向 Claude 用户菜单添加自定义按钮 (例如 "Recents")
*/
addCustomButtonToMenu: function() {
const settingsButton = document.querySelector(CLAUDE_SELECTORS.SETTINGS_BUTTON);
if (!settingsButton) return; // 设置按钮不存在
const menu = settingsButton.closest(CLAUDE_SELECTORS.MENU_CONTAINER);
if (!menu || document.getElementById(CLAUDE_IDS.CUSTOM_MENU_BUTTON)) return; // 菜单不存在或按钮已添加
const newButton = document.createElement('button');
newButton.id = CLAUDE_IDS.CUSTOM_MENU_BUTTON;
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);
}
},
/**
* 观察 Claude 菜单的出现并添加自定义按钮
*/
observeMenuForButtonAddition: function() {
// 初始尝试添加,以防菜单已存在
this.addCustomButtonToMenu();
// 观察 body 的子节点变化,因为菜单可能是动态添加到 body 直属的
Utils.observeDOMChanges(document.body, { childList: true, subtree: true }, () => {
this.addCustomButtonToMenu();
});
},
/**
* 修正 Claude 输入区域的宽度,确保其适应宽屏布局
*/
fixInputWidths: function() {
document.querySelectorAll(CLAUDE_SELECTORS.TEXT_INPUT_ELEMENTS).forEach(el => {
if (el && !el.getAttribute(CLAUDE_ATTRIBUTES.WIDTH_FIXED)) {
el.style.maxWidth = '100%'; // 确保输入框最大宽度为100%
el.setAttribute(CLAUDE_ATTRIBUTES.WIDTH_FIXED, 'true');
}
});
},
/**
* 观察 Claude 输入区域的变化以修正宽度 (使用节流)
*/
observeAndFixInputWidths: function() {
let timeoutId;
const debouncedFixWidths = () => {
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
this.fixInputWidths();
}, 300); // 300ms 防抖
};
// 初始修正
this.fixInputWidths();
const formContainer = document.querySelector(CLAUDE_SELECTORS.FORM_CONTAINER) || document.body;
Utils.observeDOMChanges(formContainer, {
childList: true,
subtree: true,
attributes: false // 通常不需要观察属性变化来修正宽度
}, debouncedFixWidths);
},
/**
* 初始化 Claude 平台的所有特定功能
*/
init: function() {
if (this.jsExecuted) return; // 防止重复初始化
StyleManager.addPlatformStyles(this.getStyles()); // 添加 Claude 特定样式
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.observeMenuForButtonAddition();
this.observeAndFixInputWidths();
});
} else {
this.observeMenuForButtonAddition();
this.observeAndFixInputWidths();
}
this.jsExecuted = true;
}
};
// ChatGPT 模块
const ChatGPTModule = {
isCurrent: window.location.hostname.includes(PLATFORM_HOSTS.CHATGPT),
/**
* 获取 ChatGPT 平台的特定样式
* @returns {string} CSS 字符串
*/
getStyles: function() {
return `
/* ChatGPT 宽屏 CSS */
/* 主内容区域,移除最大宽度限制 */
.text-base.gap-4.md\\:gap-6.md\\:max-w-2xl.lg\\:max-w-xl.xl\\:max-w-3xl.p-4.md\\:py-6.flex.lg\\:px-0.m-auto,
.md\\:max-w-2xl.lg\\:max-w-xl.xl\\:max-w-3xl, /* 针对不同层级的容器 */
main .text-token-text-primary .w-full .max-w-agw { /* 2024-05-09 新选择器 */
max-width: 95% !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* 输入区域 */
form .w-full { /* 确保表单内的输入区域也扩展 */
max-width: 95% !important; /* 或者根据实际情况调整为更具体的选择器 */
margin-left: auto !important;
margin-right: auto !important;
}
/* 针对特定布局的微调,可能需要根据 ChatGPT UI 更新而调整 */
.stretch {
width: 100% !important; /* 确保某些弹性元素完全填充 */
}
/* 确保聊天记录容器也扩展 */
.h-full { /* 如果有高度限制的容器,可能也需要调整 */
/* height: 100% !important; */ /* 这个要小心,可能导致布局问题 */
}
/* 针对消息输入框的父容器 */
.md\\:flex.md\\:items-end.md\\:gap-4 .w-full {
max-width: 100% !important; /* 覆盖内部的 max-width */
}
/* 针对包含输入框和按钮的 flex 容器 */
.relative.flex.h-full.max-w-full.flex-1.flex-col {
max-width: 95% !important;
margin: 0 auto !important;
}
`;
},
/**
* 初始化 ChatGPT 平台的所有特定功能
*/
init: function() {
StyleManager.addPlatformStyles(this.getStyles()); // 添加 ChatGPT 特定样式
// ChatGPT 目前主要通过 CSS 实现宽屏,如果未来需要 JS 功能可在此添加
}
};
// --- 主逻辑:初始化和执行 ---
function main() {
// 确定当前平台并执行相应的初始化
if (GeminiModule.isCurrent) {
GeminiModule.init();
} else if (ClaudeModule.isCurrent) {
ClaudeModule.init();
} else if (ChatGPTModule.isCurrent) {
ChatGPTModule.init();
}
// 应用所有收集到的样式
StyleManager.applyAllStyles();
}
// 执行主函数
main();
})();