// ==UserScript==
// @name Google AI Studio Optimization
// @name:zh-CN Google AI Studio 页面优化
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @description Eye-Friendly Styles, Element Control, Auto Collapse Panels, Enhanced Input Width.
// @description:zh-CN 提供护眼样式、元素显隐控制、自动折叠左右侧面板功能、优化输入框体验及宽度,优化 Google AI Studio 使用体验。
// @author Gemini
// @match https://aistudio.google.com/prompts/*
// @match https://aistudio.google.com/*/prompts/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_info
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const SCRIPT_PREFIX = '[AI Studio 增强+]';
console.log(`${SCRIPT_PREFIX} 初始化 v${GM_info.script.version}...`);
// --- 默认设置 ---
const defaultSettings = {
useCustomStyles: true,
showUserPrompts: true,
showThinkingProcess: true,
showAIMessages: true,
showInputBox: true,
showTopNavBar: true,
showChatTitleBar: true,
showTokenCount: true, // 注意: 此设置目前在脚本中未实际控制任何元素的显隐
autoCollapseRightPanel: false,
autoCollapseLeftPanel: false
};
// --- 初始化设置 ---
let settings = {};
for (const key in defaultSettings) {
settings[key] = GM_getValue(key, defaultSettings[key]);
if (GM_getValue(key) === undefined) {
GM_setValue(key, defaultSettings[key]);
console.log(`${SCRIPT_PREFIX} 初始化新设置: ${key} = ${defaultSettings[key]}`);
}
}
console.log(`${SCRIPT_PREFIX} 当前设置:`, settings);
// --- 菜单文本定义 ---
const MENU_TEXT_ENABLED = "🟢 启用";
const MENU_TEXT_DISABLED = "🔴 禁用";
const MENU_TEXT_SHOW_ALL = "🟢 显示所有";
const MENU_TEXT_HIDE_ALL = "🔴 隐藏所有";
// --- 菜单定义 ---
var menu_ALL = [
[
"useCustomStyles",
"自定义样式",
],
[
"autoCollapseLeftPanel",
"自动折叠左侧面板",
],
[
"autoCollapseRightPanel",
"自动折叠右侧面板",
],
[
"showTopNavBar",
"顶部导航栏",
],
[
"showChatTitleBar",
"聊天标题栏",
],
[
"showUserPrompts",
"用户消息",
],
[
"showThinkingProcess",
"AI 思考过程",
],
[
"showAIMessages",
"AI 消息",
],
[
"showInputBox",
"底部输入框",
],
[
"toggleAllDisplays", // 特殊键名,用于切换所有可见性设置
"切换所有显示项", // 这个基础文本在 toggleAllDisplays 中会进一步动态调整
],
];
var menu_ID = []; // 用于存储菜单命令ID,以便注销
// --- CSS 样式 ---
const customStyles = `
:root {
/* 主题颜色变量 */
--enhancer-bg-main: #f8f9fa;
--enhancer-text-main: #212529;
--enhancer-bg-user-prompt: #e9ecef;
--enhancer-text-user-prompt: #343a40;
--enhancer-bg-model-prompt: #ffffff;
--enhancer-text-model-prompt: #212529;
--enhancer-border-color: #dee2e6;
--enhancer-button-bg: #f1f3f5;
--enhancer-button-hover-bg: #e9ecef;
--enhancer-header-bg: #ffffff;
--enhancer-input-bg: #ffffff; /* 用于输入框区域的背景,可与主背景区分 */
--enhancer-link-color: #007bff;
--enhancer-code-bg: #e9ecef;
--enhancer-code-text: #212529;
--enhancer-thinking-bg: #f8f9fa;
/* 字体大小 - 显著缩小 */
--enhancer-font-size-base: 13px; /* 基础字号调小 */
--enhancer-font-size-xs: 0.78rem; /* 约 10px, 用于页脚等辅助文字 */
--enhancer-font-size-sm: 0.88rem; /* 约 11.5px, 用于次要文字 */
--enhancer-font-size-md: 0.94rem; /* 约 12.2px, 用于正文和主要内容 */
--enhancer-font-size-lg: 1.0rem; /* 约 13px, 用于小标题等 */
--enhancer-line-height-base: 1.45; /* 减小行高 */
}
@media (prefers-color-scheme: dark) {
:root {
--enhancer-bg-main: #212529;
--enhancer-text-main: #e9ecef;
--enhancer-bg-user-prompt: #343a40;
--enhancer-text-user-prompt: #f8f9fa;
--enhancer-bg-model-prompt: #2c3034;
--enhancer-text-model-prompt: #f8f9fa;
--enhancer-border-color: #495057;
--enhancer-button-bg: #495057;
--enhancer-button-hover-bg: #5a6268;
--enhancer-header-bg: #2c3034;
--enhancer-input-bg: #212529; /* 暗黑模式下输入框区域背景,可与主背景一致或稍有区别 */
--enhancer-link-color: #6cb2eb;
--enhancer-code-bg: #343a40;
--enhancer-code-text: #f8f9fa;
--enhancer-thinking-bg: #2c3034;
}
}
/* 页面整体字体和背景 */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", "思源黑体", "思源黑体 CN", "Microsoft YaHei", "微软雅黑", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
font-size: var(--enhancer-font-size-md) !important;
line-height: var(--enhancer-line-height-base) !important;
color: var(--enhancer-text-main) !important;
background-color: var(--enhancer-bg-main) !important;
}
.chunk-editor-main {
background: var(--enhancer-bg-main) !important;
padding: 8px !important;
}
.chunk-editor-main p {
font-family: inherit !important;
margin-bottom: 0.5em !important;
}
/* 用户消息气泡 */
.user-prompt-container .text-chunk {
background: var(--enhancer-bg-user-prompt) !important;
color: var(--enhancer-text-user-prompt) !important;
padding: 7px 10px !important;
border-radius: 6px !important;
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
margin-bottom: 6px !important;
}
.user-prompt-container p {
font-size: var(--enhancer-font-size-md) !important;
line-height: var(--enhancer-line-height-base) !important;
color: var(--enhancer-text-user-prompt) !important;
}
/* AI 模型消息气泡 */
.model-prompt-container {
background: var(--enhancer-bg-model-prompt) !important;
color: var(--enhancer-text-model-prompt) !important;
padding: 8px 12px !important;
border-radius: 8px !important;
border: 1px solid var(--enhancer-border-color) !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
margin-bottom: 10px !important;
}
.model-prompt-container:has(.mat-accordion) {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 5px !important;
}
.model-prompt-container p {
font-size: var(--enhancer-font-size-md) !important;
line-height: var(--enhancer-line-height-base) !important;
color: var(--enhancer-text-model-prompt) !important;
}
.model-prompt-container pre, .model-prompt-container code {
background-color: var(--enhancer-code-bg) !important;
color: var(--enhancer-code-text) !important;
padding: 0.15em 0.3em;
font-size: 0.88em;
border-radius: 3px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace !important;
}
.model-prompt-container pre {
padding: 0.8em !important;
margin: 0.3em 0 !important;
overflow-x: auto !important;
}
/* AI 思考过程 / Accordion 折叠内容 */
ms-thought-chunk, .mat-accordion {
background-color: var(--enhancer-thinking-bg) !important;
border-radius: 5px !important;
padding: 6px 8px !important;
margin-bottom: 6px !important;
}
.mat-accordion p {
font-size: var(--enhancer-font-size-sm) !important;
color: var(--enhancer-text-main) !important;
opacity: 0.85;
}
ms-thought-chunk{
max-width:1300px !important;
}
/* 每轮对话的页脚 (Token 数量等) */
.turn-footer {
font-size: var(--enhancer-font-size-xs) !important;
background: none !important;
opacity: 0.65;
color: var(--enhancer-text-main) !important;
padding-top: 5px !important;
margin-bottom: 5px !important;
}
/* 顶部导航和头部区域 */
.page-header, .header-container {
height: auto !important;
padding: 5px 10px !important;
background-color: var(--enhancer-header-bg) !important;
border-bottom: 1px solid var(--enhancer-border-color) !important;
}
.top-nav {
font-size: var(--enhancer-font-size-sm) !important;
display: flex;
align-items: center;
}
/* 聊天区域的工具栏 */
.toolbar-container {
height: auto !important;
padding: 5px 10px !important;
background-color: var(--enhancer-header-bg) !important;
border-bottom: 1px solid var(--enhancer-border-color) !important;
}
/* 链接样式 */
a {
color: var(--enhancer-link-color) !important;
text-decoration: none !important;
}
a:hover {
text-decoration: underline !important;
}
/* --------------------------------------------------- */
/* Enhanced Input Area Styles (Footer) */
/* --------------------------------------------------- */
footer:has(.prompt-input-wrapper) {
background-color: var(--enhancer-input-bg) !important;
border-top: 1px solid var(--enhancer-border-color) !important;
padding: 10px 10px !important; /* MODIFIED: Reduced horizontal padding */
position: sticky !important;
bottom: 0 !important;
z-index: 100 !important;
box-shadow: 0 -2px 5px rgba(0,0,0,0.05);
}
@media (prefers-color-scheme: dark) {
footer:has(.prompt-input-wrapper) {
box-shadow: 0 -2px 8px rgba(0,0,0,0.15) !important;
}
}
.prompt-input-wrapper {
display: flex !important;
align-items: center !important;
gap: 8px !important; /* MODIFIED: Slightly reduced gap */
}
.prompt-input-wrapper-container {
flex-grow: 1 !important;
display: flex !important;
position: relative !important;
}
.prompt-input-wrapper-container textarea {
flex-grow: 1 !important;
background-color: var(--enhancer-bg-main) !important; /* Use main background for textarea */
color: var(--enhancer-text-main) !important;
border: 1px solid var(--enhancer-border-color) !important;
border-radius: 22px !important;
padding: 10px 18px !important;
font-size: var(--enhancer-font-size-md) !important;
line-height: var(--enhancer-line-height-base) !important;
min-height: 44px !important;
max-height: 200px !important;
resize: none !important;
box-shadow: 0 1px 2px rgba(0,0,0,0.03) inset !important;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.prompt-input-wrapper-container textarea::placeholder {
color: color-mix(in srgb, var(--enhancer-text-main) 60%, transparent) !important;
opacity: 1 !important;
}
.prompt-input-wrapper-container textarea:focus {
outline: none !important;
border-color: var(--enhancer-link-color) !important;
background-color: var(--enhancer-bg-main) !important;
box-shadow: 0 0 0 3px color-mix(in srgb, var(--enhancer-link-color) 20%, transparent),
0 1px 2px rgba(0,0,0,0.03) inset !important;
}
/* Standard Icon Buttons (Mic, Attach, etc.) */
footer .prompt-input-wrapper button.mat-icon-button,
footer .prompt-input-wrapper button[mat-icon-button] {
background-color: transparent !important;
color: var(--enhancer-text-main) !important;
border: none !important;
border-radius: 50% !important;
width: 40px !important;
height: 40px !important;
padding: 0 !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
flex-shrink: 0;
transition: background-color 0.2s ease, color 0.2s ease;
}
footer .prompt-input-wrapper button.mat-icon-button:hover,
footer .prompt-input-wrapper button[mat-icon-button]:hover {
background-color: var(--enhancer-button-hover-bg) !important;
}
footer .prompt-input-wrapper button.mat-icon-button mat-icon,
footer .prompt-input-wrapper button[mat-icon-button] mat-icon,
footer .prompt-input-wrapper button.mat-icon-button svg,
footer .prompt-input-wrapper button[mat-icon-button] svg {
font-size: 22px !important;
width: 22px !important;
height: 22px !important;
fill: currentColor !important;
}
/* Prominent Action Button (FAB-like: Send, Stop) */
footer .prompt-input-wrapper button.mat-fab,
footer .prompt-input-wrapper button[mat-fab],
footer .prompt-input-wrapper button.mat-flat-button.send-button-class, /* Add .send-button-class to your Send button if it's flat */
footer .prompt-input-wrapper button[mat-flat-button].send-button-class {
background-color: var(--enhancer-link-color) !important;
color: white !important;
border: none !important;
border-radius: 50% !important;
width: 44px !important;
height: 44px !important;
padding: 0 !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
box-shadow: 0 2px 5px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08) !important;
flex-shrink: 0;
transition: background-color 0.2s ease, box-shadow 0.2s ease;
}
footer .prompt-input-wrapper button.mat-fab:hover,
footer .prompt-input-wrapper button[mat-fab]:hover,
footer .prompt-input-wrapper button.mat-flat-button.send-button-class:hover,
footer .prompt-input-wrapper button[mat-flat-button].send-button-class:hover {
background-color: color-mix(in srgb, var(--enhancer-link-color) 90%, black) !important;
box-shadow: 0 4px 8px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.1) !important;
}
footer .prompt-input-wrapper button.mat-fab mat-icon,
footer .prompt-input-wrapper button[mat-fab] mat-icon,
footer .prompt-input-wrapper button.mat-fab svg,
footer .prompt-input-wrapper button[mat-fab] svg,
footer .prompt-input-wrapper button.mat-flat-button.send-button-class mat-icon,
footer .prompt-input-wrapper button[mat-flat-button].send-button-class svg {
fill: white !important;
color: white !important;
font-size: 24px !important;
width: 24px !important;
height: 24px !important;
}
/* Rectangular Flat Button (if used for e.g. a text "Send" or "Run" button and not covered by .send-button-class) */
footer .prompt-input-wrapper button.mat-flat-button:not([class*='mat-fab']):not([class*='mat-icon-button']):not(.send-button-class),
footer .prompt-input-wrapper button[mat-flat-button]:not([class*='mat-fab']):not([class*='mat-icon-button']):not(.send-button-class) {
background-color: var(--enhancer-button-bg) !important;
color: var(--enhancer-text-main) !important;
border-radius: 20px !important;
padding: 0 12px !important; /* MODIFIED: Reduced horizontal padding for text buttons */
height: 40px !important;
font-size: var(--enhancer-font-size-sm) !important;
font-weight: 500 !important;
border: 1px solid var(--enhancer-border-color) !important;
box-shadow: none !important;
transition: background-color 0.2s ease, border-color 0.2s ease;
white-space: nowrap; /* Prevent text like "Ctrl+Enter" from wrapping if it's part of button text */
}
footer .prompt-input-wrapper button.mat-flat-button:not([class*='mat-fab']):not([class*='mat-icon-button']):not(.send-button-class):hover {
background-color: var(--enhancer-button-hover-bg) !important;
}
footer .prompt-input-wrapper button.mat-flat-button:not([class*='mat-fab']):not([class*='mat-icon-button']):not(.send-button-class) mat-icon,
footer .prompt-input-wrapper button[mat-flat-button]:not([class*='mat-fab']):not([class*='mat-icon-button']):not(.send-button-class) svg {
margin-right: 6px !important; /* MODIFIED: Reduced margin for icon in text button */
font-size: 18px !important;
width: 18px !important;
height: 18px !important;
}
/* User Avatar (adjust selector if needed: e.g., img.user-avatar or button.avatar-button img) */
footer .prompt-input-wrapper img[src*="googleusercontent.com"],
footer .prompt-input-wrapper .user-avatar-selector { /* Placeholder for more specific avatar selector */
width: 36px !important;
height: 36px !important;
border-radius: 50% !important;
object-fit: cover !important;
margin-left: 4px !important; /* MODIFIED: Reduced margin */
flex-shrink: 0;
border: 1px solid var(--enhancer-border-color);
}
/* .prompt-chip-button style (for chips like "Add file", usually above or separate from main input actions) */
.prompt-chip-button {
background: var(--enhancer-button-bg) !important;
color: var(--enhancer-text-main) !important;
border: 1px solid var(--enhancer-border-color) !important;
border-radius: 16px !important; /* Pill-shaped for chips */
padding: 5px 12px !important;
font-size: var(--enhancer-font-size-sm) !important;
transition: background-color 0.15s ease, border-color 0.15s ease;
margin: 4px !important;
height: auto !important;
width: auto !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
text-decoration: none !important;
}
.prompt-chip-button:hover {
background: var(--enhancer-button-hover-bg) !important;
text-decoration: none !important;
}
`;
const hideUserPromptsStyle = `.chat-turn-container.user { display: none !important; }`;
const hideThinkingProcessStyle = `.chat-turn-container .thought-container, ms-thought-chunk {display: none !important;}`;
const hideAIMessagesStyle = `.chat-turn-container.model { display: none !important; }`;
const hideInputBoxStyle = `footer:has(.prompt-input-wrapper) { display: none !important; }`;
const hideTopNavBarStyle = `.header-container { display: none !important; }`;
const hideChatTitleBarStyle = `.page-header > .content > .title-toolbar, .toolbar-container { display: none !important; }`;
// --- 样式应用函数 ---
function updateStyles() {
const existingStyles = document.querySelectorAll('style[data-enhancer-style]');
existingStyles.forEach(style => style.remove());
if (settings.useCustomStyles) {
const styleElement = document.createElement('style');
styleElement.setAttribute('data-enhancer-style', 'base');
styleElement.textContent = customStyles;
document.head.appendChild(styleElement);
}
if (!settings.showUserPrompts) {
const hideUserStyle = document.createElement('style');
hideUserStyle.setAttribute('data-enhancer-style', 'user-visibility');
hideUserStyle.textContent = hideUserPromptsStyle;
document.head.appendChild(hideUserStyle);
}
if (!settings.showThinkingProcess) {
const hideThinkingStyle = document.createElement('style');
hideThinkingStyle.setAttribute('data-enhancer-style', 'thinking-visibility');
hideThinkingStyle.textContent = hideThinkingProcessStyle;
document.head.appendChild(hideThinkingStyle);
}
if (!settings.showAIMessages) {
const hideAIStyle = document.createElement('style');
hideAIStyle.setAttribute('data-enhancer-style', 'ai-message-visibility');
hideAIStyle.textContent = hideAIMessagesStyle;
document.head.appendChild(hideAIStyle);
}
if (!settings.showInputBox) {
const hideInputBox = document.createElement('style');
hideInputBox.setAttribute('data-enhancer-style', 'input-box-visibility');
hideInputBox.textContent = hideInputBoxStyle;
document.head.appendChild(hideInputBox);
}
if (!settings.showTopNavBar) {
const hideTopNav = document.createElement('style');
hideTopNav.setAttribute('data-enhancer-style', 'top-nav-visibility');
hideTopNav.textContent = hideTopNavBarStyle;
document.head.appendChild(hideTopNav);
}
if (!settings.showChatTitleBar) {
const hideChatTitle = document.createElement('style');
hideChatTitle.setAttribute('data-enhancer-style', 'chat-title-visibility');
hideChatTitle.textContent = hideChatTitleBarStyle;
document.head.appendChild(hideChatTitle);
}
console.log(`${SCRIPT_PREFIX} 已根据设置更新样式.`);
}
// --- 浮动通知函数 ---
function showNotification(message) {
const notificationId = 'enhancer-notification';
let notification = document.getElementById(notificationId);
if (!notification) {
notification = document.createElement('div');
notification.id = notificationId;
notification.style.cssText = `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(40, 40, 40, 0.9);
color: white;
padding: 12px 22px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
z-index: 10000;
opacity: 0;
transition: opacity 0.4s ease-in-out, bottom 0.4s ease-in-out;
font-size: 14px;
text-align: center;
max-width: 80%;
`;
document.body.appendChild(notification);
}
if (notification.timeoutId) {
clearTimeout(notification.timeoutId);
notification.style.bottom = '0px';
notification.style.opacity = '0';
}
notification.textContent = message;
setTimeout(() => {
notification.style.opacity = '1';
notification.style.bottom = '20px';
}, 50);
notification.timeoutId = setTimeout(() => {
notification.style.opacity = '0';
notification.style.bottom = '0px';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
notification.timeoutId = null;
}, 400);
}, 2500);
}
// --- 菜单命令函数 ---
function registerMenuCommands() {
menu_ID.forEach(id => GM_unregisterMenuCommand(id));
menu_ID = [];
console.log(`${SCRIPT_PREFIX} 注册(不可用)菜单命令...`);
menu_ALL.forEach(item => {
const settingKey = item[0];
const baseMenuText = item[1];
if (settingKey === "toggleAllDisplays") {
const displaySettingsKeys = [
"showUserPrompts", "showThinkingProcess", "showAIMessages",
"showInputBox", "showTopNavBar", "showChatTitleBar"
];
const allEnabled = displaySettingsKeys.every(key => settings[key]);
const menuText = `${allEnabled ? MENU_TEXT_HIDE_ALL : MENU_TEXT_SHOW_ALL} (${baseMenuText})`;
menu_ID.push(GM_registerMenuCommand(menuText, toggleAllDisplays));
} else {
if (settings.hasOwnProperty(settingKey)) {
const currentSettingValue = settings[settingKey];
const menuText = `${currentSettingValue ? MENU_TEXT_DISABLED : MENU_TEXT_ENABLED} ${baseMenuText}`;
menu_ID.push(GM_registerMenuCommand(
menuText,
() => menuSwitch(settingKey)
));
} else {
console.warn(`${SCRIPT_PREFIX} 注册(不可用)菜单时未找到设置键 "${settingKey}".`);
}
}
});
console.log(`${SCRIPT_PREFIX} 菜单命令已注册(不可用).`);
}
function menuSwitch(settingKey) {
let newValue = !settings[settingKey];
settings[settingKey] = newValue;
GM_setValue(settingKey, newValue);
console.log(`${SCRIPT_PREFIX} 已切换 ${settingKey} 至 ${newValue}`);
const menuItem = menu_ALL.find(item => item[0] === settingKey);
if (!menuItem) {
console.error(`${SCRIPT_PREFIX} 未能找到设置键的菜单项: ${settingKey}`);
return;
}
const baseMenuText = menuItem[1];
if (['useCustomStyles', 'showUserPrompts', 'showThinkingProcess', 'showAIMessages', 'showInputBox', 'showTopNavBar', 'showChatTitleBar'].includes(settingKey)) {
updateStyles();
} else if (settingKey === 'autoCollapseRightPanel') {
if (newValue) {
console.log(`${SCRIPT_PREFIX} 自动折叠右侧面板已启用, 尝试初始检查/点击.`);
setTimeout(triggerAutoCollapseRightPanelIfNeeded, 500);
}
} else if (settingKey === 'autoCollapseLeftPanel') {
if (newValue) {
console.log(`${SCRIPT_PREFIX} 自动折叠左侧面板已启用, 尝试折叠.`);
setTimeout(triggerAutoCollapseLeftPanelIfNeeded, 500);
}
}
registerMenuCommands();
showNotification(`${baseMenuText} ${newValue ? '已启用' : '已禁用'}`);
}
function toggleAllDisplays() {
const displaySettingsKeys = [
"showUserPrompts", "showThinkingProcess", "showAIMessages",
"showInputBox", "showTopNavBar", "showChatTitleBar"
];
const enableAll = !displaySettingsKeys.every(key => settings[key]);
console.log(`${SCRIPT_PREFIX} 切换所有显示项至: ${enableAll ? '显示' : '隐藏'}`);
displaySettingsKeys.forEach(key => {
if (settings.hasOwnProperty(key)) {
settings[key] = enableAll;
GM_setValue(key, enableAll);
} else {
console.warn(`${SCRIPT_PREFIX} 切换所有显示项时未找到设置键 "${key}".`);
}
});
updateStyles();
registerMenuCommands();
showNotification(`所有显示项 ${enableAll ? '已启用 (显示)' : '已禁用 (隐藏)'}`);
}
// --- 自动折叠右侧面板逻辑 ---
const RUN_SETTINGS_BUTTON_SELECTOR = '.toggles-container button[aria-label="Run settings"], .toggles-container button[aria-label="运行设置"]';
const RIGHT_PANEL_TAG_NAME = 'MS-RIGHT-SIDE-PANEL';
const NGTNS_REGEX = /ng-tns-c\d+-\d+/;
let lastNgTnsClass = null;
let clickDebounceTimeoutRight = null;
let panelObserver = null;
function clickRunSettingsButton() {
if (clickDebounceTimeoutRight) {
clearTimeout(clickDebounceTimeoutRight);
clickDebounceTimeoutRight = null;
}
if (!settings.autoCollapseRightPanel) return;
const button = document.querySelector(RUN_SETTINGS_BUTTON_SELECTOR);
if (button) {
const style = window.getComputedStyle(button);
const panel = button.closest(RIGHT_PANEL_TAG_NAME);
if (panel && style.display !== 'none' && style.visibility !== 'hidden' && !button.disabled) {
if (getNgTnsClass(panel)) {
console.log(`${SCRIPT_PREFIX} 自动折叠右侧面板: 点击 "运行设置" 按钮.`);
button.click();
}
}
}
}
function getNgTnsClass(element) {
if (!element || !element.classList) return null;
for (const className of element.classList) {
if (NGTNS_REGEX.test(className)) {
return className;
}
}
return null;
}
function triggerAutoCollapseRightPanelIfNeeded() {
if (!settings.autoCollapseRightPanel) return;
const panel = document.querySelector(RIGHT_PANEL_TAG_NAME);
if (panel) {
const currentNgTnsClass = getNgTnsClass(panel);
if (currentNgTnsClass && (!lastNgTnsClass || currentNgTnsClass !== lastNgTnsClass)) {
lastNgTnsClass = currentNgTnsClass;
if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
clickDebounceTimeoutRight = setTimeout(clickRunSettingsButton, 300);
} else if (!currentNgTnsClass && lastNgTnsClass) {
lastNgTnsClass = null;
}
} else {
lastNgTnsClass = null;
}
}
const panelObserverCallback = function(mutationsList, observer) {
if (!settings.autoCollapseRightPanel) return;
let panelPotentiallyChanged = false;
for (const mutation of mutationsList) {
if (mutation.type === 'attributes' &&
mutation.attributeName === 'class' &&
mutation.target.tagName === RIGHT_PANEL_TAG_NAME) {
const targetPanel = mutation.target;
const currentNgTnsClass = getNgTnsClass(targetPanel);
if (currentNgTnsClass && currentNgTnsClass !== lastNgTnsClass) {
lastNgTnsClass = currentNgTnsClass;
panelPotentiallyChanged = true;
break;
} else if (!currentNgTnsClass && lastNgTnsClass) {
lastNgTnsClass = null;
}
} else if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
let potentialPanel = null;
if (node.tagName === RIGHT_PANEL_TAG_NAME) potentialPanel = node;
else if (node.querySelector) potentialPanel = node.querySelector(RIGHT_PANEL_TAG_NAME);
if (potentialPanel) {
const currentNgTnsClass = getNgTnsClass(potentialPanel);
if (currentNgTnsClass && (!lastNgTnsClass || currentNgTnsClass !== lastNgTnsClass) ) {
lastNgTnsClass = currentNgTnsClass;
panelPotentiallyChanged = true;
}
if (panelPotentiallyChanged) break;
}
}
}
if (panelPotentiallyChanged) break;
}
}
if (panelPotentiallyChanged) {
if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
clickDebounceTimeoutRight = setTimeout(clickRunSettingsButton, 300);
}
};
function initializePanelObserver() {
if (panelObserver) panelObserver.disconnect();
const observerConfig = {
attributes: true, attributeFilter: ['class'],
childList: true, subtree: true
};
panelObserver = new MutationObserver(panelObserverCallback);
panelObserver.observe(document.body, observerConfig);
console.log(`${SCRIPT_PREFIX} 右侧面板 MutationObserver 已启动.`);
}
// --- 自动折叠左侧面板逻辑 ---
const LEFT_PANEL_TOGGLE_BUTTON_SELECTOR = '.navbar-content-wrapper button[aria-label="Expand or collapse navigation menu"], .navbar-content-wrapper button[aria-label="展开或收起导航菜单"]';
const LEFT_PANEL_EXPANDED_CLASS = 'sidenav-opened';
let clickDebounceTimeoutLeft = null;
function clickLeftPanelToggleButton() {
if (clickDebounceTimeoutLeft) {
clearTimeout(clickDebounceTimeoutLeft);
clickDebounceTimeoutLeft = null;
}
if (!settings.autoCollapseLeftPanel) {
return;
}
const button = document.querySelector(LEFT_PANEL_TOGGLE_BUTTON_SELECTOR);
if (button && document.body.classList.contains(LEFT_PANEL_EXPANDED_CLASS)) {
console.log(`${SCRIPT_PREFIX} 自动折叠左侧面板: 点击切换按钮.`);
button.click();
} else if (!button) {
console.log(`${SCRIPT_PREFIX} 自动折叠左侧: 未找到切换按钮.`);
}
}
function triggerAutoCollapseLeftPanelIfNeeded() {
if (!settings.autoCollapseLeftPanel) return;
if (clickDebounceTimeoutLeft) clearTimeout(clickDebounceTimeoutLeft);
clickDebounceTimeoutLeft = setTimeout(clickLeftPanelToggleButton, 200);
}
// --- 脚本初始化 ---
function initializeScript() {
console.log(`${SCRIPT_PREFIX} 正在运行初始化...`);
updateStyles();
registerMenuCommands();
initializePanelObserver();
setTimeout(() => {
if (settings.autoCollapseLeftPanel) {
triggerAutoCollapseLeftPanelIfNeeded();
}
}, 1500);
setTimeout(() => {
if (settings.autoCollapseRightPanel) {
const panel = document.querySelector(RIGHT_PANEL_TAG_NAME);
if (panel) {
const initialNgTns = getNgTnsClass(panel);
if (initialNgTns) {
lastNgTnsClass = initialNgTns;
triggerAutoCollapseRightPanelIfNeeded();
} else {
lastNgTnsClass = null;
}
} else {
lastNgTnsClass = null;
}
}
}, 1800);
console.log(`${SCRIPT_PREFIX} 初始化完成.`);
}
// --- 启动脚本 ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeScript);
} else {
initializeScript();
}
window.addEventListener('unload', () => {
if (panelObserver) {
panelObserver.disconnect();
console.log(`${SCRIPT_PREFIX} 右侧面板 MutationObserver 已断开.`);
}
if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
if (clickDebounceTimeoutLeft) clearTimeout(clickDebounceTimeoutLeft);
const notification = document.getElementById('enhancer-notification');
if (notification && notification.timeoutId) {
clearTimeout(notification.timeoutId);
}
});
})();