@young1lin/dsh-ui-gitworkbench 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +28 -0
- package/CHANGELOG_EN.md +28 -0
- package/README.md +100 -50
- package/README_EN.md +2 -1
- package/lib/client.js +6902 -6256
- package/package.json +1 -1
- package/src/client/ChangesFileTree.tsx +550 -0
- package/src/client/ChromeGlyph.tsx +27 -0
- package/src/client/CodeEditor.tsx +129 -3
- package/src/client/CommitHistory.tsx +1001 -0
- package/src/client/DiffViews.tsx +1070 -0
- package/src/client/GitWorkbenchPanel.module.css +15 -2513
- package/src/client/GitWorkbenchPanel.tsx +37 -3959
- package/src/client/PaneDivider.tsx +74 -0
- package/src/client/WorkbenchControls.tsx +1047 -0
- package/src/client/WorktreeGlyph.tsx +24 -0
- package/src/client/cm-search-theme.ts +250 -0
- package/src/client/diff-nav.ts +59 -0
- package/src/client/git-workbench-types.ts +252 -0
- package/src/client/locales.ts +14 -8
- package/src/client/row-window.ts +23 -0
- package/src/client/search-count.ts +125 -0
- package/src/client/side-rows.ts +66 -0
- package/src/client/styles/changes.css +505 -0
- package/src/client/styles/controls.css +236 -0
- package/src/client/styles/environment.css +89 -0
- package/src/client/styles/files.css +113 -0
- package/src/client/styles/history-filters.css +400 -0
- package/src/client/styles/history.css +276 -0
- package/src/client/styles/image.css +67 -0
- package/src/client/styles/operations.css +179 -0
- package/src/client/styles/shell.css +431 -0
- package/src/client/styles/themes.css +224 -0
- package/src/client/use-change-nav.ts +6 -5
- package/src/client/use-row-window.ts +55 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/* ============================ theme palettes ============================ */
|
|
2
|
+
|
|
3
|
+
/* GitHub — the default, and the reference the other palettes are balanced against. */
|
|
4
|
+
.overlay[data-gs-theme='github-dark'] {
|
|
5
|
+
--gs-bg: #0d1117; --gs-panel: #161b22; --gs-raise: #21262d;
|
|
6
|
+
--gs-border: #30363d; --gs-border-soft: #21262d;
|
|
7
|
+
--gs-fg: #e6edf3; --gs-fg-muted: #c9d1d9; --gs-fg-dim: #8b949e; --gs-fg-faint: #6e7681; --gs-fg-fainter: #484f58;
|
|
8
|
+
--gs-accent: #58a6ff; --gs-accent-bg: rgba(56, 139, 253, 0.14); --gs-accent-border: rgba(56, 139, 253, 0.55);
|
|
9
|
+
--gs-add: #3fb950; --gs-del: #f85149; --gs-warn: #d29922; --gs-info: #79c0ff;
|
|
10
|
+
--gs-add-bg: rgba(46, 160, 67, 0.16); --gs-del-bg: rgba(248, 81, 73, 0.16);
|
|
11
|
+
--gs-warn-bg: rgba(187, 128, 9, 0.16); --gs-info-bg: rgba(56, 139, 253, 0.16);
|
|
12
|
+
/* Primer's own diffBlob tokens, flattened over --gs-bg (#0d1117). Opaque
|
|
13
|
+
rather than the alpha hexes Primer ships, because here the number cells
|
|
14
|
+
paint ON TOP of the row tint — two alphas would composite to roughly twice
|
|
15
|
+
GitHub's strength. Line 15% / num 30% / word 40% of the base hue. */
|
|
16
|
+
--gs-add-line: #12261e; --gs-del-line: #25181c;
|
|
17
|
+
--gs-add-word: #1a4a29; --gs-del-word: #6b2b2b;
|
|
18
|
+
--gs-add-num: #1c4428; --gs-del-num: #542426;
|
|
19
|
+
--gs-hunk: #111d2e; --gs-hunk-num: #0c2d6b;
|
|
20
|
+
--gs-neutral-bg: rgba(110, 118, 129, 0.18);
|
|
21
|
+
--gs-backdrop: rgba(1, 4, 9, 0.6); --gs-shadow: rgba(1, 4, 9, 0.55);
|
|
22
|
+
}
|
|
23
|
+
.overlay[data-gs-theme='github-light'] {
|
|
24
|
+
--gs-bg: #ffffff; --gs-panel: #f6f8fa; --gs-raise: #eaeef2;
|
|
25
|
+
--gs-border: #d0d7de; --gs-border-soft: #eaeef2;
|
|
26
|
+
--gs-fg: #1f2328; --gs-fg-muted: #32383f; --gs-fg-dim: #656d76; --gs-fg-faint: #8c959f; --gs-fg-fainter: #afb8c1;
|
|
27
|
+
--gs-accent: #0969da; --gs-accent-bg: rgba(9, 105, 218, 0.1); --gs-accent-border: rgba(9, 105, 218, 0.45);
|
|
28
|
+
--gs-add: #1a7f37; --gs-del: #cf222e; --gs-warn: #9a6700; --gs-info: #0550ae;
|
|
29
|
+
--gs-add-bg: rgba(26, 127, 55, 0.12); --gs-del-bg: rgba(207, 34, 46, 0.12);
|
|
30
|
+
--gs-warn-bg: rgba(154, 103, 0, 0.12); --gs-info-bg: rgba(9, 105, 218, 0.12);
|
|
31
|
+
/* Primer's light diffBlob tokens, which are already opaque upstream. */
|
|
32
|
+
--gs-add-line: #dafbe1; --gs-del-line: #ffebe9;
|
|
33
|
+
--gs-add-word: #aceebb; --gs-del-word: #ffcecb;
|
|
34
|
+
--gs-add-num: #aceebb; --gs-del-num: #ffcecb;
|
|
35
|
+
--gs-hunk: #ddf4ff; --gs-hunk-num: #b6e3ff;
|
|
36
|
+
--gs-neutral-bg: rgba(110, 118, 129, 0.12);
|
|
37
|
+
--gs-backdrop: rgba(31, 35, 40, 0.28); --gs-shadow: rgba(31, 35, 40, 0.22);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* IntelliJ IDEA — the New UI (2022.3+, the default since 2024.1), not Darcula.
|
|
41
|
+
Its greys are the giveaway: the New UI dropped Darcula's warm #3C3F41 chrome
|
|
42
|
+
for the cooler #1E1F22 editor over a #2B2D30 tool window, and moved the
|
|
43
|
+
accent to #3574F0.
|
|
44
|
+
|
|
45
|
+
The semantic four are the IntelliJ platform's own VCS colours (the keys the
|
|
46
|
+
log and the diff gutter paint with), except that dark's green is the New UI's
|
|
47
|
+
brighter #5FAD65 rather than the platform #499C54 — the latter is a fill
|
|
48
|
+
colour there, and as 12px TEXT on #1E1F22 it lands under 4:1.
|
|
49
|
+
|
|
50
|
+
Unlike the GitHub palettes below, these are not transcribed from a published
|
|
51
|
+
token file, so there is no test pinning them to a source of truth; they are
|
|
52
|
+
read off the New UI's own chrome and diff. */
|
|
53
|
+
.overlay[data-gs-theme='idea-dark'] {
|
|
54
|
+
--gs-bg: #1e1f22; --gs-panel: #2b2d30; --gs-raise: #393b40;
|
|
55
|
+
--gs-border: #393b40; --gs-border-soft: #2b2d30;
|
|
56
|
+
--gs-fg: #dfe1e5; --gs-fg-muted: #ced0d6; --gs-fg-dim: #9da0a8; --gs-fg-faint: #6f737a; --gs-fg-fainter: #4e5157;
|
|
57
|
+
--gs-accent: #548af7; --gs-accent-bg: rgba(84, 138, 247, 0.16); --gs-accent-border: rgba(84, 138, 247, 0.55);
|
|
58
|
+
--gs-add: #5fad65; --gs-del: #e26e6e; --gs-warn: #f0a732; --gs-info: #3592c4;
|
|
59
|
+
--gs-add-bg: rgba(95, 173, 101, 0.16); --gs-del-bg: rgba(226, 110, 110, 0.16);
|
|
60
|
+
--gs-warn-bg: rgba(240, 167, 50, 0.16); --gs-info-bg: rgba(53, 146, 196, 0.16);
|
|
61
|
+
/* IDEA paints diff as solid blocks rather than a wash, and the word-level
|
|
62
|
+
highlight as a distinctly stronger block over it — so these are opaque,
|
|
63
|
+
like GitHub's, instead of the alpha tints the other families use. */
|
|
64
|
+
--gs-add-line: #293c2e; --gs-del-line: #3d2b2b;
|
|
65
|
+
--gs-add-word: #3d6640; --gs-del-word: #6b3838;
|
|
66
|
+
--gs-hunk: rgba(84, 138, 247, 0.10); --gs-neutral-bg: rgba(157, 160, 168, 0.18);
|
|
67
|
+
--gs-backdrop: rgba(16, 17, 19, 0.6); --gs-shadow: rgba(0, 0, 0, 0.5);
|
|
68
|
+
}
|
|
69
|
+
.overlay[data-gs-theme='idea-light'] {
|
|
70
|
+
--gs-bg: #ffffff; --gs-panel: #f7f8fa; --gs-raise: #ebecf0;
|
|
71
|
+
--gs-border: #d3d5db; --gs-border-soft: #ebecf0;
|
|
72
|
+
--gs-fg: #1e1f22; --gs-fg-muted: #3c3f44; --gs-fg-dim: #6c707e; --gs-fg-faint: #818594; --gs-fg-fainter: #a8adbd;
|
|
73
|
+
--gs-accent: #3574f0; --gs-accent-bg: rgba(53, 116, 240, 0.1); --gs-accent-border: rgba(53, 116, 240, 0.45);
|
|
74
|
+
--gs-add: #398a4a; --gs-del: #cc4a4a; --gs-warn: #a8760b; --gs-info: #2b7fb8;
|
|
75
|
+
--gs-add-bg: rgba(57, 138, 74, 0.12); --gs-del-bg: rgba(204, 74, 74, 0.12);
|
|
76
|
+
--gs-warn-bg: rgba(168, 118, 11, 0.12); --gs-info-bg: rgba(43, 127, 184, 0.12);
|
|
77
|
+
--gs-add-line: #e6f5e9; --gs-del-line: #fbe9ea;
|
|
78
|
+
--gs-add-word: #c2e5c9; --gs-del-word: #f5c9cc;
|
|
79
|
+
--gs-hunk: rgba(53, 116, 240, 0.07); --gs-neutral-bg: rgba(108, 112, 126, 0.12);
|
|
80
|
+
--gs-backdrop: rgba(30, 31, 34, 0.26); --gs-shadow: rgba(30, 31, 34, 0.18);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* VS Code — Dark+ / Light+, the default editor themes. */
|
|
84
|
+
.overlay[data-gs-theme='vscode-dark'] {
|
|
85
|
+
--gs-bg: #1e1e1e; --gs-panel: #252526; --gs-raise: #2d2d30;
|
|
86
|
+
--gs-border: #3e3e42; --gs-border-soft: #2d2d30;
|
|
87
|
+
--gs-fg: #d4d4d4; --gs-fg-muted: #cccccc; --gs-fg-dim: #9d9d9d; --gs-fg-faint: #808080; --gs-fg-fainter: #5a5a5a;
|
|
88
|
+
--gs-accent: #0098ff; --gs-accent-bg: rgba(0, 122, 204, 0.24); --gs-accent-border: rgba(0, 152, 255, 0.6);
|
|
89
|
+
--gs-add: #89d185; --gs-del: #f14c4c; --gs-warn: #cca700; --gs-info: #75beff;
|
|
90
|
+
--gs-add-bg: rgba(137, 209, 133, 0.18); --gs-del-bg: rgba(241, 76, 76, 0.18);
|
|
91
|
+
--gs-warn-bg: rgba(204, 167, 0, 0.18); --gs-info-bg: rgba(117, 190, 255, 0.18);
|
|
92
|
+
--gs-add-line: rgba(155, 185, 85, 0.18); --gs-del-line: rgba(255, 0, 0, 0.16);
|
|
93
|
+
--gs-add-word: rgba(155, 185, 85, 0.4); --gs-del-word: rgba(255, 0, 0, 0.36);
|
|
94
|
+
--gs-hunk: rgba(0, 122, 204, 0.14); --gs-neutral-bg: rgba(190, 190, 190, 0.14);
|
|
95
|
+
--gs-backdrop: rgba(0, 0, 0, 0.55); --gs-shadow: rgba(0, 0, 0, 0.6);
|
|
96
|
+
}
|
|
97
|
+
.overlay[data-gs-theme='vscode-light'] {
|
|
98
|
+
--gs-bg: #ffffff; --gs-panel: #f3f3f3; --gs-raise: #e8e8e8;
|
|
99
|
+
--gs-border: #cecece; --gs-border-soft: #e8e8e8;
|
|
100
|
+
--gs-fg: #1e1e1e; --gs-fg-muted: #333333; --gs-fg-dim: #616161; --gs-fg-faint: #767676; --gs-fg-fainter: #a0a0a0;
|
|
101
|
+
--gs-accent: #005fb8; --gs-accent-bg: rgba(0, 95, 184, 0.1); --gs-accent-border: rgba(0, 95, 184, 0.45);
|
|
102
|
+
--gs-add: #10793f; --gs-del: #b5200d; --gs-warn: #855b00; --gs-info: #005fb8;
|
|
103
|
+
--gs-add-bg: rgba(16, 121, 63, 0.12); --gs-del-bg: rgba(181, 32, 13, 0.12);
|
|
104
|
+
--gs-warn-bg: rgba(133, 91, 0, 0.12); --gs-info-bg: rgba(0, 95, 184, 0.12);
|
|
105
|
+
--gs-add-line: rgba(155, 185, 85, 0.22); --gs-del-line: rgba(255, 0, 0, 0.13);
|
|
106
|
+
--gs-add-word: rgba(155, 185, 85, 0.45); --gs-del-word: rgba(255, 0, 0, 0.3);
|
|
107
|
+
--gs-hunk: rgba(0, 95, 184, 0.07); --gs-neutral-bg: rgba(97, 97, 97, 0.12);
|
|
108
|
+
--gs-backdrop: rgba(0, 0, 0, 0.25); --gs-shadow: rgba(0, 0, 0, 0.18);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Cyberpunk — neon on deep violet; the light variant keeps the magenta/cyan pair
|
|
112
|
+
over a near-white ground so the theme survives a bright room. */
|
|
113
|
+
.overlay[data-gs-theme='cyberpunk-dark'] {
|
|
114
|
+
--gs-bg: #0b0417; --gs-panel: #14082a; --gs-raise: #1f0d3d;
|
|
115
|
+
--gs-border: #3a1f6b; --gs-border-soft: #26124a;
|
|
116
|
+
--gs-fg: #f0e6ff; --gs-fg-muted: #d5c2f5; --gs-fg-dim: #a98fd6; --gs-fg-faint: #7d64a8; --gs-fg-fainter: #56427a;
|
|
117
|
+
--gs-accent: #00f0ff; --gs-on-accent: #0b0417; --gs-accent-bg: rgba(0, 240, 255, 0.14); --gs-accent-border: rgba(0, 240, 255, 0.5);
|
|
118
|
+
--gs-add: #39ff88; --gs-del: #ff2e88; --gs-warn: #ffcc00; --gs-info: #00f0ff;
|
|
119
|
+
--gs-add-bg: rgba(57, 255, 136, 0.14); --gs-del-bg: rgba(255, 46, 136, 0.14);
|
|
120
|
+
--gs-warn-bg: rgba(255, 204, 0, 0.14); --gs-info-bg: rgba(0, 240, 255, 0.14);
|
|
121
|
+
--gs-add-line: rgba(57, 255, 136, 0.12); --gs-del-line: rgba(255, 46, 136, 0.12);
|
|
122
|
+
--gs-add-word: rgba(57, 255, 136, 0.34); --gs-del-word: rgba(255, 46, 136, 0.34);
|
|
123
|
+
--gs-hunk: rgba(0, 240, 255, 0.1); --gs-neutral-bg: rgba(169, 143, 214, 0.16);
|
|
124
|
+
--gs-backdrop: rgba(6, 2, 14, 0.68); --gs-shadow: rgba(0, 240, 255, 0.14);
|
|
125
|
+
}
|
|
126
|
+
.overlay[data-gs-theme='cyberpunk-light'] {
|
|
127
|
+
--gs-bg: #fdfbff; --gs-panel: #f4eeff; --gs-raise: #e9dfff;
|
|
128
|
+
--gs-border: #c9b3f0; --gs-border-soft: #e0d2f7;
|
|
129
|
+
--gs-fg: #1a0b2e; --gs-fg-muted: #33195c; --gs-fg-dim: #6b4ba0; --gs-fg-faint: #8f74bd; --gs-fg-fainter: #b9a5d6;
|
|
130
|
+
--gs-accent: #00a6b8; --gs-accent-bg: rgba(0, 166, 184, 0.12); --gs-accent-border: rgba(0, 166, 184, 0.5);
|
|
131
|
+
--gs-add: #00875a; --gs-del: #d6006e; --gs-warn: #b37a00; --gs-info: #00a6b8;
|
|
132
|
+
--gs-add-bg: rgba(0, 135, 90, 0.12); --gs-del-bg: rgba(214, 0, 110, 0.12);
|
|
133
|
+
--gs-warn-bg: rgba(179, 122, 0, 0.12); --gs-info-bg: rgba(0, 166, 184, 0.12);
|
|
134
|
+
--gs-add-line: rgba(0, 200, 130, 0.14); --gs-del-line: rgba(255, 46, 136, 0.12);
|
|
135
|
+
--gs-add-word: rgba(0, 200, 130, 0.34); --gs-del-word: rgba(255, 46, 136, 0.3);
|
|
136
|
+
--gs-hunk: rgba(0, 166, 184, 0.08); --gs-neutral-bg: rgba(107, 75, 160, 0.12);
|
|
137
|
+
--gs-backdrop: rgba(26, 11, 46, 0.28); --gs-shadow: rgba(26, 11, 46, 0.16);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* One — Atom's One Dark / One Light. */
|
|
141
|
+
.overlay[data-gs-theme='one-dark'] {
|
|
142
|
+
--gs-bg: #282c34; --gs-panel: #21252b; --gs-raise: #2c313a;
|
|
143
|
+
--gs-border: #3e4451; --gs-border-soft: #2c313a;
|
|
144
|
+
--gs-fg: #abb2bf; --gs-fg-muted: #b6bdca; --gs-fg-dim: #7f848e; --gs-fg-faint: #636d83; --gs-fg-fainter: #4b5263;
|
|
145
|
+
--gs-accent: #61afef; --gs-accent-bg: rgba(97, 175, 239, 0.14); --gs-accent-border: rgba(97, 175, 239, 0.55);
|
|
146
|
+
--gs-add: #98c379; --gs-del: #e06c75; --gs-warn: #e5c07b; --gs-info: #56b6c2;
|
|
147
|
+
--gs-add-bg: rgba(152, 195, 121, 0.16); --gs-del-bg: rgba(224, 108, 117, 0.16);
|
|
148
|
+
--gs-warn-bg: rgba(229, 192, 123, 0.16); --gs-info-bg: rgba(86, 182, 194, 0.16);
|
|
149
|
+
--gs-add-line: rgba(152, 195, 121, 0.14); --gs-del-line: rgba(224, 108, 117, 0.14);
|
|
150
|
+
--gs-add-word: rgba(152, 195, 121, 0.38); --gs-del-word: rgba(224, 108, 117, 0.38);
|
|
151
|
+
--gs-hunk: rgba(97, 175, 239, 0.1); --gs-neutral-bg: rgba(127, 132, 142, 0.18);
|
|
152
|
+
--gs-backdrop: rgba(23, 26, 33, 0.6); --gs-shadow: rgba(15, 17, 21, 0.55);
|
|
153
|
+
}
|
|
154
|
+
.overlay[data-gs-theme='one-light'] {
|
|
155
|
+
--gs-bg: #fafafa; --gs-panel: #f0f0f0; --gs-raise: #e5e5e6;
|
|
156
|
+
--gs-border: #d4d4d4; --gs-border-soft: #e5e5e6;
|
|
157
|
+
--gs-fg: #383a42; --gs-fg-muted: #4a4c53; --gs-fg-dim: #696c77; --gs-fg-faint: #909196; --gs-fg-fainter: #b8b9bd;
|
|
158
|
+
--gs-accent: #4078f2; --gs-accent-bg: rgba(64, 120, 242, 0.1); --gs-accent-border: rgba(64, 120, 242, 0.45);
|
|
159
|
+
--gs-add: #50a14f; --gs-del: #e45649; --gs-warn: #c18401; --gs-info: #0184bc;
|
|
160
|
+
--gs-add-bg: rgba(80, 161, 79, 0.12); --gs-del-bg: rgba(228, 86, 73, 0.12);
|
|
161
|
+
--gs-warn-bg: rgba(193, 132, 1, 0.12); --gs-info-bg: rgba(1, 132, 188, 0.12);
|
|
162
|
+
--gs-add-line: rgba(80, 161, 79, 0.14); --gs-del-line: rgba(228, 86, 73, 0.13);
|
|
163
|
+
--gs-add-word: rgba(80, 161, 79, 0.34); --gs-del-word: rgba(228, 86, 73, 0.32);
|
|
164
|
+
--gs-hunk: rgba(64, 120, 242, 0.07); --gs-neutral-bg: rgba(105, 108, 119, 0.12);
|
|
165
|
+
--gs-backdrop: rgba(56, 58, 66, 0.26); --gs-shadow: rgba(56, 58, 66, 0.18);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Solarized — Ethan Schoonover's fixed sixteen, both official grounds. */
|
|
169
|
+
.overlay[data-gs-theme='solarized-dark'] {
|
|
170
|
+
--gs-bg: #002b36; --gs-panel: #073642; --gs-raise: #0a4553;
|
|
171
|
+
--gs-border: #0f5666; --gs-border-soft: #073642;
|
|
172
|
+
--gs-fg: #93a1a1; --gs-fg-muted: #839496; --gs-fg-dim: #657b83; --gs-fg-faint: #586e75; --gs-fg-fainter: #45636b;
|
|
173
|
+
--gs-accent: #268bd2; --gs-accent-bg: rgba(38, 139, 210, 0.16); --gs-accent-border: rgba(38, 139, 210, 0.55);
|
|
174
|
+
--gs-add: #859900; --gs-del: #dc322f; --gs-warn: #b58900; --gs-info: #2aa198;
|
|
175
|
+
--gs-add-bg: rgba(133, 153, 0, 0.2); --gs-del-bg: rgba(220, 50, 47, 0.18);
|
|
176
|
+
--gs-warn-bg: rgba(181, 137, 0, 0.18); --gs-info-bg: rgba(42, 161, 152, 0.18);
|
|
177
|
+
--gs-add-line: rgba(133, 153, 0, 0.16); --gs-del-line: rgba(220, 50, 47, 0.15);
|
|
178
|
+
--gs-add-word: rgba(133, 153, 0, 0.4); --gs-del-word: rgba(220, 50, 47, 0.38);
|
|
179
|
+
--gs-hunk: rgba(38, 139, 210, 0.12); --gs-neutral-bg: rgba(101, 123, 131, 0.2);
|
|
180
|
+
--gs-backdrop: rgba(0, 20, 26, 0.62); --gs-shadow: rgba(0, 20, 26, 0.55);
|
|
181
|
+
}
|
|
182
|
+
.overlay[data-gs-theme='solarized-light'] {
|
|
183
|
+
--gs-bg: #fdf6e3; --gs-panel: #eee8d5; --gs-raise: #e3ddca;
|
|
184
|
+
--gs-border: #d5cfbb; --gs-border-soft: #eee8d5;
|
|
185
|
+
--gs-fg: #586e75; --gs-fg-muted: #657b83; --gs-fg-dim: #839496; --gs-fg-faint: #93a1a1; --gs-fg-fainter: #b5b0a0;
|
|
186
|
+
--gs-accent: #268bd2; --gs-accent-bg: rgba(38, 139, 210, 0.12); --gs-accent-border: rgba(38, 139, 210, 0.45);
|
|
187
|
+
--gs-add: #6c7c00; --gs-del: #cb2825; --gs-warn: #9a7400; --gs-info: #21867f;
|
|
188
|
+
--gs-add-bg: rgba(133, 153, 0, 0.18); --gs-del-bg: rgba(220, 50, 47, 0.14);
|
|
189
|
+
--gs-warn-bg: rgba(181, 137, 0, 0.16); --gs-info-bg: rgba(42, 161, 152, 0.14);
|
|
190
|
+
--gs-add-line: rgba(133, 153, 0, 0.16); --gs-del-line: rgba(220, 50, 47, 0.12);
|
|
191
|
+
--gs-add-word: rgba(133, 153, 0, 0.38); --gs-del-word: rgba(220, 50, 47, 0.3);
|
|
192
|
+
--gs-hunk: rgba(38, 139, 210, 0.08); --gs-neutral-bg: rgba(88, 110, 117, 0.14);
|
|
193
|
+
--gs-backdrop: rgba(88, 110, 117, 0.26); --gs-shadow: rgba(88, 110, 117, 0.2);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Nord — Polar Night for dark, Snow Storm for light. Nord's aurora greens and
|
|
197
|
+
reds are tuned for a dark ground, so the light variant darkens the diff pair
|
|
198
|
+
rather than washing it out. */
|
|
199
|
+
.overlay[data-gs-theme='nord-dark'] {
|
|
200
|
+
--gs-bg: #2e3440; --gs-panel: #3b4252; --gs-raise: #434c5e;
|
|
201
|
+
--gs-border: #4c566a; --gs-border-soft: #3b4252;
|
|
202
|
+
--gs-fg: #eceff4; --gs-fg-muted: #d8dee9; --gs-fg-dim: #9aa5b8; --gs-fg-faint: #7b88a1; --gs-fg-fainter: #5d6a82;
|
|
203
|
+
--gs-accent: #88c0d0; --gs-on-accent: #2e3440; --gs-accent-bg: rgba(136, 192, 208, 0.14); --gs-accent-border: rgba(136, 192, 208, 0.55);
|
|
204
|
+
--gs-add: #a3be8c; --gs-del: #bf616a; --gs-warn: #ebcb8b; --gs-info: #81a1c1;
|
|
205
|
+
--gs-add-bg: rgba(163, 190, 140, 0.16); --gs-del-bg: rgba(191, 97, 106, 0.18);
|
|
206
|
+
--gs-warn-bg: rgba(235, 203, 139, 0.16); --gs-info-bg: rgba(129, 161, 193, 0.16);
|
|
207
|
+
--gs-add-line: rgba(163, 190, 140, 0.14); --gs-del-line: rgba(191, 97, 106, 0.15);
|
|
208
|
+
--gs-add-word: rgba(163, 190, 140, 0.38); --gs-del-word: rgba(191, 97, 106, 0.4);
|
|
209
|
+
--gs-hunk: rgba(136, 192, 208, 0.1); --gs-neutral-bg: rgba(154, 165, 184, 0.16);
|
|
210
|
+
--gs-backdrop: rgba(29, 35, 45, 0.62); --gs-shadow: rgba(20, 25, 33, 0.55);
|
|
211
|
+
}
|
|
212
|
+
.overlay[data-gs-theme='nord-light'] {
|
|
213
|
+
--gs-bg: #eceff4; --gs-panel: #e5e9f0; --gs-raise: #d8dee9;
|
|
214
|
+
--gs-border: #c3cbd8; --gs-border-soft: #dfe4ec;
|
|
215
|
+
--gs-fg: #2e3440; --gs-fg-muted: #3b4252; --gs-fg-dim: #4c566a; --gs-fg-faint: #6b7689; --gs-fg-fainter: #9aa4b5;
|
|
216
|
+
--gs-accent: #5e81ac; --gs-accent-bg: rgba(94, 129, 172, 0.12); --gs-accent-border: rgba(94, 129, 172, 0.5);
|
|
217
|
+
--gs-add: #5e7d47; --gs-del: #a5424c; --gs-warn: #97701f; --gs-info: #4c7a8c;
|
|
218
|
+
--gs-add-bg: rgba(94, 125, 71, 0.14); --gs-del-bg: rgba(165, 66, 76, 0.13);
|
|
219
|
+
--gs-warn-bg: rgba(151, 112, 31, 0.14); --gs-info-bg: rgba(76, 122, 140, 0.13);
|
|
220
|
+
--gs-add-line: rgba(94, 125, 71, 0.14); --gs-del-line: rgba(165, 66, 76, 0.12);
|
|
221
|
+
--gs-add-word: rgba(94, 125, 71, 0.34); --gs-del-word: rgba(165, 66, 76, 0.32);
|
|
222
|
+
--gs-hunk: rgba(94, 129, 172, 0.09); --gs-neutral-bg: rgba(76, 86, 106, 0.13);
|
|
223
|
+
--gs-backdrop: rgba(46, 52, 64, 0.26); --gs-shadow: rgba(46, 52, 64, 0.18);
|
|
224
|
+
}
|
|
@@ -22,8 +22,8 @@ import { anchorFrom, scrollTopFor, stepToBlock, type BlockTop, type NavMemory }
|
|
|
22
22
|
|
|
23
23
|
/** What a pane needs to offer the walk. */
|
|
24
24
|
export interface ChangeNav {
|
|
25
|
-
/** Move to the next (1) or previous (-1) change
|
|
26
|
-
readonly goToChange: (direction: 1 | -1) =>
|
|
25
|
+
/** Move to the next (1) or previous (-1) change and report its block id. */
|
|
26
|
+
readonly goToChange: (direction: 1 | -1) => number | null
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -76,17 +76,18 @@ export function useChangeNav(
|
|
|
76
76
|
return tops
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const goToChange = (direction: 1 | -1):
|
|
79
|
+
const goToChange = (direction: 1 | -1): number | null => {
|
|
80
80
|
const scroller = scrollRef.current
|
|
81
|
-
if (scroller === null) return
|
|
81
|
+
if (scroller === null) return null
|
|
82
82
|
const measured = tops === undefined ? blockTops() : tops()
|
|
83
83
|
const target = stepToBlock(measured, anchorFrom(measured, scroller.scrollTop, navMemory.current), direction)
|
|
84
|
-
if (target === null) return
|
|
84
|
+
if (target === null) return null
|
|
85
85
|
scroller.scrollTop = scrollTopFor(target.top)
|
|
86
86
|
// Read back rather than storing what was asked for: the browser clamps at
|
|
87
87
|
// the end of the content, and the clamped value is what the next press
|
|
88
88
|
// compares against to tell "still here" from "the reader scrolled".
|
|
89
89
|
navMemory.current = { block: target.block, scrollTop: scroller.scrollTop }
|
|
90
|
+
return target.block
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
return { goToChange }
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { rowWindow, rowWindowForMount, sameRowWindow, type HeldRowWindow, type RowWindow } from './row-window.ts'
|
|
4
|
+
|
|
5
|
+
/* ---------- shared helpers ---------- */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The rows a diff actually has to put in the DOM, tracked against its scroller.
|
|
9
|
+
*
|
|
10
|
+
* Rendering a whole file cost the pane 3.9 seconds of main thread and a
|
|
11
|
+
* 3.6-second frozen frame at 4,000 lines — for a one-line change, because the
|
|
12
|
+
* side-by-side view draws every line of the file whether it changed or not.
|
|
13
|
+
* This is the fix that removes the length from the cost rather than capping it.
|
|
14
|
+
*
|
|
15
|
+
* The window is held in state rather than the raw scroll offset so a scroll
|
|
16
|
+
* that does not move it renders nothing: `start` only changes once a whole row
|
|
17
|
+
* has passed under the viewport's edge.
|
|
18
|
+
*
|
|
19
|
+
* @param scrollRef - the element that scrolls the rows.
|
|
20
|
+
* @param rowCount - how many rows the diff has.
|
|
21
|
+
* @returns the rows to render and the spacer heights standing in for the rest.
|
|
22
|
+
*/
|
|
23
|
+
export function useRowWindow(scrollRef: { current: HTMLElement | null }, rowCount: number, mountKey: string): RowWindow {
|
|
24
|
+
const count = Number.isFinite(rowCount) ? Math.max(0, Math.trunc(rowCount)) : 0
|
|
25
|
+
const [held, setHeld] = useState<HeldRowWindow>(() => ({
|
|
26
|
+
mountKey, rowCount: count, win: rowWindow(0, 0, count),
|
|
27
|
+
}))
|
|
28
|
+
// A file can change while the scroller is temporarily unmounted by loading.
|
|
29
|
+
// Do not paint the previous file's spacer for one frame — derive a fresh top
|
|
30
|
+
// window immediately; the effect below replaces it with the measured one.
|
|
31
|
+
const visible = rowWindowForMount(held, count, mountKey)
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const el = scrollRef.current
|
|
34
|
+
if (el === null) return
|
|
35
|
+
const read = (): void => {
|
|
36
|
+
const next = rowWindow(el.scrollTop, el.clientHeight, count)
|
|
37
|
+
setHeld(prev => prev.mountKey === mountKey && prev.rowCount === count && sameRowWindow(prev.win, next)
|
|
38
|
+
? prev
|
|
39
|
+
: { mountKey, rowCount: count, win: next })
|
|
40
|
+
}
|
|
41
|
+
read()
|
|
42
|
+
// Passive: this listener never calls preventDefault, and saying so keeps
|
|
43
|
+
// it off the scroll's critical path.
|
|
44
|
+
el.addEventListener('scroll', read, { passive: true })
|
|
45
|
+
// The drawer resizes without the page doing so — a dragged edge, the
|
|
46
|
+
// maximize button — and a taller pane needs more rows.
|
|
47
|
+
const observer = new ResizeObserver(read)
|
|
48
|
+
observer.observe(el)
|
|
49
|
+
return () => {
|
|
50
|
+
el.removeEventListener('scroll', read)
|
|
51
|
+
observer.disconnect()
|
|
52
|
+
}
|
|
53
|
+
}, [scrollRef, count, mountKey])
|
|
54
|
+
return visible
|
|
55
|
+
}
|