-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbetter_codeblock.js
More file actions
109 lines (93 loc) · 6.46 KB
/
better_codeblock.js
File metadata and controls
109 lines (93 loc) · 6.46 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
// ==UserScript==
// @name Gemini Better Codeblock
// @description Better codeblock
// @author KlartNET
// @version 0.0
// @match https://gemini.google.com/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 1. [CSS 최적화] 중복 선택자 병합 및 렌더링 트리 탐색 가속화
const style = document.createElement('style');
style.textContent = `
.formatted-code-block-internal-container {
padding: 0 !important; overflow: visible !important; width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; position: relative !important; outline: none !important;
clip-path: inset(0 round var(--gem-sys-shape--corner-extra-large-max, 16px)) !important;
}
.animated-opacity { overflow: visible !important; position: relative !important; display: grid !important; grid-template-columns: minmax(0, 1fr) !important; width: 100% !important; max-width: 100% !important; }
.animated-opacity pre { grid-column: 1 !important; overflow-x: auto !important; display: block !important; width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; padding: 16px 24px 24px 24px !important; margin: 0 !important; cursor: pointer !important; }
/* 기본 헤더 스타일 단일화 (기본값: Relative 배치) */
.code-block-decoration.header-formatted {
grid-column: 1 !important; left: 0 !important; z-index: 100 !important; width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; margin: 0 !important; display: flex !important; justify-content: space-between !important; align-items: center !important; border-radius: 0 !important;
height: auto !important; min-height: auto !important; padding: 12px 24px !important;
background-color: rgba(248, 249, 250, 0.5) !important; border-bottom: 1px solid rgba(0, 0, 0, 0.06) !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.01);
position: relative !important; top: auto !important;
}
/* 조건 충족 시에만 Sticky 상속 속성으로 오버라이드 */
.formatted-code-block-internal-container.is-expanded .code-block-decoration.header-formatted,
.formatted-code-block-internal-container:not(.can-collapse) .code-block-decoration.header-formatted {
position: sticky !important; top: 0 !important;
}
/* 다크모드(.gb_J) 반투명 레이어 매칭 선언 통합 */
@media (prefers-color-scheme: dark) { .code-block-decoration.header-formatted { background-color: rgba(19, 19, 20, 0.5) !important; border-bottom-color: rgba(255, 255, 255, 0.06) !important; } }
.gb_J .code-block-decoration.header-formatted, html[theme="dark"] .code-block-decoration.header-formatted { background-color: rgba(19, 19, 20, 0.5) !important; border-bottom-color: rgba(255, 255, 255, 0.06) !important; }
/* 비확장 및 확장 레이아웃 연산 격리 */
.formatted-code-block-internal-container.can-collapse:not(.is-expanded) pre { max-height: 240px !important; overflow-y: hidden !important; }
.formatted-code-block-internal-container.can-collapse::after {
content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 60px; pointer-events: none; z-index: 10;
background: linear-gradient(transparent, var(--lumi-sys-color--code-background, #f8f9fa)) !important; transition: opacity 200ms cubic-bezier(0.2, 0, 0, 1); opacity: 1;
}
@media (prefers-color-scheme: dark) { .formatted-code-block-internal-container.can-collapse::after { background: linear-gradient(transparent, var(--lumi-sys-color--code-background, #131314)) !important; } }
.gb_J .formatted-code-block-internal-container.can-collapse::after, html[theme="dark"] .formatted-code-block-internal-container.can-collapse::after { background: linear-gradient(transparent, var(--lumi-sys-color--code-background, #131314)) !important; }
.formatted-code-block-internal-container.can-collapse.is-expanded pre { max-height: none !important; overflow-y: auto !important; }
.formatted-code-block-internal-container.can-collapse.is-expanded::after { opacity: 0 !important; }
`;
document.head.appendChild(style);
let isFrameRequested = false;
// 2. [JS 최적화] Read/Write 배치 처리를 통한 단일 프레임 리플로우 구현
function executeBatchProcessing() {
isFrameRequested = false;
const targets = document.querySelectorAll('.formatted-code-block-internal-container:not([data-fold-initialized])');
if (!targets.length) return;
const writeQueue = [];
// Phase 1: 순수 무부하 레이아웃 Read (DOM 변형 없이 높이값만 일괄 수집)
targets.forEach(container => {
const pre = container.querySelector('pre');
if (pre) {
const scrollHeight = pre.scrollHeight;
// 비동기 렌더링 도중 빈 데이터 스트림 상태가 아닐 때만 큐에 진입
if (scrollHeight > 40) {
writeQueue.push({ container, pre, scrollHeight });
}
}
});
// Phase 2: 일괄 DOM Write (브라우저의 리플로우 연산을 단 1회로 압축)
writeQueue.forEach(({ container, pre, scrollHeight }) => {
container.setAttribute('data-fold-initialized', 'true');
if (scrollHeight > 400) {
container.classList.add('can-collapse');
pre.addEventListener('click', () => container.classList.add('is-expanded'));
}
});
}
// 3. MutationObserver 버스트 통제 및 requestAnimationFrame 스케줄링
const observer = new MutationObserver(() => {
if (!isFrameRequested) {
isFrameRequested = true;
requestAnimationFrame(executeBatchProcessing);
}
});
observer.observe(document.body, { childList: true, subtree: true });
// 4. 전역 바깥 클릭(Blur) 이벤트 리스너 연산 고속화
document.addEventListener('click', (e) => {
const expandedContainers = document.querySelectorAll('.formatted-code-block-internal-container.is-expanded');
if (!expandedContainers.length) return;
expandedContainers.forEach(container => {
if (!container.contains(e.target)) {
container.classList.remove('is-expanded');
}
});
});
})();