Optimize long text fluency and add local code folding. Note: The global folding feature is currently in Beta.
// ==UserScript==
// @name Gemini Web UI Optimizer
// @name:zh-CN Gemini网页版UI优化器
// @namespace https://github.com/JayConstruct/gemini-web-optimizer
// @version 1.0.1
// @description Optimize long text fluency and add local code folding. Note: The global folding feature is currently in Beta.
// @description:zh-CN 优化长文本流畅度,添加局部代码折叠功能。注:全局折叠功能目前为测试版(Beta)。
// @author JayConstruct
// @license MIT
// @match https://gemini.google.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// ==========================================
// 1. 样式注入:原生 UI 像素级匹配与细节优化
// ==========================================
const style = document.createElement('style');
style.textContent = `
* { overflow-anchor: none !important; }
user-query, model-response {
content-visibility: auto !important;
contain: content !important;
transform: translateZ(0);
will-change: transform;
}
/* 强制操作区按钮垂直居中对齐 */
code-block .buttons {
display: flex !important;
align-items: center !important;
}
/* 🌟 局部与全局按钮的基础样式共用 */
.custom-fold-btn, .custom-global-fold-btn {
background: transparent;
border: none;
color: var(--mat-sys-on-surface-variant, inherit);
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
margin: 0 2px;
display: inline-flex;
align-items: center;
justify-content: center;
outline: none;
position: relative;
transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.custom-fold-btn:hover, .custom-global-fold-btn:hover {
background-color: rgba(154, 160, 166, 0.15);
}
.custom-fold-btn:active, .custom-global-fold-btn:active {
background-color: rgba(154, 160, 166, 0.3);
}
/* SVG 图标控制 */
.custom-fold-btn svg, .custom-global-fold-btn svg {
width: 24px;
height: 24px;
fill: currentColor;
transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* 局部按钮的 180度旋转 */
.custom-fold-btn.is-rotated svg {
transform: rotate(-180deg);
}
/* 头部背景框底部圆角平滑过渡 */
code-block > div > div:first-child {
transition: border-radius 250ms cubic-bezier(0.4, 0, 0.2, 1) !important;
}
.code-folded > div > div:first-child {
border-bottom-left-radius: 16px !important;
border-bottom-right-radius: 16px !important;
}
/* 瞬间折叠以修复滚动锚定 Bug */
code-block > div > div:nth-of-type(2) {
opacity: 1;
transition: opacity 0.15s ease-out;
}
.code-folded > div > div:nth-of-type(2) {
display: none !important;
}
`;
document.head.appendChild(style);
// ==========================================
// 2. 原生图标构建器
// ==========================================
// 局部折叠图标 (expand_more)
const createNativeIcon = () => {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', "M12 15.4l-6-6L7.4 8l4.6 4.6L16.6 8 18 9.4z");
svg.appendChild(path);
return svg;
};
// ==========================================
// 3. 🎯 V9.8 核心:全局折叠控制器
// ==========================================
let isAllFolded = false; // 全局状态
// Material 语义路径
const PATH_COLLAPSE_ALL = "M7.41 18.59L8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"; // unfold_less
const PATH_EXPAND_ALL = "M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z"; // unfold_more
const injectGlobalButton = () => {
if (document.querySelector('.custom-global-fold-btn')) return;
// 查找所有 share 按钮,取第一个(通常是顶部 Header 里的那个全局 Share 按钮)
const shareIcons = document.querySelectorAll('mat-icon[data-mat-icon-name="share"]');
if (shareIcons.length === 0) return;
const topShareBtn = shareIcons[0].closest('button');
if (!topShareBtn || !topShareBtn.parentNode) return;
// 创建全局按钮
const globalBtn = document.createElement('button');
globalBtn.className = 'custom-global-fold-btn';
globalBtn.title = '折叠所有代码块';
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// 初始状态为全部展开,因此显示“准备折叠”的图标
path.setAttribute('d', PATH_COLLAPSE_ALL);
svg.appendChild(path);
globalBtn.appendChild(svg);
// 全局点击事件
globalBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
isAllFolded = !isAllFolded;
// 1. 更新全局按钮自身的状态
if (isAllFolded) {
path.setAttribute('d', PATH_EXPAND_ALL);
globalBtn.title = '展开所有代码块';
} else {
path.setAttribute('d', PATH_COLLAPSE_ALL);
globalBtn.title = '折叠所有代码块';
}
// 2. 同步并覆盖所有局部代码块的状态
document.querySelectorAll('code-block').forEach(block => {
const localBtn = block.querySelector('.custom-fold-btn');
if (isAllFolded) {
block.classList.add('code-folded');
if (localBtn) {
localBtn.classList.add('is-rotated');
localBtn.title = '展开代码';
}
} else {
block.classList.remove('code-folded');
if (localBtn) {
localBtn.classList.remove('is-rotated');
localBtn.title = '收起代码';
}
}
});
};
// 将按钮插入到 Share 按钮的左边
topShareBtn.parentNode.insertBefore(globalBtn, topShareBtn);
};
// ==========================================
// 4. 局部按钮注入与精准滚动引擎
// ==========================================
const injectFoldButton = (codeBlock) => {
if (codeBlock.querySelector('.custom-fold-btn')) return;
const topBar = codeBlock.querySelector('div > div:first-child');
if (!topBar) return;
const copyBtn = topBar.querySelector('button');
if (!copyBtn) return;
const foldBtn = document.createElement('button');
foldBtn.className = 'custom-fold-btn';
foldBtn.title = '收起代码';
foldBtn.appendChild(createNativeIcon());
foldBtn.onclick = (e) => {
e.preventDefault(); e.stopPropagation();
const rectBefore = foldBtn.getBoundingClientRect();
const isFolded = codeBlock.classList.toggle('code-folded');
foldBtn.classList.toggle('is-rotated');
foldBtn.title = isFolded ? '展开代码' : '收起代码';
requestAnimationFrame(() => {
const rectAfter = foldBtn.getBoundingClientRect();
const delta = rectAfter.top - rectBefore.top;
if (Math.abs(delta) > 0) {
let scroller = codeBlock;
while (scroller && scroller !== document.body && scroller !== document.documentElement) {
const s = window.getComputedStyle(scroller);
if ((s.overflowY === 'auto' || s.overflowY === 'scroll' || s.overflowY === 'overlay') && scroller.scrollHeight > scroller.clientHeight) break;
scroller = scroller.parentNode;
}
if (scroller && scroller !== document.body && scroller !== document.documentElement) {
scroller.scrollTop += delta;
} else {
window.scrollBy(0, delta);
}
}
});
};
copyBtn.parentNode.insertBefore(foldBtn, copyBtn);
};
// ==========================================
// 5. Fiber 调度与增量监听
// ==========================================
const initHighlightScheduler = () => {
if (!window.hljs || window.hljs._isFiberOptimized) return;
const originalHighlight = window.hljs.highlightBlock;
let pendingBlocks = new Set();
let streamTimeout = null;
let isStreaming = false;
const processPendingBlocks = (idleDeadline) => {
while (pendingBlocks.size > 0 && (idleDeadline.timeRemaining() > 5 || idleDeadline.didTimeout)) {
const block = pendingBlocks.values().next().value;
pendingBlocks.delete(block);
try { originalHighlight.call(window.hljs, block); } catch (e) {}
}
if (pendingBlocks.size > 0) requestIdleCallback(processPendingBlocks, { timeout: 2000 });
};
window.hljs.highlightBlock = function(block) {
pendingBlocks.add(block);
clearTimeout(streamTimeout);
if (!isStreaming) block.classList.remove('hljs');
isStreaming = true;
streamTimeout = setTimeout(() => {
isStreaming = false;
if (window.requestIdleCallback) requestIdleCallback(processPendingBlocks, { timeout: 2000 });
}, 1000);
};
window.hljs._isFiberOptimized = true;
};
const domObserver = new MutationObserver((mutations) => {
if (!window.hljs || !window.hljs._isFiberOptimized) initHighlightScheduler();
// 尝试在动态 DOM 变化时重新挂载全局按钮(防止被 Angular 刷新掉)
injectGlobalButton();
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.tagName === 'CODE-BLOCK') injectFoldButton(node);
else if (node.querySelectorAll) {
const blocks = node.querySelectorAll('code-block');
if (blocks.length > 0) blocks.forEach(injectFoldButton);
}
}
}
}
});
setTimeout(() => {
document.querySelectorAll('code-block').forEach(injectFoldButton);
const chatContainer = document.querySelector('chat-container') || document.body;
domObserver.observe(chatContainer, { childList: true, subtree: true });
initHighlightScheduler();
injectGlobalButton(); // 首次注入全局按钮
console.log("🚀 Gemini Ultimate Tool v9.8:全局一键折叠控制引擎已上线。");
}, 3000);
})();