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