@tanishqkancharla/diffmap 0.1.4 → 0.2.0
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/README.md +69 -61
- package/index.html +18 -0
- package/package.json +7 -5
- package/src/annotations.ts +36 -29
- package/src/changes.ts +229 -0
- package/src/cli.test.ts +50 -0
- package/src/cli.ts +41 -5
- package/src/errors.ts +10 -0
- package/src/gist/GistApp.tsx +39 -20
- package/src/gist/GistStatus.tsx +2 -51
- package/src/gist/fetchGist.ts +23 -0
- package/src/git.test.ts +402 -0
- package/src/git.ts +498 -0
- package/src/github/GitHubApp.tsx +39 -22
- package/src/github/fetchRepo.test.ts +59 -1
- package/src/github/fetchRepo.ts +99 -43
- package/src/github/patch.test.ts +18 -9
- package/src/github/patch.ts +30 -19
- package/src/host/HostHome.tsx +86 -0
- package/src/host/files.ts +10 -0
- package/src/host/home.md +156 -0
- package/src/host/home.test.ts +83 -0
- package/src/index.ts +1 -0
- package/src/packageVersion.ts +7 -0
- package/src/parseFence.ts +6 -6
- package/src/parseViewer.test.ts +87 -0
- package/src/parseViewer.ts +103 -77
- package/src/serve.test.ts +306 -0
- package/src/serve.ts +48 -5
- package/src/share.ts +18 -0
- package/src/update.test.ts +19 -0
- package/src/update.ts +285 -0
- package/src/viewer/CallStackDiff.tsx +15 -3
- package/src/viewer/ChangesCard.tsx +267 -0
- package/src/viewer/CloseServerButton.tsx +16 -0
- package/src/viewer/ComarkView.tsx +83 -15
- package/src/viewer/Fence.tsx +9 -1
- package/src/viewer/FileExcerpt.tsx +25 -3
- package/src/viewer/HtmlPlaceholder.tsx +6 -1
- package/src/viewer/MermaidBlock.tsx +99 -11
- package/src/viewer/RangePicker.tsx +526 -0
- package/src/viewer/SourceDiffPanel.tsx +277 -121
- package/src/viewer/TableOfContents.tsx +313 -39
- package/src/viewer/ViewerApp.tsx +120 -68
- package/src/viewer/changesSource.ts +91 -0
- package/src/viewer/sourceHeader.test.ts +2 -87
- package/src/viewer/sourceHeader.ts +4 -63
- package/src/viewer/sourceNavigation.ts +33 -42
- package/src/viewer/viewerMode.ts +1 -1
- package/vite.config.ts +2 -1
- package/vite.gist.config.ts +57 -0
- package/src/viewer/Check.tsx +0 -24
- package/src/viewer/DoneButton.tsx +0 -32
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type PointerEvent } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import {
|
|
4
|
+
backgroundColor,
|
|
5
|
+
borderColor,
|
|
6
|
+
Button,
|
|
7
|
+
Check,
|
|
8
|
+
ChevronDown,
|
|
9
|
+
colors,
|
|
10
|
+
focusRing,
|
|
11
|
+
GitBranch,
|
|
12
|
+
radius,
|
|
13
|
+
shadow,
|
|
14
|
+
spacing,
|
|
15
|
+
text,
|
|
16
|
+
} from "maui";
|
|
17
|
+
import { style, useStyles } from "purse-styles";
|
|
18
|
+
import {
|
|
19
|
+
revWindow,
|
|
20
|
+
shortRev,
|
|
21
|
+
windowLabel,
|
|
22
|
+
windowRev,
|
|
23
|
+
type Delta,
|
|
24
|
+
type DeltaWindow,
|
|
25
|
+
type Rev,
|
|
26
|
+
} from "../changes.js";
|
|
27
|
+
import { codeFontFamily } from "../codeFont.js";
|
|
28
|
+
import { loadLocalDeltas } from "./changesSource.ts";
|
|
29
|
+
|
|
30
|
+
const ROW = 28;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* "All commits" menu: unstaged, staged, then the branch's commits. Click a
|
|
34
|
+
* row for one change; drag the handles between rows, or drag across rows, for
|
|
35
|
+
* a window.
|
|
36
|
+
*/
|
|
37
|
+
export function RangePicker(props: { rev: Rev; onChange: (rev: Rev) => void }) {
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
const [deltas, setDeltas] = useState<Delta[] | Error>();
|
|
40
|
+
// The window being dragged; it is applied on release.
|
|
41
|
+
const [dragging, setDragging] = useState<DeltaWindow>();
|
|
42
|
+
const button = useRef<HTMLButtonElement>(null);
|
|
43
|
+
const popover = useRef<HTMLDivElement>(null);
|
|
44
|
+
const rows = useRef<HTMLDivElement>(null);
|
|
45
|
+
const [anchor, setAnchor] = useState({ top: 0, left: 0 });
|
|
46
|
+
const trigger = useStyles(styles.trigger);
|
|
47
|
+
const float = useStyles(styles.float);
|
|
48
|
+
const item = useStyles(styles.item);
|
|
49
|
+
const row = useStyles(styles.row);
|
|
50
|
+
const handle = useStyles(styles.handle);
|
|
51
|
+
const foot = useStyles(styles.foot);
|
|
52
|
+
|
|
53
|
+
// Read once for the label, and again on each open: commits and uncommitted changes move.
|
|
54
|
+
// The list reaches back to the open rev, so it always shows as picked.
|
|
55
|
+
const refresh = () => {
|
|
56
|
+
// oxlint-disable-next-line typescript/no-floating-promises -- The picker owns this request; a late result only updates its rows.
|
|
57
|
+
void loadLocalDeltas(props.rev).then(setDeltas);
|
|
58
|
+
};
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
// oxlint-disable-next-line typescript/no-floating-promises -- The picker owns this request; a late result only updates its rows.
|
|
61
|
+
void loadLocalDeltas(props.rev).then(setDeltas);
|
|
62
|
+
}, [props.rev]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!open) return;
|
|
66
|
+
const close = (event: globalThis.PointerEvent) => {
|
|
67
|
+
const target = event.target as Node;
|
|
68
|
+
if (popover.current?.contains(target) || button.current?.contains(target))
|
|
69
|
+
return;
|
|
70
|
+
setOpen(false);
|
|
71
|
+
};
|
|
72
|
+
const escape = (event: KeyboardEvent) => {
|
|
73
|
+
if (event.key !== "Escape") return;
|
|
74
|
+
setOpen(false);
|
|
75
|
+
button.current?.focus();
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener("pointerdown", close);
|
|
78
|
+
document.addEventListener("keydown", escape);
|
|
79
|
+
return () => {
|
|
80
|
+
document.removeEventListener("pointerdown", close);
|
|
81
|
+
document.removeEventListener("keydown", escape);
|
|
82
|
+
};
|
|
83
|
+
}, [open]);
|
|
84
|
+
|
|
85
|
+
const list = deltas instanceof Error ? undefined : deltas;
|
|
86
|
+
const picked = list === undefined ? undefined : revWindow(list, props.rev);
|
|
87
|
+
const shown = dragging ?? picked;
|
|
88
|
+
const label =
|
|
89
|
+
list !== undefined && shown !== undefined
|
|
90
|
+
? windowLabel(list, shown)
|
|
91
|
+
: shortRev(props.rev);
|
|
92
|
+
|
|
93
|
+
// Open on the picked rows, even when they sit far down the list.
|
|
94
|
+
const pickedNewest = picked?.newest;
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!open || pickedNewest === undefined) return;
|
|
97
|
+
rows.current?.children[pickedNewest]?.scrollIntoView({ block: "center" });
|
|
98
|
+
}, [open, pickedNewest]);
|
|
99
|
+
|
|
100
|
+
const apply = (window: DeltaWindow) => {
|
|
101
|
+
setDragging(undefined);
|
|
102
|
+
if (list === undefined) return;
|
|
103
|
+
props.onChange(windowRev(list, window));
|
|
104
|
+
};
|
|
105
|
+
// Row boundary under the pointer: 0 is above the first row, N below the last.
|
|
106
|
+
const boundaryAt = (event: { clientY: number }) =>
|
|
107
|
+
Math.round(
|
|
108
|
+
(event.clientY - rows.current!.getBoundingClientRect().top) / ROW,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const dragHandle = (event: PointerEvent, end: "newest" | "oldest") => {
|
|
112
|
+
if (list === undefined || shown === undefined) return;
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
115
|
+
const start = shown;
|
|
116
|
+
let next = start;
|
|
117
|
+
const target = event.currentTarget as HTMLElement;
|
|
118
|
+
const move = (moveEvent: globalThis.PointerEvent) => {
|
|
119
|
+
const boundary = boundaryAt(moveEvent);
|
|
120
|
+
next =
|
|
121
|
+
end === "newest"
|
|
122
|
+
? { ...start, newest: Math.max(0, Math.min(boundary, start.oldest)) }
|
|
123
|
+
: {
|
|
124
|
+
...start,
|
|
125
|
+
oldest: Math.min(
|
|
126
|
+
list.length - 1,
|
|
127
|
+
Math.max(boundary - 1, start.newest),
|
|
128
|
+
),
|
|
129
|
+
};
|
|
130
|
+
setDragging(next);
|
|
131
|
+
};
|
|
132
|
+
const up = () => {
|
|
133
|
+
target.removeEventListener("pointermove", move);
|
|
134
|
+
target.removeEventListener("pointerup", up);
|
|
135
|
+
target.removeEventListener("pointercancel", up);
|
|
136
|
+
apply(next);
|
|
137
|
+
};
|
|
138
|
+
target.addEventListener("pointermove", move);
|
|
139
|
+
target.addEventListener("pointerup", up);
|
|
140
|
+
target.addEventListener("pointercancel", up);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const dragRows = (event: PointerEvent, index: number) => {
|
|
144
|
+
if (list === undefined || event.button !== 0) return;
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
const container = rows.current!;
|
|
147
|
+
container.setPointerCapture(event.pointerId);
|
|
148
|
+
let next = { newest: index, oldest: index };
|
|
149
|
+
setDragging(next);
|
|
150
|
+
const move = (moveEvent: globalThis.PointerEvent) => {
|
|
151
|
+
const over = Math.max(
|
|
152
|
+
0,
|
|
153
|
+
Math.min(
|
|
154
|
+
list.length - 1,
|
|
155
|
+
Math.floor(
|
|
156
|
+
(moveEvent.clientY - container.getBoundingClientRect().top) / ROW,
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
next = {
|
|
161
|
+
newest: Math.min(index, over),
|
|
162
|
+
oldest: Math.max(index, over),
|
|
163
|
+
};
|
|
164
|
+
setDragging(next);
|
|
165
|
+
};
|
|
166
|
+
const up = () => {
|
|
167
|
+
container.removeEventListener("pointermove", move);
|
|
168
|
+
container.removeEventListener("pointerup", up);
|
|
169
|
+
container.removeEventListener("pointercancel", up);
|
|
170
|
+
apply(next);
|
|
171
|
+
};
|
|
172
|
+
container.addEventListener("pointermove", move);
|
|
173
|
+
container.addEventListener("pointerup", up);
|
|
174
|
+
container.addEventListener("pointercancel", up);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const nudge = (end: "newest" | "oldest", by: number) => {
|
|
178
|
+
if (list === undefined || shown === undefined) return;
|
|
179
|
+
apply(
|
|
180
|
+
end === "newest"
|
|
181
|
+
? {
|
|
182
|
+
...shown,
|
|
183
|
+
newest: Math.max(0, Math.min(shown.oldest, shown.newest + by)),
|
|
184
|
+
}
|
|
185
|
+
: {
|
|
186
|
+
...shown,
|
|
187
|
+
oldest: Math.min(
|
|
188
|
+
list.length - 1,
|
|
189
|
+
Math.max(shown.newest, shown.oldest + by),
|
|
190
|
+
),
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<>
|
|
197
|
+
<Button
|
|
198
|
+
ref={button}
|
|
199
|
+
className={trigger}
|
|
200
|
+
variant="quiet"
|
|
201
|
+
aria-haspopup="dialog"
|
|
202
|
+
aria-expanded={open}
|
|
203
|
+
onClick={() => {
|
|
204
|
+
const rect = button.current!.getBoundingClientRect();
|
|
205
|
+
const icon = button
|
|
206
|
+
.current!.querySelector("svg")!
|
|
207
|
+
.getBoundingClientRect();
|
|
208
|
+
// Line the menu's icons and rail up under the trigger's icon.
|
|
209
|
+
setAnchor({ top: rect.bottom + 4, left: icon.left - INSET });
|
|
210
|
+
if (!open) refresh();
|
|
211
|
+
setOpen(!open);
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<GitBranch size="sm" />
|
|
215
|
+
<span>{label}</span>
|
|
216
|
+
<ChevronDown size="sm" />
|
|
217
|
+
</Button>
|
|
218
|
+
{open &&
|
|
219
|
+
createPortal(
|
|
220
|
+
<div
|
|
221
|
+
ref={popover}
|
|
222
|
+
className={float}
|
|
223
|
+
role="dialog"
|
|
224
|
+
aria-label="Pick changes"
|
|
225
|
+
style={anchor}
|
|
226
|
+
>
|
|
227
|
+
{deltas instanceof Error ? (
|
|
228
|
+
<div className={foot}>{deltas.message}</div>
|
|
229
|
+
) : list === undefined ? (
|
|
230
|
+
<div className={foot}>Loading changes…</div>
|
|
231
|
+
) : (
|
|
232
|
+
<>
|
|
233
|
+
<button
|
|
234
|
+
type="button"
|
|
235
|
+
className={item}
|
|
236
|
+
data-selected={
|
|
237
|
+
shown?.newest === 0 && shown.oldest === list.length - 1
|
|
238
|
+
}
|
|
239
|
+
onClick={() => apply({ newest: 0, oldest: list.length - 1 })}
|
|
240
|
+
>
|
|
241
|
+
<GitBranch size="sm" />
|
|
242
|
+
<span>All commits</span>
|
|
243
|
+
{shown?.newest === 0 && shown.oldest === list.length - 1 && (
|
|
244
|
+
<Check size="sm" />
|
|
245
|
+
)}
|
|
246
|
+
</button>
|
|
247
|
+
<div className="scroll">
|
|
248
|
+
<div ref={rows} className="rows">
|
|
249
|
+
{list.map((delta, index) => {
|
|
250
|
+
const inside =
|
|
251
|
+
shown !== undefined &&
|
|
252
|
+
index >= shown.newest &&
|
|
253
|
+
index <= shown.oldest;
|
|
254
|
+
return (
|
|
255
|
+
<button
|
|
256
|
+
key={delta.kind === "commit" ? delta.sha : delta.kind}
|
|
257
|
+
type="button"
|
|
258
|
+
className={row}
|
|
259
|
+
data-inside={inside}
|
|
260
|
+
data-kind={delta.kind}
|
|
261
|
+
data-first={index === 0}
|
|
262
|
+
data-last={index === list.length - 1}
|
|
263
|
+
title={
|
|
264
|
+
delta.kind === "commit"
|
|
265
|
+
? `${delta.sha.slice(0, 7)} ${delta.subject}`
|
|
266
|
+
: undefined
|
|
267
|
+
}
|
|
268
|
+
onPointerDown={(event) => dragRows(event, index)}
|
|
269
|
+
onKeyDown={(event) => {
|
|
270
|
+
if (event.key !== "Enter" && event.key !== " ")
|
|
271
|
+
return;
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
apply({ newest: index, oldest: index });
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<span className="rail" aria-hidden="true">
|
|
277
|
+
<span className="dot" />
|
|
278
|
+
</span>
|
|
279
|
+
{delta.kind === "commit" ? (
|
|
280
|
+
<span className="label">
|
|
281
|
+
<span className="sha">
|
|
282
|
+
{delta.sha.slice(0, 7)}
|
|
283
|
+
</span>
|
|
284
|
+
<span className="subject">{delta.subject}</span>
|
|
285
|
+
</span>
|
|
286
|
+
) : (
|
|
287
|
+
<span className="label">
|
|
288
|
+
{delta.kind === "staged" ? "Staged" : "Unstaged"}
|
|
289
|
+
</span>
|
|
290
|
+
)}
|
|
291
|
+
<span className="stat">
|
|
292
|
+
{delta.additions > 0 && (
|
|
293
|
+
<span className="added">+{delta.additions}</span>
|
|
294
|
+
)}
|
|
295
|
+
{delta.deletions > 0 && (
|
|
296
|
+
<span className="removed">
|
|
297
|
+
−{delta.deletions}
|
|
298
|
+
</span>
|
|
299
|
+
)}
|
|
300
|
+
</span>
|
|
301
|
+
</button>
|
|
302
|
+
);
|
|
303
|
+
})}
|
|
304
|
+
{shown !== undefined && (
|
|
305
|
+
<>
|
|
306
|
+
<span
|
|
307
|
+
className="window"
|
|
308
|
+
aria-hidden="true"
|
|
309
|
+
style={{
|
|
310
|
+
top: shown.newest * ROW,
|
|
311
|
+
height: (shown.oldest - shown.newest + 1) * ROW,
|
|
312
|
+
}}
|
|
313
|
+
/>
|
|
314
|
+
<button
|
|
315
|
+
type="button"
|
|
316
|
+
className={handle}
|
|
317
|
+
style={{ top: shown.newest * ROW }}
|
|
318
|
+
aria-label="Newest change in the range"
|
|
319
|
+
onPointerDown={(event) => dragHandle(event, "newest")}
|
|
320
|
+
onKeyDown={(event) => {
|
|
321
|
+
const by = { ArrowUp: -1, ArrowDown: 1 }[event.key];
|
|
322
|
+
if (by === undefined) return;
|
|
323
|
+
event.preventDefault();
|
|
324
|
+
nudge("newest", by);
|
|
325
|
+
}}
|
|
326
|
+
/>
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
className={handle}
|
|
330
|
+
style={{ top: (shown.oldest + 1) * ROW }}
|
|
331
|
+
aria-label="Oldest change in the range"
|
|
332
|
+
onPointerDown={(event) => dragHandle(event, "oldest")}
|
|
333
|
+
onKeyDown={(event) => {
|
|
334
|
+
const by = { ArrowUp: -1, ArrowDown: 1 }[event.key];
|
|
335
|
+
if (by === undefined) return;
|
|
336
|
+
event.preventDefault();
|
|
337
|
+
nudge("oldest", by);
|
|
338
|
+
}}
|
|
339
|
+
/>
|
|
340
|
+
</>
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div className={foot}>
|
|
345
|
+
Click a change, or drag the handles for a range.
|
|
346
|
+
</div>
|
|
347
|
+
</>
|
|
348
|
+
)}
|
|
349
|
+
</div>,
|
|
350
|
+
document.body,
|
|
351
|
+
)}
|
|
352
|
+
</>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Maui menu items' inline padding (spacing 4), before each row's icon or rail dot. */
|
|
357
|
+
const INSET = 9;
|
|
358
|
+
/** The rail column is as wide as a small icon, so dots sit under icons. */
|
|
359
|
+
const RAIL = 16;
|
|
360
|
+
|
|
361
|
+
const styles = {
|
|
362
|
+
trigger: style(text({ size: "md", fontWeight: 400, color: "highContrast" }), {
|
|
363
|
+
gap: "8px",
|
|
364
|
+
minWidth: 0,
|
|
365
|
+
"& > span": {
|
|
366
|
+
overflow: "hidden",
|
|
367
|
+
textOverflow: "ellipsis",
|
|
368
|
+
whiteSpace: "nowrap",
|
|
369
|
+
},
|
|
370
|
+
}),
|
|
371
|
+
// Maui's menu popover: element surface, small radius, strong shadow, rows edge to edge.
|
|
372
|
+
float: style(
|
|
373
|
+
radius.sm,
|
|
374
|
+
shadow.strong,
|
|
375
|
+
spacing.padding({ y: 1 }),
|
|
376
|
+
text({ size: "sm", color: "highContrast" }),
|
|
377
|
+
{
|
|
378
|
+
position: "fixed",
|
|
379
|
+
zIndex: 50,
|
|
380
|
+
width: "400px",
|
|
381
|
+
maxWidth: "calc(100vw - 32px)",
|
|
382
|
+
backgroundColor: backgroundColor.element,
|
|
383
|
+
"& .scroll": {
|
|
384
|
+
maxHeight: "min(60vh, 520px)",
|
|
385
|
+
overflowY: "auto",
|
|
386
|
+
// Room for the handles that sit on the first and last boundaries.
|
|
387
|
+
paddingBlock: "6px",
|
|
388
|
+
borderBlock: `1px solid ${borderColor.border}`,
|
|
389
|
+
marginBlock: "2px",
|
|
390
|
+
},
|
|
391
|
+
"& .rows": { position: "relative", touchAction: "none" },
|
|
392
|
+
// The picked window runs down the rail, between the two handles.
|
|
393
|
+
"& .window": {
|
|
394
|
+
position: "absolute",
|
|
395
|
+
left: `${String(INSET + RAIL / 2 - 1)}px`,
|
|
396
|
+
width: "2px",
|
|
397
|
+
borderRadius: "1px",
|
|
398
|
+
backgroundColor: colors.accent[9],
|
|
399
|
+
pointerEvents: "none",
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
),
|
|
403
|
+
item: style(
|
|
404
|
+
{
|
|
405
|
+
paddingInline: `${String(INSET)}px`,
|
|
406
|
+
display: "flex",
|
|
407
|
+
alignItems: "center",
|
|
408
|
+
gap: "8px",
|
|
409
|
+
width: "100%",
|
|
410
|
+
height: `${String(ROW)}px`,
|
|
411
|
+
border: 0,
|
|
412
|
+
background: "transparent",
|
|
413
|
+
font: "inherit",
|
|
414
|
+
color: "inherit",
|
|
415
|
+
textAlign: "left",
|
|
416
|
+
cursor: "default",
|
|
417
|
+
"& > span": { flex: "1 1 auto" },
|
|
418
|
+
"& > svg": { flexShrink: 0 },
|
|
419
|
+
"&:hover": { backgroundColor: backgroundColor.elementHover },
|
|
420
|
+
// Maui's selected menu item: accent text, medium weight, a check.
|
|
421
|
+
"&[data-selected='true']": { color: colors.accent[9], fontWeight: 500 },
|
|
422
|
+
},
|
|
423
|
+
focusRing(),
|
|
424
|
+
),
|
|
425
|
+
row: style(
|
|
426
|
+
{
|
|
427
|
+
display: "flex",
|
|
428
|
+
alignItems: "center",
|
|
429
|
+
gap: "8px",
|
|
430
|
+
width: "100%",
|
|
431
|
+
height: `${String(ROW)}px`,
|
|
432
|
+
paddingInline: `${String(INSET)}px`,
|
|
433
|
+
border: 0,
|
|
434
|
+
background: "transparent",
|
|
435
|
+
font: "inherit",
|
|
436
|
+
color: "inherit",
|
|
437
|
+
textAlign: "left",
|
|
438
|
+
cursor: "default",
|
|
439
|
+
userSelect: "none",
|
|
440
|
+
"&:hover": { backgroundColor: backgroundColor.elementHover },
|
|
441
|
+
"&[data-inside='true']": { color: colors.accent[9], fontWeight: 500 },
|
|
442
|
+
"& .rail": {
|
|
443
|
+
position: "relative",
|
|
444
|
+
alignSelf: "stretch",
|
|
445
|
+
width: `${String(RAIL)}px`,
|
|
446
|
+
flexShrink: 0,
|
|
447
|
+
},
|
|
448
|
+
// The rail runs through the center of every row.
|
|
449
|
+
"& .rail::before": {
|
|
450
|
+
content: "''",
|
|
451
|
+
position: "absolute",
|
|
452
|
+
left: "calc(50% - 0.5px)",
|
|
453
|
+
top: 0,
|
|
454
|
+
bottom: 0,
|
|
455
|
+
borderLeft: `1px solid ${colors.gray[6]}`,
|
|
456
|
+
},
|
|
457
|
+
"&[data-first='true'] .rail::before": { top: "50%" },
|
|
458
|
+
"&[data-last='true'] .rail::before": { bottom: "50%" },
|
|
459
|
+
"& .dot": {
|
|
460
|
+
position: "absolute",
|
|
461
|
+
left: "50%",
|
|
462
|
+
top: "50%",
|
|
463
|
+
width: "7px",
|
|
464
|
+
height: "7px",
|
|
465
|
+
margin: "-3.5px 0 0 -3.5px",
|
|
466
|
+
borderRadius: "50%",
|
|
467
|
+
backgroundColor: colors.gray[9],
|
|
468
|
+
},
|
|
469
|
+
"&[data-kind='staged'] .dot, &[data-kind='unstaged'] .dot": {
|
|
470
|
+
backgroundColor: backgroundColor.app,
|
|
471
|
+
boxShadow: `inset 0 0 0 1.5px ${colors.gray[9]}`,
|
|
472
|
+
},
|
|
473
|
+
"&[data-inside='true'] .dot": {
|
|
474
|
+
backgroundColor: colors.accent[9],
|
|
475
|
+
boxShadow: "none",
|
|
476
|
+
},
|
|
477
|
+
"& .label": {
|
|
478
|
+
display: "flex",
|
|
479
|
+
gap: "8px",
|
|
480
|
+
flex: "1 1 auto",
|
|
481
|
+
minWidth: 0,
|
|
482
|
+
},
|
|
483
|
+
"& .sha": { fontFamily: codeFontFamily, flexShrink: 0 },
|
|
484
|
+
"& .subject": {
|
|
485
|
+
overflow: "hidden",
|
|
486
|
+
textOverflow: "ellipsis",
|
|
487
|
+
whiteSpace: "nowrap",
|
|
488
|
+
},
|
|
489
|
+
"& .stat": {
|
|
490
|
+
display: "flex",
|
|
491
|
+
gap: "6px",
|
|
492
|
+
fontWeight: 400,
|
|
493
|
+
flexShrink: 0,
|
|
494
|
+
fontFamily: codeFontFamily,
|
|
495
|
+
fontSize: "12px",
|
|
496
|
+
fontVariantNumeric: "tabular-nums",
|
|
497
|
+
},
|
|
498
|
+
"& .added": { color: colors.green[11] },
|
|
499
|
+
"& .removed": { color: colors.red[11] },
|
|
500
|
+
},
|
|
501
|
+
focusRing(),
|
|
502
|
+
),
|
|
503
|
+
// Sits on the boundary between two rows, centered on the rail.
|
|
504
|
+
handle: style(
|
|
505
|
+
{
|
|
506
|
+
position: "absolute",
|
|
507
|
+
left: `${String(INSET + RAIL / 2 - 8)}px`,
|
|
508
|
+
width: "16px",
|
|
509
|
+
height: "8px",
|
|
510
|
+
marginTop: "-4px",
|
|
511
|
+
padding: 0,
|
|
512
|
+
border: 0,
|
|
513
|
+
borderRadius: "4px",
|
|
514
|
+
backgroundColor: backgroundColor.app,
|
|
515
|
+
boxShadow: `inset 0 0 0 2px ${colors.accent[9]}`,
|
|
516
|
+
cursor: "ns-resize",
|
|
517
|
+
touchAction: "none",
|
|
518
|
+
zIndex: 1,
|
|
519
|
+
},
|
|
520
|
+
focusRing(undefined, `inset 0 0 0 2px ${colors.accent[9]}`),
|
|
521
|
+
),
|
|
522
|
+
foot: style(
|
|
523
|
+
spacing.padding({ x: 4, y: 1 }),
|
|
524
|
+
text({ size: "xs", color: "lowContrast" }),
|
|
525
|
+
),
|
|
526
|
+
};
|