@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
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { useEffect, useMemo, useRef, useState, type MouseEvent } from "react";
|
|
2
2
|
import { CodeView, type CodeViewHandle } from "@pierre/diffs/react";
|
|
3
|
-
import type {
|
|
4
|
-
CodeViewDiffItem,
|
|
5
|
-
CodeViewOptions,
|
|
6
|
-
CodeViewLineSelection,
|
|
7
|
-
} from "@pierre/diffs";
|
|
3
|
+
import type { CodeViewOptions, CodeViewLineSelection } from "@pierre/diffs";
|
|
8
4
|
import {
|
|
9
5
|
backgroundColor,
|
|
10
6
|
border,
|
|
11
7
|
borderColor,
|
|
8
|
+
colors,
|
|
12
9
|
Button,
|
|
10
|
+
ChevronDown,
|
|
13
11
|
flex,
|
|
14
12
|
spacing,
|
|
15
13
|
text,
|
|
@@ -21,20 +19,17 @@ import {
|
|
|
21
19
|
type SourceAnnotation,
|
|
22
20
|
type SourceReference,
|
|
23
21
|
} from "../annotations.js";
|
|
22
|
+
import { changedFileItem, type Rev } from "../changes.js";
|
|
24
23
|
import type { SourceDefinition } from "../definitions.js";
|
|
25
24
|
import { useGistPin } from "../gist/pin.ts";
|
|
26
25
|
import { useGitHubPin } from "../github/pin.ts";
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} from "./sourceNavigation.js";
|
|
26
|
+
import { useHostFiles } from "../host/files.ts";
|
|
27
|
+
import { ChangesSummary } from "./ChangesCard.tsx";
|
|
28
|
+
import { useChanges } from "./changesSource.ts";
|
|
29
|
+
import { RangePicker } from "./RangePicker.tsx";
|
|
30
|
+
import { requestSource, useSourceReference } from "./sourceNavigation.js";
|
|
32
31
|
import { pierreDiffOptions, sourceSelectionCss } from "./pierre.js";
|
|
33
|
-
import {
|
|
34
|
-
sourcePanelOrigin,
|
|
35
|
-
sourcePanelPath,
|
|
36
|
-
sourcePanelPin,
|
|
37
|
-
} from "./sourceHeader.ts";
|
|
32
|
+
import { sourcePanelPin } from "./sourceHeader.ts";
|
|
38
33
|
import { useViewerMode } from "./viewerMode.ts";
|
|
39
34
|
|
|
40
35
|
export type SourceSelection = {
|
|
@@ -49,7 +44,10 @@ type DefinitionNavigation = {
|
|
|
49
44
|
};
|
|
50
45
|
|
|
51
46
|
export function SourceDiffPanel(props: {
|
|
52
|
-
|
|
47
|
+
/** The changes to show; references with their own rev open that rev instead. */
|
|
48
|
+
rev: Rev;
|
|
49
|
+
/** Local viewers pick other changes with the range picker. */
|
|
50
|
+
onRevChange?: (rev: Rev) => void;
|
|
53
51
|
selection: SourceSelection | undefined;
|
|
54
52
|
onSelect: (selection: SourceSelection) => void;
|
|
55
53
|
}) {
|
|
@@ -78,86 +76,189 @@ export function SourceDiffPanel(props: {
|
|
|
78
76
|
const pinClass = useStyles(styles.pin);
|
|
79
77
|
const links = useStyles(styles.links);
|
|
80
78
|
const code = useStyles(styles.code);
|
|
79
|
+
const summary = useStyles(styles.summary);
|
|
80
|
+
const codeWrap = useStyles(styles.codeWrap);
|
|
81
|
+
const collapseIcon = useStyles(styles.collapseIcon);
|
|
81
82
|
const github = useGitHubPin();
|
|
82
83
|
const gist = useGistPin();
|
|
84
|
+
const hostFiles = useHostFiles();
|
|
83
85
|
const selection = props.selection;
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
86
|
+
const reference = selection?.reference;
|
|
87
|
+
const changes = useChanges(props.rev);
|
|
88
|
+
const items = useMemo(
|
|
87
89
|
() =>
|
|
88
|
-
|
|
89
|
-
?
|
|
90
|
-
:
|
|
91
|
-
[
|
|
90
|
+
changes === undefined || changes instanceof Error
|
|
91
|
+
? []
|
|
92
|
+
: changes.files.flatMap((changed) => changedFileItem(changed) ?? []),
|
|
93
|
+
[changes],
|
|
92
94
|
);
|
|
95
|
+
// A rev-less reference resolves against current files; a commit reference names its lines directly.
|
|
96
|
+
const source = useSourceReference(
|
|
97
|
+
reference?.kind === "file" && reference.rev === undefined
|
|
98
|
+
? reference
|
|
99
|
+
: undefined,
|
|
100
|
+
);
|
|
101
|
+
const resolvedFile = source instanceof Error ? undefined : source;
|
|
102
|
+
const inChanges =
|
|
103
|
+
resolvedFile !== undefined &&
|
|
104
|
+
items.some((item) => item.id === resolvedFile.path);
|
|
93
105
|
const file =
|
|
94
|
-
definition
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
: definition;
|
|
106
|
+
definition ??
|
|
107
|
+
(resolvedFile !== undefined && changes !== undefined && !inChanges
|
|
108
|
+
? resolvedFile
|
|
109
|
+
: undefined);
|
|
99
110
|
const status =
|
|
100
111
|
navigation.status !== undefined
|
|
101
112
|
? navigation.status
|
|
102
113
|
: source instanceof Error
|
|
103
114
|
? source.message
|
|
104
|
-
:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
: changes instanceof Error
|
|
116
|
+
? changes.message
|
|
117
|
+
: changes === undefined
|
|
118
|
+
? "Loading changes…"
|
|
119
|
+
: reference?.kind === "file" &&
|
|
120
|
+
reference.rev === undefined &&
|
|
121
|
+
source === undefined &&
|
|
122
|
+
(mode === "local" ||
|
|
123
|
+
mode === "github" ||
|
|
124
|
+
hostFiles !== undefined)
|
|
125
|
+
? "Opening source…"
|
|
126
|
+
: items.length === 0 && file === undefined
|
|
127
|
+
? "No changes."
|
|
128
|
+
: undefined;
|
|
109
129
|
const selectedLines = useMemo<CodeViewLineSelection | null>(() => {
|
|
110
130
|
if (file !== undefined)
|
|
111
131
|
return { id: file.path, range: { start: file.start, end: file.end } };
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
const focus =
|
|
133
|
+
resolvedFile ??
|
|
134
|
+
(reference?.kind === "file" && reference.start !== undefined
|
|
135
|
+
? {
|
|
136
|
+
path: reference.path,
|
|
137
|
+
start: reference.start,
|
|
138
|
+
end: reference.end ?? reference.start,
|
|
139
|
+
}
|
|
140
|
+
: undefined);
|
|
114
141
|
// oxlint-disable-next-line unicorn/no-null -- Pierre uses null for a controlled empty selection.
|
|
115
|
-
if (
|
|
142
|
+
if (focus === undefined) return null;
|
|
116
143
|
return {
|
|
117
|
-
id:
|
|
118
|
-
range: {
|
|
119
|
-
start: ref.start,
|
|
120
|
-
end: ref.end,
|
|
121
|
-
side: ref.side === "old" ? "deletions" : "additions",
|
|
122
|
-
},
|
|
144
|
+
id: focus.path,
|
|
145
|
+
range: { start: focus.start, end: focus.end, side: "additions" },
|
|
123
146
|
};
|
|
124
|
-
}, [
|
|
147
|
+
}, [reference, file, resolvedFile]);
|
|
148
|
+
const focusedPath =
|
|
149
|
+
file?.path ?? (reference?.kind === "file" ? reference.path : undefined);
|
|
150
|
+
// Collapsed files reset when the rev or the selected reference changes, so a reference always shows its file.
|
|
151
|
+
const [collapse, setCollapse] = useState<{
|
|
152
|
+
rev: Rev;
|
|
153
|
+
selection: SourceSelection | undefined;
|
|
154
|
+
paths: ReadonlySet<string>;
|
|
155
|
+
}>({ rev: props.rev, selection, paths: new Set() });
|
|
156
|
+
if (collapse.rev !== props.rev || collapse.selection !== selection) {
|
|
157
|
+
setCollapse({ rev: props.rev, selection, paths: new Set() });
|
|
158
|
+
}
|
|
159
|
+
const rangeId = selectedLines?.id;
|
|
160
|
+
const rangeStart = selectedLines?.range.start;
|
|
161
|
+
const rangeEnd = selectedLines?.range.end;
|
|
162
|
+
const rangeSide =
|
|
163
|
+
selectedLines !== null && "side" in selectedLines.range
|
|
164
|
+
? selectedLines.range.side
|
|
165
|
+
: undefined;
|
|
166
|
+
const shownItems = useMemo(
|
|
167
|
+
() =>
|
|
168
|
+
items.map((item) => {
|
|
169
|
+
// The referenced file shows enough unchanged context to reveal its range.
|
|
170
|
+
const changed =
|
|
171
|
+
item.id === rangeId &&
|
|
172
|
+
rangeSide === "additions" &&
|
|
173
|
+
changes !== undefined &&
|
|
174
|
+
!(changes instanceof Error)
|
|
175
|
+
? changes.files.find((entry) => entry.path === item.id)
|
|
176
|
+
: undefined;
|
|
177
|
+
const shown =
|
|
178
|
+
changed === undefined
|
|
179
|
+
? item
|
|
180
|
+
: (changedFileItem(changed, {
|
|
181
|
+
start: rangeStart!,
|
|
182
|
+
end: rangeEnd!,
|
|
183
|
+
}) ?? item);
|
|
184
|
+
const collapsed = collapse.paths.has(item.id);
|
|
185
|
+
// Pierre only rereads an item when its version changes.
|
|
186
|
+
return {
|
|
187
|
+
...shown,
|
|
188
|
+
collapsed,
|
|
189
|
+
version: (shown.version ?? 0) * 2 + (collapsed ? 1 : 0),
|
|
190
|
+
};
|
|
191
|
+
}),
|
|
192
|
+
[items, changes, collapse.paths, rangeId, rangeStart, rangeEnd, rangeSide],
|
|
193
|
+
);
|
|
194
|
+
const toggleCollapsed = (path: string) => {
|
|
195
|
+
const paths = new Set(collapse.paths);
|
|
196
|
+
if (!paths.delete(path)) paths.add(path);
|
|
197
|
+
setCollapse({ ...collapse, paths });
|
|
198
|
+
};
|
|
199
|
+
// File headers live in Pierre's shadow DOM; a click inside one names its file through the prefix slot.
|
|
200
|
+
const onHeaderClick = (event: MouseEvent) => {
|
|
201
|
+
if (file !== undefined) return;
|
|
202
|
+
const fileHeader = event.nativeEvent
|
|
203
|
+
.composedPath()
|
|
204
|
+
.find(
|
|
205
|
+
(target): target is Element =>
|
|
206
|
+
target instanceof Element && target.hasAttribute("data-diffs-header"),
|
|
207
|
+
);
|
|
208
|
+
const root = fileHeader?.getRootNode();
|
|
209
|
+
if (!(root instanceof ShadowRoot)) return;
|
|
210
|
+
const path = root.host
|
|
211
|
+
.querySelector("[data-collapse-path]")
|
|
212
|
+
?.getAttribute("data-collapse-path");
|
|
213
|
+
if (path !== null && path !== undefined) toggleCollapsed(path);
|
|
214
|
+
};
|
|
125
215
|
|
|
126
|
-
const openPath = sourcePanelPath({
|
|
127
|
-
filePath: file?.path,
|
|
128
|
-
reference: selection?.reference,
|
|
129
|
-
items: props.items,
|
|
130
|
-
linkedDiffId: linkedDiff?.id,
|
|
131
|
-
});
|
|
132
|
-
const origin = sourcePanelOrigin({
|
|
133
|
-
showingFile: file !== undefined,
|
|
134
|
-
reference: selection?.reference,
|
|
135
|
-
items: props.items,
|
|
136
|
-
linkedDiffId: linkedDiff?.id,
|
|
137
|
-
});
|
|
138
216
|
const pin =
|
|
139
|
-
|
|
217
|
+
file === undefined
|
|
140
218
|
? { kind: "none" as const }
|
|
141
|
-
: sourcePanelPin({
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
github,
|
|
145
|
-
gist,
|
|
146
|
-
});
|
|
219
|
+
: sourcePanelPin({ path: file.path, github, gist });
|
|
220
|
+
// Diffs keep Pierre's sticky per-file headers; a single file shows its path in the panel title.
|
|
221
|
+
const showTitle = file !== undefined;
|
|
147
222
|
const options = pierreDiffOptions({
|
|
148
223
|
themeType: resolvedTheme,
|
|
149
|
-
disableFileHeader:
|
|
224
|
+
disableFileHeader: showTitle,
|
|
150
225
|
});
|
|
226
|
+
const loadedChanges =
|
|
227
|
+
changes === undefined || changes instanceof Error ? undefined : changes;
|
|
228
|
+
|
|
229
|
+
// Scroll again once the changes arrive, since Pierre cannot scroll to items it does not have yet.
|
|
230
|
+
const ready = file !== undefined || loadedChanges !== undefined;
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
if (!ready || focusedPath === undefined || rangeId !== undefined) return;
|
|
233
|
+
viewer.current?.scrollTo({
|
|
234
|
+
type: "item",
|
|
235
|
+
id: focusedPath,
|
|
236
|
+
align: "start",
|
|
237
|
+
behavior: "instant",
|
|
238
|
+
});
|
|
239
|
+
}, [ready, focusedPath, rangeId]);
|
|
151
240
|
|
|
152
241
|
useEffect(() => {
|
|
153
|
-
if (
|
|
242
|
+
if (
|
|
243
|
+
!ready ||
|
|
244
|
+
rangeId === undefined ||
|
|
245
|
+
rangeStart === undefined ||
|
|
246
|
+
rangeEnd === undefined
|
|
247
|
+
) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
154
250
|
viewer.current?.scrollTo({
|
|
155
251
|
type: "range",
|
|
156
|
-
|
|
252
|
+
id: rangeId,
|
|
253
|
+
range: {
|
|
254
|
+
start: rangeStart,
|
|
255
|
+
end: rangeEnd,
|
|
256
|
+
...(rangeSide === undefined ? {} : { side: rangeSide }),
|
|
257
|
+
},
|
|
157
258
|
align: "center",
|
|
158
259
|
behavior: "instant",
|
|
159
260
|
});
|
|
160
|
-
}, [
|
|
261
|
+
}, [ready, rangeId, rangeStart, rangeEnd, rangeSide]);
|
|
161
262
|
|
|
162
263
|
const onTokenClick: CodeViewOptions<undefined, undefined>["onTokenClick"] = (
|
|
163
264
|
token,
|
|
@@ -214,11 +315,43 @@ export function SourceDiffPanel(props: {
|
|
|
214
315
|
|
|
215
316
|
return (
|
|
216
317
|
<aside id="source-diff-panel" aria-label="Source changes" className={panel}>
|
|
217
|
-
<div
|
|
218
|
-
{
|
|
318
|
+
<div
|
|
319
|
+
className={header}
|
|
320
|
+
data-diffmap-kind="source-header"
|
|
321
|
+
// Pierre's file headers draw their own top border, which divides them from this header.
|
|
322
|
+
data-file-headers={!showTitle && items.length > 0}
|
|
323
|
+
>
|
|
324
|
+
{!showTitle && props.onRevChange !== undefined && (
|
|
219
325
|
<div className={titleRow}>
|
|
220
|
-
<
|
|
221
|
-
|
|
326
|
+
<RangePicker rev={props.rev} onChange={props.onRevChange} />
|
|
327
|
+
{loadedChanges !== undefined && (
|
|
328
|
+
<ChangesSummary className={summary} changes={loadedChanges} />
|
|
329
|
+
)}
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
{!showTitle &&
|
|
333
|
+
props.onRevChange === undefined &&
|
|
334
|
+
loadedChanges !== undefined && (
|
|
335
|
+
<div className={titleRow}>
|
|
336
|
+
{loadedChanges.url === undefined ? (
|
|
337
|
+
<div className={titleClass} title={loadedChanges.title}>
|
|
338
|
+
{loadedChanges.title}
|
|
339
|
+
</div>
|
|
340
|
+
) : (
|
|
341
|
+
<a
|
|
342
|
+
className={titleClass}
|
|
343
|
+
href={loadedChanges.url}
|
|
344
|
+
title={loadedChanges.title}
|
|
345
|
+
>
|
|
346
|
+
{loadedChanges.title}
|
|
347
|
+
</a>
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
{showTitle && (
|
|
352
|
+
<div className={titleRow}>
|
|
353
|
+
<div className={titleClass} title={file.path}>
|
|
354
|
+
{file.path}
|
|
222
355
|
</div>
|
|
223
356
|
{definition !== undefined && (
|
|
224
357
|
<Button
|
|
@@ -257,82 +390,86 @@ export function SourceDiffPanel(props: {
|
|
|
257
390
|
{pin.label}
|
|
258
391
|
</a>
|
|
259
392
|
)}
|
|
260
|
-
{pin.kind === "spec" && (
|
|
261
|
-
<span className={pinClass} data-diffmap-pin="spec">
|
|
262
|
-
{pin.label}
|
|
263
|
-
</span>
|
|
264
|
-
)}
|
|
265
|
-
{mode === "local" && (
|
|
266
|
-
<span>⌘-click or Ctrl-click a symbol to go to its definition.</span>
|
|
267
|
-
)}
|
|
268
393
|
{status !== undefined && <span role="status">{status}</span>}
|
|
269
|
-
{selection
|
|
270
|
-
<span>
|
|
271
|
-
Select a linked stack row or diagram element to open its source.
|
|
272
|
-
</span>
|
|
273
|
-
) : (
|
|
394
|
+
{selection !== undefined &&
|
|
274
395
|
selection.annotation.references.length > 1 && (
|
|
275
396
|
<div className={links} aria-label="Linked changes">
|
|
276
|
-
{selection.annotation.references.map((
|
|
397
|
+
{selection.annotation.references.map((option, index) => (
|
|
277
398
|
<Button
|
|
278
399
|
key={index}
|
|
279
400
|
variantColor="blue"
|
|
280
|
-
variant={
|
|
281
|
-
reference === selection.reference ? "primary" : "quiet"
|
|
282
|
-
}
|
|
401
|
+
variant={option === selection.reference ? "primary" : "quiet"}
|
|
283
402
|
onClick={() =>
|
|
284
403
|
props.onSelect({
|
|
285
404
|
annotation: selection.annotation,
|
|
286
|
-
reference,
|
|
405
|
+
reference: option,
|
|
287
406
|
})
|
|
288
407
|
}
|
|
289
408
|
>
|
|
290
|
-
{referenceLabel(
|
|
409
|
+
{referenceLabel(option)}
|
|
291
410
|
</Button>
|
|
292
411
|
))}
|
|
293
412
|
</div>
|
|
294
|
-
)
|
|
295
|
-
)}
|
|
413
|
+
)}
|
|
296
414
|
</div>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
415
|
+
{/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- Pierre's file headers are the click targets; this only catches their clicks. */}
|
|
416
|
+
<div className={codeWrap} onClick={onHeaderClick}>
|
|
417
|
+
<CodeView
|
|
418
|
+
ref={viewer}
|
|
419
|
+
className={code}
|
|
420
|
+
items={
|
|
421
|
+
file === undefined
|
|
422
|
+
? shownItems
|
|
423
|
+
: [
|
|
424
|
+
{
|
|
425
|
+
type: "file",
|
|
426
|
+
id: file.path,
|
|
427
|
+
file: {
|
|
428
|
+
name: file.path,
|
|
429
|
+
contents: file.contents,
|
|
430
|
+
},
|
|
310
431
|
},
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
432
|
+
]
|
|
433
|
+
}
|
|
434
|
+
selectedLines={selectedLines}
|
|
435
|
+
disableWorkerPool
|
|
436
|
+
options={{
|
|
437
|
+
...options,
|
|
438
|
+
unsafeCSS:
|
|
439
|
+
options.unsafeCSS + sourceSelectionCss + panelBackgroundCss,
|
|
440
|
+
// Files stack flush under the header, with no gaps between them.
|
|
441
|
+
layout: { paddingTop: 0, paddingBottom: 0, gap: 0 },
|
|
442
|
+
stickyHeaders: true,
|
|
443
|
+
onTokenClick,
|
|
444
|
+
}}
|
|
445
|
+
renderHeaderPrefix={(item) => (
|
|
446
|
+
<span
|
|
447
|
+
className={collapseIcon}
|
|
448
|
+
data-collapse-path={item.id}
|
|
449
|
+
data-collapsed={collapse.paths.has(item.id)}
|
|
450
|
+
aria-hidden="true"
|
|
451
|
+
>
|
|
452
|
+
<ChevronDown size="sm" />
|
|
453
|
+
</span>
|
|
454
|
+
)}
|
|
455
|
+
/>
|
|
456
|
+
</div>
|
|
323
457
|
</aside>
|
|
324
458
|
);
|
|
325
459
|
}
|
|
326
460
|
|
|
461
|
+
/** Pierre paints its own white; the panel's background shows through instead. */
|
|
462
|
+
const panelBackgroundCss = `:host { --diffs-light-bg: ${backgroundColor.app}; --diffs-dark-bg: ${backgroundColor.app}; }
|
|
463
|
+
[data-diffs-header] { border-top: 1px solid ${borderColor.border}; cursor: pointer; }`;
|
|
464
|
+
|
|
327
465
|
const styles = {
|
|
328
466
|
panel: style(flex({ direction: "column" }), border(["left"], "border"), {
|
|
329
|
-
|
|
467
|
+
gridArea: "diff",
|
|
330
468
|
minWidth: 0,
|
|
331
469
|
minHeight: 0,
|
|
332
470
|
overflow: "hidden",
|
|
333
471
|
backgroundColor: backgroundColor.app,
|
|
334
472
|
"@media (max-width: 900px)": {
|
|
335
|
-
gridColumn: "1",
|
|
336
473
|
height: "45vh",
|
|
337
474
|
borderLeft: "none",
|
|
338
475
|
borderTop: `1px solid ${borderColor.border}`,
|
|
@@ -340,10 +477,14 @@ const styles = {
|
|
|
340
477
|
}),
|
|
341
478
|
header: style(
|
|
342
479
|
flex({ direction: "column" }),
|
|
343
|
-
|
|
480
|
+
// Same vertical padding as the page header; the inline padding lines the
|
|
481
|
+
// picker's icon and label up with Pierre's file headers below.
|
|
482
|
+
spacing.padding({ y: 3 }),
|
|
483
|
+
{ paddingInline: "7px" },
|
|
344
484
|
border(["bottom"], "border"),
|
|
345
485
|
text({ size: "sm", color: "lowContrast" }),
|
|
346
486
|
{
|
|
487
|
+
"&[data-file-headers='true']": { borderBottom: "none" },
|
|
347
488
|
gap: "6px",
|
|
348
489
|
minWidth: 0,
|
|
349
490
|
maxHeight: "35%",
|
|
@@ -351,7 +492,9 @@ const styles = {
|
|
|
351
492
|
flexShrink: 0,
|
|
352
493
|
},
|
|
353
494
|
),
|
|
354
|
-
|
|
495
|
+
// Pierre's file headers pad 16px; this header pads 7px.
|
|
496
|
+
summary: style({ marginLeft: "auto", marginRight: "9px" }),
|
|
497
|
+
titleRow: style(flex({ direction: "row", alignItems: "center" }), {
|
|
355
498
|
gap: "6px",
|
|
356
499
|
minWidth: 0,
|
|
357
500
|
}),
|
|
@@ -373,6 +516,19 @@ const styles = {
|
|
|
373
516
|
},
|
|
374
517
|
}),
|
|
375
518
|
links: style(flex({ direction: "row" }), { gap: "6px", flexWrap: "wrap" }),
|
|
519
|
+
codeWrap: style({
|
|
520
|
+
display: "flex",
|
|
521
|
+
flexDirection: "column",
|
|
522
|
+
flex: "1 1 auto",
|
|
523
|
+
minWidth: 0,
|
|
524
|
+
minHeight: 0,
|
|
525
|
+
}),
|
|
526
|
+
collapseIcon: style({
|
|
527
|
+
display: "flex",
|
|
528
|
+
color: colors.gray[11],
|
|
529
|
+
transition: "transform 150ms ease-out",
|
|
530
|
+
"&[data-collapsed='true']": { transform: "rotate(-90deg)" },
|
|
531
|
+
}),
|
|
376
532
|
code: style({
|
|
377
533
|
flex: "1 1 auto",
|
|
378
534
|
minWidth: 0,
|