-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
283 lines (248 loc) · 11.8 KB
/
script.js
File metadata and controls
283 lines (248 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// ==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);
})();