@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
isCommitRev,
|
|
4
|
+
type Delta,
|
|
5
|
+
type DeltasResponse,
|
|
6
|
+
type Changes,
|
|
7
|
+
type ChangesResponse,
|
|
8
|
+
type Rev,
|
|
9
|
+
} from "../changes.js";
|
|
10
|
+
import { DiffmapDefinitionError } from "../errors.js";
|
|
11
|
+
|
|
12
|
+
/** Where a viewer reads changes: the local server, GitHub, or a gist snapshot. */
|
|
13
|
+
export type ChangesLoader = (rev: Rev) => Promise<Changes | Error>;
|
|
14
|
+
|
|
15
|
+
const localCache = new Map<Rev, Promise<Changes | Error>>();
|
|
16
|
+
|
|
17
|
+
/** Commits never change, so they are cached; anything uncommitted is re-read. */
|
|
18
|
+
export const loadLocalChanges: ChangesLoader = (rev) => {
|
|
19
|
+
const cached = localCache.get(rev);
|
|
20
|
+
if (cached !== undefined) return cached;
|
|
21
|
+
const request = fetch(`/__diffmap/changes?${new URLSearchParams({ rev })}`)
|
|
22
|
+
.then((response) => response.json())
|
|
23
|
+
.then((value) => {
|
|
24
|
+
// SAFETY: the local diffmap server owns this response shape.
|
|
25
|
+
const result = value as ChangesResponse;
|
|
26
|
+
if (result.changes !== undefined) return result.changes;
|
|
27
|
+
return new DiffmapDefinitionError({
|
|
28
|
+
reason: result.error ?? "Could not load changes.",
|
|
29
|
+
});
|
|
30
|
+
})
|
|
31
|
+
.catch(
|
|
32
|
+
(cause) =>
|
|
33
|
+
new DiffmapDefinitionError({
|
|
34
|
+
reason: "Could not reach the source service.",
|
|
35
|
+
cause,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
if (isCommitRev(rev)) localCache.set(rev, request);
|
|
39
|
+
return request;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** The range picker's rows. Always re-read: uncommitted changes and HEAD move. */
|
|
43
|
+
export async function loadLocalDeltas(rev: Rev): Promise<Delta[] | Error> {
|
|
44
|
+
const result = await fetch(
|
|
45
|
+
`/__diffmap/deltas?${new URLSearchParams({ rev })}`,
|
|
46
|
+
)
|
|
47
|
+
.then((response) => response.json())
|
|
48
|
+
.then((value) => {
|
|
49
|
+
// SAFETY: the local diffmap server owns this response shape.
|
|
50
|
+
return value as DeltasResponse;
|
|
51
|
+
})
|
|
52
|
+
.catch(
|
|
53
|
+
(cause) =>
|
|
54
|
+
new DiffmapDefinitionError({
|
|
55
|
+
reason: "Could not reach the source service.",
|
|
56
|
+
cause,
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
if (result instanceof Error) return result;
|
|
60
|
+
return (
|
|
61
|
+
result.deltas ??
|
|
62
|
+
new DiffmapDefinitionError({
|
|
63
|
+
reason: result.error ?? "Could not list changes.",
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const ChangesLoaderContext =
|
|
69
|
+
createContext<ChangesLoader>(loadLocalChanges);
|
|
70
|
+
|
|
71
|
+
export function useChanges(rev: Rev) {
|
|
72
|
+
const load = useContext(ChangesLoaderContext);
|
|
73
|
+
const [loaded, setLoaded] = useState<{
|
|
74
|
+
rev: Rev;
|
|
75
|
+
load: ChangesLoader;
|
|
76
|
+
result: Changes | Error;
|
|
77
|
+
}>();
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
let active = true;
|
|
80
|
+
// oxlint-disable-next-line typescript/no-floating-promises -- The effect owns this request and ignores it after cleanup.
|
|
81
|
+
void load(rev).then((result) => {
|
|
82
|
+
if (active) setLoaded({ rev, load, result });
|
|
83
|
+
});
|
|
84
|
+
return () => {
|
|
85
|
+
active = false;
|
|
86
|
+
};
|
|
87
|
+
}, [rev, load]);
|
|
88
|
+
return loaded?.rev === rev && loaded.load === load
|
|
89
|
+
? loaded.result
|
|
90
|
+
: undefined;
|
|
91
|
+
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
sourcePanelOrigin,
|
|
6
|
-
sourcePanelPath,
|
|
7
|
-
sourcePanelPin,
|
|
8
|
-
} from "./sourceHeader.ts";
|
|
3
|
+
import { sourcePanelPin } from "./sourceHeader.ts";
|
|
9
4
|
|
|
10
5
|
const sha = "ef78bf4678d2e76a7c8bcc4087fcadf347a1fcdf";
|
|
11
6
|
const github = {
|
|
@@ -19,18 +14,9 @@ const gist = {
|
|
|
19
14
|
htmlUrl: "https://gist.github.com/a3d2fe2b2e0b1422a4fd5378baced900",
|
|
20
15
|
};
|
|
21
16
|
|
|
22
|
-
function diffItem(id: string, name: string): CodeViewDiffItem {
|
|
23
|
-
return {
|
|
24
|
-
id,
|
|
25
|
-
type: "diff",
|
|
26
|
-
fileDiff: { name } as CodeViewDiffItem["fileDiff"],
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
17
|
test("github file pin is owner/repo @ shortsha linking to the blob at the pin SHA", () => {
|
|
31
18
|
const path = "src/cli.ts";
|
|
32
19
|
const pin = sourcePanelPin({
|
|
33
|
-
origin: "file",
|
|
34
20
|
path,
|
|
35
21
|
github,
|
|
36
22
|
});
|
|
@@ -41,42 +27,8 @@ test("github file pin is owner/repo @ shortsha linking to the blob at the pin SH
|
|
|
41
27
|
});
|
|
42
28
|
});
|
|
43
29
|
|
|
44
|
-
test("embedded source-diff says from this spec, not a GitHub blob", () => {
|
|
45
|
-
const items = [diffItem("cli", "src/cli.ts")];
|
|
46
|
-
const origin = sourcePanelOrigin({
|
|
47
|
-
showingFile: false,
|
|
48
|
-
reference: { kind: "file", path: "src/cli.ts" },
|
|
49
|
-
items,
|
|
50
|
-
linkedDiffId: "cli",
|
|
51
|
-
});
|
|
52
|
-
assert.equal(origin, "spec-diff");
|
|
53
|
-
assert.deepEqual(sourcePanelPin({ origin, path: "src/cli.ts", github }), {
|
|
54
|
-
kind: "spec",
|
|
55
|
-
label: "from this spec",
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("PR file diffs pin to the repo blob, not from this spec", () => {
|
|
60
|
-
const items = [diffItem("pr:src/cli.ts", "src/cli.ts")];
|
|
61
|
-
const origin = sourcePanelOrigin({
|
|
62
|
-
showingFile: false,
|
|
63
|
-
reference: { kind: "file", path: "src/cli.ts" },
|
|
64
|
-
items,
|
|
65
|
-
});
|
|
66
|
-
assert.equal(origin, "repo-diff");
|
|
67
|
-
const pin = sourcePanelPin({ origin, path: "src/cli.ts", github });
|
|
68
|
-
assert.equal(pin.kind, "github");
|
|
69
|
-
if (pin.kind !== "github") return;
|
|
70
|
-
assert.equal(pin.label, "tanishqkancharla/diffmap @ ef78bf4");
|
|
71
|
-
assert.match(
|
|
72
|
-
pin.href,
|
|
73
|
-
/\/blob\/ef78bf4678d2e76a7c8bcc4087fcadf347a1fcdf\/src\/cli.ts$/,
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
30
|
test("gist pin is gist id / filename, not a fake repo", () => {
|
|
78
31
|
const pin = sourcePanelPin({
|
|
79
|
-
origin: "file",
|
|
80
32
|
path: "src/cli.ts",
|
|
81
33
|
gist,
|
|
82
34
|
});
|
|
@@ -87,45 +39,8 @@ test("gist pin is gist id / filename, not a fake repo", () => {
|
|
|
87
39
|
});
|
|
88
40
|
});
|
|
89
41
|
|
|
90
|
-
test("gist embedded source-diff says from this spec", () => {
|
|
91
|
-
const origin = sourcePanelOrigin({
|
|
92
|
-
showingFile: false,
|
|
93
|
-
items: [diffItem("serve", "src/serve.ts")],
|
|
94
|
-
reference: { kind: "diff", id: "serve", side: "new", start: 1, end: 3 },
|
|
95
|
-
});
|
|
96
|
-
assert.equal(origin, "spec-diff");
|
|
97
|
-
assert.deepEqual(sourcePanelPin({ origin, path: "src/serve.ts", gist }), {
|
|
98
|
-
kind: "spec",
|
|
99
|
-
label: "from this spec",
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
42
|
test("local file has no pin line", () => {
|
|
104
|
-
assert.deepEqual(sourcePanelPin({
|
|
43
|
+
assert.deepEqual(sourcePanelPin({ path: "src/cli.ts" }), {
|
|
105
44
|
kind: "none",
|
|
106
45
|
});
|
|
107
46
|
});
|
|
108
|
-
|
|
109
|
-
test("sourcePanelPath prefers the open file, then the linked diff, then the reference", () => {
|
|
110
|
-
const items = [diffItem("cli", "src/cli.ts")];
|
|
111
|
-
assert.equal(
|
|
112
|
-
sourcePanelPath({ filePath: "src/registry.ts", items }),
|
|
113
|
-
"src/registry.ts",
|
|
114
|
-
);
|
|
115
|
-
assert.equal(
|
|
116
|
-
sourcePanelPath({
|
|
117
|
-
items,
|
|
118
|
-
linkedDiffId: "cli",
|
|
119
|
-
reference: { kind: "file", path: "src/other.ts" },
|
|
120
|
-
}),
|
|
121
|
-
"src/cli.ts",
|
|
122
|
-
);
|
|
123
|
-
assert.equal(
|
|
124
|
-
sourcePanelPath({
|
|
125
|
-
items,
|
|
126
|
-
reference: { kind: "file", path: "src/cli.ts" },
|
|
127
|
-
}),
|
|
128
|
-
"src/cli.ts",
|
|
129
|
-
);
|
|
130
|
-
assert.equal(sourcePanelPath({ items: [] }), undefined);
|
|
131
|
-
});
|
|
@@ -1,82 +1,23 @@
|
|
|
1
|
-
import type { CodeViewDiffItem } from "@pierre/diffs";
|
|
2
|
-
import type { SourceReference } from "../annotations.js";
|
|
3
1
|
import type { GistPin } from "../gist/pin.ts";
|
|
4
2
|
import { githubGistUrl } from "../gist/route.ts";
|
|
5
3
|
import type { GitHubPin } from "../github/pin.ts";
|
|
6
4
|
import { githubBlobUrl } from "../github/route.ts";
|
|
7
5
|
|
|
8
|
-
const PR_DIFF_PREFIX = "pr:";
|
|
9
|
-
|
|
10
|
-
export type SourcePanelOrigin = "file" | "spec-diff" | "repo-diff";
|
|
11
|
-
|
|
12
6
|
export type SourcePanelPin =
|
|
13
7
|
| { kind: "github"; label: string; href: string }
|
|
14
8
|
| { kind: "gist"; label: string; href: string }
|
|
15
|
-
| { kind: "spec"; label: "from this spec" }
|
|
16
9
|
| { kind: "none" };
|
|
17
10
|
|
|
18
|
-
/** Pull-files diffs are repo patches at the pin SHA; everything else is embedded in the spec. */
|
|
19
|
-
export function isSpecEmbeddedDiff(item: CodeViewDiffItem) {
|
|
20
|
-
return !item.id.startsWith(PR_DIFF_PREFIX);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function sourcePanelPath(input: {
|
|
24
|
-
filePath?: string;
|
|
25
|
-
reference?: SourceReference;
|
|
26
|
-
items: CodeViewDiffItem[];
|
|
27
|
-
linkedDiffId?: string;
|
|
28
|
-
}): string | undefined {
|
|
29
|
-
if (input.filePath !== undefined && input.filePath.length > 0) {
|
|
30
|
-
return input.filePath;
|
|
31
|
-
}
|
|
32
|
-
if (input.linkedDiffId !== undefined) {
|
|
33
|
-
const linked = input.items.find((item) => item.id === input.linkedDiffId);
|
|
34
|
-
if (linked !== undefined) return linked.fileDiff.name;
|
|
35
|
-
}
|
|
36
|
-
const ref = input.reference;
|
|
37
|
-
if (ref === undefined) return undefined;
|
|
38
|
-
if (ref.kind === "file") return ref.path;
|
|
39
|
-
return input.items.find((item) => item.id === ref.id)?.fileDiff.name;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function sourcePanelOrigin(input: {
|
|
43
|
-
showingFile: boolean;
|
|
44
|
-
reference?: SourceReference;
|
|
45
|
-
items: CodeViewDiffItem[];
|
|
46
|
-
linkedDiffId?: string;
|
|
47
|
-
}): SourcePanelOrigin {
|
|
48
|
-
if (input.showingFile) return "file";
|
|
49
|
-
const ref = input.reference;
|
|
50
|
-
const focused =
|
|
51
|
-
input.linkedDiffId !== undefined
|
|
52
|
-
? input.items.find((item) => item.id === input.linkedDiffId)
|
|
53
|
-
: ref?.kind === "diff"
|
|
54
|
-
? input.items.find((item) => item.id === ref.id)
|
|
55
|
-
: ref?.kind === "file"
|
|
56
|
-
? input.items.find((item) => item.fileDiff.name === ref.path)
|
|
57
|
-
: undefined;
|
|
58
|
-
if (focused !== undefined) {
|
|
59
|
-
return isSpecEmbeddedDiff(focused) ? "spec-diff" : "repo-diff";
|
|
60
|
-
}
|
|
61
|
-
if (input.items.some(isSpecEmbeddedDiff)) return "spec-diff";
|
|
62
|
-
if (input.items.length > 0) return "repo-diff";
|
|
63
|
-
return "file";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
11
|
/**
|
|
67
|
-
* Pin line
|
|
68
|
-
* SHA for the open path.
|
|
12
|
+
* Pin line under a single open file. A hosted spec is one SHA; blob links use
|
|
13
|
+
* that SHA for the open path.
|
|
69
14
|
*/
|
|
70
15
|
export function sourcePanelPin(input: {
|
|
71
|
-
|
|
72
|
-
path: string | undefined;
|
|
16
|
+
path: string;
|
|
73
17
|
github?: GitHubPin;
|
|
74
18
|
gist?: GistPin;
|
|
75
19
|
}): SourcePanelPin {
|
|
76
|
-
if (input.
|
|
77
|
-
return { kind: "spec", label: "from this spec" };
|
|
78
|
-
}
|
|
79
|
-
if (input.github !== undefined && input.path !== undefined) {
|
|
20
|
+
if (input.github !== undefined) {
|
|
80
21
|
const shortSha = input.github.sha.slice(0, 7);
|
|
81
22
|
return {
|
|
82
23
|
kind: "github",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
|
-
import type { CodeViewDiffItem, CodeViewLineSelection } from "@pierre/diffs";
|
|
3
2
|
import type { SourceReference } from "../annotations.js";
|
|
4
3
|
import type { SourceDefinition, DefinitionResponse } from "../definitions.js";
|
|
5
4
|
import { DiffmapDefinitionError } from "../errors.js";
|
|
@@ -138,45 +137,3 @@ function readHostFileReference(
|
|
|
138
137
|
end: range.end,
|
|
139
138
|
};
|
|
140
139
|
}
|
|
141
|
-
|
|
142
|
-
export function matchingSourceDiff(
|
|
143
|
-
source: SourceDefinition,
|
|
144
|
-
items: CodeViewDiffItem[],
|
|
145
|
-
): CodeViewLineSelection | undefined {
|
|
146
|
-
const lines = source.contents.split(/\r?\n/);
|
|
147
|
-
for (const item of items) {
|
|
148
|
-
const diff = item.fileDiff;
|
|
149
|
-
if (diff.name !== source.path) continue;
|
|
150
|
-
const ranges = diff.hunks.flatMap((hunk) => {
|
|
151
|
-
const start = Math.max(source.start, hunk.additionStart);
|
|
152
|
-
const end = Math.min(
|
|
153
|
-
source.end,
|
|
154
|
-
hunk.additionStart + hunk.additionCount - 1,
|
|
155
|
-
);
|
|
156
|
-
return start <= end ? [{ start, end, hunk }] : [];
|
|
157
|
-
});
|
|
158
|
-
if (ranges.length === 0) continue;
|
|
159
|
-
const matches = ranges.every(({ start, end, hunk }) => {
|
|
160
|
-
for (let line = start; line <= end; line++) {
|
|
161
|
-
const index = diff.isPartial
|
|
162
|
-
? hunk.additionLineIndex + line - hunk.additionStart
|
|
163
|
-
: line - 1;
|
|
164
|
-
if (
|
|
165
|
-
diff.additionLines[index]?.replace(/\r?\n$/, "") !== lines[line - 1]
|
|
166
|
-
)
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
return true;
|
|
170
|
-
});
|
|
171
|
-
if (!matches) continue;
|
|
172
|
-
return {
|
|
173
|
-
id: item.id,
|
|
174
|
-
range: {
|
|
175
|
-
start: Math.min(...ranges.map((range) => range.start)),
|
|
176
|
-
end: Math.max(...ranges.map((range) => range.end)),
|
|
177
|
-
side: "additions",
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
package/vite.config.ts
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import react from "@vitejs/plugin-react";
|
|
4
|
+
import { defineConfig, type UserConfig } from "vite";
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const gistRoot = path.join(packageRoot, "gist");
|
|
8
|
+
|
|
9
|
+
/** Production / preview: static gist viewer. Never pulls `virtual:diffmap`. */
|
|
10
|
+
export function gistAppConfig(): UserConfig {
|
|
11
|
+
return {
|
|
12
|
+
root: gistRoot,
|
|
13
|
+
cacheDir: path.join(packageRoot, "node_modules/.vite-gist"),
|
|
14
|
+
publicDir: false,
|
|
15
|
+
optimizeDeps: {
|
|
16
|
+
include: [
|
|
17
|
+
"react",
|
|
18
|
+
"react-dom",
|
|
19
|
+
"react/jsx-dev-runtime",
|
|
20
|
+
"maui",
|
|
21
|
+
"motion/react",
|
|
22
|
+
"purse-styles",
|
|
23
|
+
"@pierre/diffs/react",
|
|
24
|
+
"beautiful-mermaid",
|
|
25
|
+
"errore",
|
|
26
|
+
"md4x/standalone",
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
plugins: [react()],
|
|
30
|
+
appType: "spa",
|
|
31
|
+
resolve: {
|
|
32
|
+
alias: {
|
|
33
|
+
"md4x/napi": path.join(
|
|
34
|
+
packageRoot,
|
|
35
|
+
"node_modules/md4x/lib/standalone.mjs",
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
dedupe: ["react", "react-dom", "purse-styles"],
|
|
39
|
+
},
|
|
40
|
+
server: {
|
|
41
|
+
fs: {
|
|
42
|
+
allow: [packageRoot],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
preview: {
|
|
46
|
+
host: "127.0.0.1",
|
|
47
|
+
port: 4178,
|
|
48
|
+
},
|
|
49
|
+
build: {
|
|
50
|
+
outDir: path.join(packageRoot, "dist"),
|
|
51
|
+
emptyOutDir: true,
|
|
52
|
+
},
|
|
53
|
+
clearScreen: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default defineConfig(gistAppConfig());
|
package/src/viewer/TocButton.tsx
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Button, UnorderedList, backgroundColor } from "maui";
|
|
2
|
-
import { style, useStyles } from "purse-styles";
|
|
3
|
-
|
|
4
|
-
export function TocButton(props: { expanded: boolean; onClick?: () => void }) {
|
|
5
|
-
const className = useStyles(pressedClass);
|
|
6
|
-
return (
|
|
7
|
-
<Button
|
|
8
|
-
id="diffmap-toc-button"
|
|
9
|
-
className={className}
|
|
10
|
-
variant="quiet"
|
|
11
|
-
aria-label="Table of contents"
|
|
12
|
-
aria-pressed={props.expanded}
|
|
13
|
-
aria-expanded={props.expanded}
|
|
14
|
-
aria-controls="diffmap-toc"
|
|
15
|
-
onClick={props.onClick}
|
|
16
|
-
>
|
|
17
|
-
<UnorderedList size="sm" />
|
|
18
|
-
</Button>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const pressedClass = style({
|
|
23
|
-
flexShrink: 0,
|
|
24
|
-
"&[aria-pressed='true']": {
|
|
25
|
-
backgroundColor: backgroundColor.elementActive,
|
|
26
|
-
},
|
|
27
|
-
});
|