@tanishqkancharla/diffmap 0.3.0 → 0.4.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 +97 -1
- package/index.html +6 -0
- package/package.json +3 -2
- package/src/changes.ts +15 -0
- package/src/codeNotes.ts +123 -0
- package/src/errors.ts +5 -0
- package/src/git.test.ts +13 -1
- package/src/git.ts +15 -2
- package/src/host/HostHome.tsx +22 -6
- package/src/host/home.md +148 -102
- package/src/host/home.test.ts +6 -7
- package/src/parseFence.ts +57 -17
- package/src/parseViewer.test.ts +216 -2
- package/src/parseViewer.ts +85 -19
- package/src/services.ts +289 -0
- package/src/share.ts +1 -1
- package/src/viewer/CallStackDiff.tsx +90 -24
- package/src/viewer/ChangesCard.tsx +133 -43
- package/src/viewer/CodeBlock.tsx +131 -0
- package/src/viewer/Fence.tsx +22 -2
- package/src/viewer/ServiceDiagramBlock.tsx +323 -0
- package/src/viewer/ServiceStage.tsx +988 -0
- package/src/viewer/SourceDiffPanel.tsx +11 -8
- package/src/viewer/TableOfContents.tsx +3 -0
- package/src/viewer/ViewerApp.tsx +18 -3
- package/src/viewer/notes.tsx +126 -0
- package/src/viewer/serviceLayout.ts +189 -0
- package/vite.config.ts +1 -0
- package/vite.gist.config.ts +1 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { useId, useState } from "react";
|
|
2
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
1
3
|
import {
|
|
2
4
|
backgroundColor,
|
|
3
5
|
borderColor,
|
|
6
|
+
ChevronRight,
|
|
4
7
|
colors,
|
|
5
8
|
Css,
|
|
6
9
|
File,
|
|
@@ -8,12 +11,15 @@ import {
|
|
|
8
11
|
FileText,
|
|
9
12
|
focusRing,
|
|
10
13
|
GitBranch,
|
|
14
|
+
motionDurationMs,
|
|
15
|
+
motionEasing,
|
|
11
16
|
text,
|
|
12
17
|
} from "maui";
|
|
13
18
|
import { style, useStyles } from "purse-styles";
|
|
14
|
-
import type {
|
|
19
|
+
import type { SourceNavigation } from "../annotations.js";
|
|
15
20
|
import {
|
|
16
21
|
isCommitRev,
|
|
22
|
+
orderForReview,
|
|
17
23
|
shortRev,
|
|
18
24
|
WORKING,
|
|
19
25
|
type ChangedFile,
|
|
@@ -24,19 +30,32 @@ import { codeFontFamily } from "../codeFont.js";
|
|
|
24
30
|
import { useChanges } from "./changesSource.ts";
|
|
25
31
|
import { pierreShell } from "./pierre.js";
|
|
26
32
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
33
|
+
/**
|
|
34
|
+
* A phase's file summary. Review opens every file; a row focuses one. Files
|
|
35
|
+
* the fence lists come first, in its order; the rest fold under a dimmed
|
|
36
|
+
* "N other files" section.
|
|
37
|
+
*/
|
|
38
|
+
export function ChangesCard(
|
|
39
|
+
props: { rev: Rev; review: string[] } & SourceNavigation,
|
|
40
|
+
) {
|
|
29
41
|
const changes = useChanges(props.rev);
|
|
42
|
+
const [showOther, setShowOther] = useState(false);
|
|
43
|
+
const otherId = useId();
|
|
44
|
+
const reducedMotion = useReducedMotion();
|
|
30
45
|
const shell = useStyles(pierreShell, styles.shell);
|
|
31
46
|
const header = useStyles(styles.header);
|
|
32
|
-
const row = useStyles(styles.row);
|
|
33
47
|
const review = useStyles(styles.review);
|
|
34
48
|
const muted = useStyles(styles.muted);
|
|
35
49
|
const range = useStyles(styles.range);
|
|
36
|
-
const
|
|
37
|
-
props.onSelectAnnotation(annotation);
|
|
50
|
+
const toggle = useStyles(styles.row, styles.toggle);
|
|
38
51
|
const files =
|
|
39
52
|
changes === undefined || changes instanceof Error ? [] : changes.files;
|
|
53
|
+
const { reviewed, other } = orderForReview(files, props.review);
|
|
54
|
+
const rowProps = {
|
|
55
|
+
rev: props.rev,
|
|
56
|
+
selectedAnnotation: props.selectedAnnotation,
|
|
57
|
+
onSelectAnnotation: props.onSelectAnnotation,
|
|
58
|
+
};
|
|
40
59
|
return (
|
|
41
60
|
<div className={shell} data-diffmap-kind="changes">
|
|
42
61
|
<div className={header}>
|
|
@@ -55,7 +74,7 @@ export function ChangesCard(props: { rev: Rev } & SourceNavigation) {
|
|
|
55
74
|
className={review}
|
|
56
75
|
aria-controls="source-diff-panel"
|
|
57
76
|
onClick={() =>
|
|
58
|
-
|
|
77
|
+
props.onSelectAnnotation({
|
|
59
78
|
text: "Review",
|
|
60
79
|
references: [{ kind: "changes", rev: props.rev }],
|
|
61
80
|
})
|
|
@@ -65,45 +84,95 @@ export function ChangesCard(props: { rev: Rev } & SourceNavigation) {
|
|
|
65
84
|
</button>
|
|
66
85
|
)}
|
|
67
86
|
</div>
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
? { kind: "file", path: file.path }
|
|
74
|
-
: { kind: "file", rev: props.rev, path: file.path },
|
|
75
|
-
],
|
|
76
|
-
};
|
|
77
|
-
return (
|
|
87
|
+
{reviewed.map((file) => (
|
|
88
|
+
<FileRow key={file.path} file={file} {...rowProps} />
|
|
89
|
+
))}
|
|
90
|
+
{other.length > 0 && (
|
|
91
|
+
<>
|
|
78
92
|
<button
|
|
79
|
-
key={file.path}
|
|
80
93
|
type="button"
|
|
81
|
-
className={
|
|
82
|
-
|
|
83
|
-
aria-controls=
|
|
84
|
-
|
|
85
|
-
props.selectedAnnotation?.text === file.path &&
|
|
86
|
-
props.selectedAnnotation.references[0]?.kind === "file" &&
|
|
87
|
-
props.selectedAnnotation.references[0].rev ===
|
|
88
|
-
annotation.references[0]!.rev
|
|
89
|
-
}
|
|
90
|
-
onClick={() => open(annotation)}
|
|
94
|
+
className={toggle}
|
|
95
|
+
aria-expanded={showOther}
|
|
96
|
+
aria-controls={otherId}
|
|
97
|
+
onClick={() => setShowOther((shown) => !shown)}
|
|
91
98
|
>
|
|
92
|
-
<
|
|
99
|
+
<ChevronRight size="sm" />
|
|
93
100
|
<span className="name">
|
|
94
|
-
{
|
|
95
|
-
{file.status === "deleted" && (
|
|
96
|
-
<span className={muted}> deleted</span>
|
|
97
|
-
)}
|
|
101
|
+
{other.length} other {other.length === 1 ? "file" : "files"}
|
|
98
102
|
</span>
|
|
99
|
-
<DiffStat
|
|
103
|
+
<DiffStat
|
|
104
|
+
additions={other.reduce((sum, file) => sum + file.additions, 0)}
|
|
105
|
+
deletions={other.reduce((sum, file) => sum + file.deletions, 0)}
|
|
106
|
+
binary={false}
|
|
107
|
+
/>
|
|
100
108
|
</button>
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
<AnimatePresence initial={false}>
|
|
110
|
+
{showOther && (
|
|
111
|
+
<motion.div
|
|
112
|
+
key="other"
|
|
113
|
+
id={otherId}
|
|
114
|
+
role="group"
|
|
115
|
+
aria-label="Other files"
|
|
116
|
+
style={{ overflow: "hidden" }}
|
|
117
|
+
initial={{ height: 0, opacity: 0 }}
|
|
118
|
+
animate={{ height: "auto", opacity: 1 }}
|
|
119
|
+
exit={{ height: 0, opacity: 0 }}
|
|
120
|
+
transition={
|
|
121
|
+
reducedMotion === true
|
|
122
|
+
? { duration: 0 }
|
|
123
|
+
: { type: "spring", visualDuration: 0.22, bounce: 0 }
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
{other.map((file) => (
|
|
127
|
+
<FileRow key={file.path} file={file} dimmed {...rowProps} />
|
|
128
|
+
))}
|
|
129
|
+
</motion.div>
|
|
130
|
+
)}
|
|
131
|
+
</AnimatePresence>
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
103
134
|
</div>
|
|
104
135
|
);
|
|
105
136
|
}
|
|
106
137
|
|
|
138
|
+
/** One changed file; selecting it opens that file in the Diff panel. */
|
|
139
|
+
function FileRow(
|
|
140
|
+
props: { rev: Rev; file: ChangedFile; dimmed?: boolean } & SourceNavigation,
|
|
141
|
+
) {
|
|
142
|
+
const row = useStyles(styles.row);
|
|
143
|
+
const muted = useStyles(styles.muted);
|
|
144
|
+
const { file, rev } = props;
|
|
145
|
+
const reference =
|
|
146
|
+
rev === WORKING
|
|
147
|
+
? { kind: "file" as const, path: file.path }
|
|
148
|
+
: { kind: "file" as const, rev, path: file.path };
|
|
149
|
+
const selected = props.selectedAnnotation?.references[0];
|
|
150
|
+
return (
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
className={row}
|
|
154
|
+
title={file.path}
|
|
155
|
+
data-dimmed={props.dimmed === true}
|
|
156
|
+
aria-controls="source-diff-panel"
|
|
157
|
+
aria-pressed={
|
|
158
|
+
props.selectedAnnotation?.text === file.path &&
|
|
159
|
+
selected?.kind === "file" &&
|
|
160
|
+
selected.rev === reference.rev
|
|
161
|
+
}
|
|
162
|
+
onClick={() =>
|
|
163
|
+
props.onSelectAnnotation({ text: file.path, references: [reference] })
|
|
164
|
+
}
|
|
165
|
+
>
|
|
166
|
+
<FileIcon path={file.path} />
|
|
167
|
+
<span className="name">
|
|
168
|
+
{file.path.split("/").pop()}
|
|
169
|
+
{file.status === "deleted" && <span className={muted}> deleted</span>}
|
|
170
|
+
</span>
|
|
171
|
+
<DiffStat {...file} />
|
|
172
|
+
</button>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
107
176
|
/** "9 files changed +214 −119" for a whole rev; the card and the Diff panel header share it. */
|
|
108
177
|
export function ChangesSummary(props: {
|
|
109
178
|
changes: Changes;
|
|
@@ -140,15 +209,15 @@ function revLabel(rev: Rev, changes: Changes | Error | undefined) {
|
|
|
140
209
|
return shortRev(rev).replace("..", "…");
|
|
141
210
|
}
|
|
142
211
|
|
|
143
|
-
function DiffStat(
|
|
144
|
-
|
|
212
|
+
function DiffStat(
|
|
213
|
+
props: Pick<ChangedFile, "additions" | "deletions" | "binary">,
|
|
214
|
+
) {
|
|
215
|
+
if (props.binary) return <span className="stat">binary</span>;
|
|
145
216
|
return (
|
|
146
217
|
<span className="stat">
|
|
147
|
-
{props.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{props.file.deletions > 0 && (
|
|
151
|
-
<span className="removed">−{props.file.deletions}</span>
|
|
218
|
+
{props.additions > 0 && <span className="added">+{props.additions}</span>}
|
|
219
|
+
{props.deletions > 0 && (
|
|
220
|
+
<span className="removed">−{props.deletions}</span>
|
|
152
221
|
)}
|
|
153
222
|
</span>
|
|
154
223
|
);
|
|
@@ -260,8 +329,29 @@ const styles = {
|
|
|
260
329
|
"&[aria-pressed='true'], &[aria-pressed='true']:hover": {
|
|
261
330
|
backgroundColor: colors.blue[3],
|
|
262
331
|
},
|
|
332
|
+
// Files outside the review list recede until you point at or pick one.
|
|
333
|
+
"&[data-dimmed='true'] > *": {
|
|
334
|
+
opacity: 0.5,
|
|
335
|
+
transition: `opacity ${String(motionDurationMs)}ms ${motionEasing}`,
|
|
336
|
+
},
|
|
337
|
+
"&[data-dimmed='true']:is(:hover, :focus-visible, [aria-pressed='true']) > *":
|
|
338
|
+
{ opacity: 1 },
|
|
263
339
|
},
|
|
264
340
|
focusRing(),
|
|
265
341
|
),
|
|
342
|
+
// A quiet footer row: the chevron sits in the icon column, so the count lines up with file names.
|
|
343
|
+
toggle: style({
|
|
344
|
+
color: colors.gray[11],
|
|
345
|
+
borderTop: `1px solid ${borderColor.border}`,
|
|
346
|
+
"& > svg": { transition: "transform 150ms ease-out" },
|
|
347
|
+
"&[aria-expanded='true'] > svg": { transform: "rotate(90deg)" },
|
|
348
|
+
// The total stat steps aside once the files show their own.
|
|
349
|
+
"& > .stat": {
|
|
350
|
+
opacity: 0.7,
|
|
351
|
+
transition: `opacity ${String(motionDurationMs)}ms ${motionEasing}`,
|
|
352
|
+
},
|
|
353
|
+
"&[aria-expanded='true'] > .stat": { opacity: 0 },
|
|
354
|
+
"&:hover": { color: colors.gray[12] },
|
|
355
|
+
}),
|
|
266
356
|
muted: style({ color: colors.gray[11] }),
|
|
267
357
|
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
2
|
+
import { getSharedHighlighter } from "@pierre/diffs";
|
|
3
|
+
import { codeBlockStyles, colors, Tooltip, useTheme } from "maui";
|
|
4
|
+
import { style, useStyles } from "purse-styles";
|
|
5
|
+
import { codeFontFamily } from "../codeFont.js";
|
|
6
|
+
import { lineSymbolMatches, type CodeNote } from "../codeNotes.js";
|
|
7
|
+
import { lineNoteDot, NoteText, NoteTokens, type Token } from "./notes.tsx";
|
|
8
|
+
|
|
9
|
+
/** A pseudocode fence with highlighted lines and hover notes. */
|
|
10
|
+
export function CodeBlock(props: {
|
|
11
|
+
lang: string;
|
|
12
|
+
source: string;
|
|
13
|
+
highlights: number[];
|
|
14
|
+
notes: CodeNote[];
|
|
15
|
+
}) {
|
|
16
|
+
const className = useStyles(codeBlockStyles.root, styles.block);
|
|
17
|
+
const lines = useTokenLines(props.source, props.lang);
|
|
18
|
+
const sourceLines = props.source.split("\n");
|
|
19
|
+
|
|
20
|
+
const renderLine = (index: number) => (
|
|
21
|
+
<span
|
|
22
|
+
key={index}
|
|
23
|
+
className="line"
|
|
24
|
+
data-highlighted={props.highlights.includes(index + 1) || undefined}
|
|
25
|
+
>
|
|
26
|
+
<NoteTokens
|
|
27
|
+
tokens={lines[index] ?? []}
|
|
28
|
+
matches={lineSymbolMatches(sourceLines[index]!, props.notes)}
|
|
29
|
+
/>
|
|
30
|
+
</span>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const rows: ReactNode[] = [];
|
|
34
|
+
for (let index = 0; index < sourceLines.length;) {
|
|
35
|
+
const note = props.notes.find(
|
|
36
|
+
(candidate) =>
|
|
37
|
+
candidate.target.kind === "lines" &&
|
|
38
|
+
candidate.target.start === index + 1,
|
|
39
|
+
);
|
|
40
|
+
if (note === undefined || note.target.kind !== "lines") {
|
|
41
|
+
rows.push(renderLine(index));
|
|
42
|
+
index++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const range: ReactNode[] = [];
|
|
46
|
+
for (; index < note.target.end; index++) range.push(renderLine(index));
|
|
47
|
+
rows.push(
|
|
48
|
+
<Tooltip key={`note-${index}`} content={<NoteText text={note.text} />}>
|
|
49
|
+
<span className="line-note" tabIndex={0}>
|
|
50
|
+
{range}
|
|
51
|
+
</span>
|
|
52
|
+
</Tooltip>,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<pre className={className} data-diffmap-kind="code">
|
|
58
|
+
<code>{rows}</code>
|
|
59
|
+
</pre>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Tokens per line from Pierre's highlighter; plain text until it loads or for unknown languages. */
|
|
64
|
+
function useTokenLines(source: string, lang: string): Token[][] {
|
|
65
|
+
const { resolvedTheme } = useTheme();
|
|
66
|
+
const [highlighted, setHighlighted] = useState<{
|
|
67
|
+
key: string;
|
|
68
|
+
lines: Token[][];
|
|
69
|
+
}>();
|
|
70
|
+
const key = `${resolvedTheme}\n${lang}\n${source}`;
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
let cancelled = false;
|
|
73
|
+
const theme = `pierre-${resolvedTheme}`;
|
|
74
|
+
const tokenize = async (language: string) => {
|
|
75
|
+
const highlighter = await getSharedHighlighter({
|
|
76
|
+
themes: [theme],
|
|
77
|
+
langs: [language],
|
|
78
|
+
});
|
|
79
|
+
return highlighter.codeToTokens(source, { lang: language, theme }).tokens;
|
|
80
|
+
};
|
|
81
|
+
// oxlint-disable-next-line typescript/no-floating-promises -- React effects cannot await; this request owns the token update.
|
|
82
|
+
void tokenize(lang)
|
|
83
|
+
.catch(() => tokenize("text"))
|
|
84
|
+
.then((lines) => {
|
|
85
|
+
if (!cancelled) setHighlighted({ key, lines });
|
|
86
|
+
})
|
|
87
|
+
.catch((cause) => {
|
|
88
|
+
console.warn("diffmap code highlight failed", cause);
|
|
89
|
+
});
|
|
90
|
+
return () => {
|
|
91
|
+
cancelled = true;
|
|
92
|
+
};
|
|
93
|
+
}, [key, lang, resolvedTheme, source]);
|
|
94
|
+
if (highlighted?.key === key) return highlighted.lines;
|
|
95
|
+
return source.split("\n").map((line) => [{ content: line }]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const styles = {
|
|
99
|
+
block: style({
|
|
100
|
+
fontFamily: codeFontFamily,
|
|
101
|
+
paddingInline: 0,
|
|
102
|
+
"& > code": {
|
|
103
|
+
display: "inline-block",
|
|
104
|
+
verticalAlign: "top",
|
|
105
|
+
minWidth: "100%",
|
|
106
|
+
font: "inherit",
|
|
107
|
+
},
|
|
108
|
+
"& .line": {
|
|
109
|
+
display: "block",
|
|
110
|
+
minHeight: "1lh",
|
|
111
|
+
paddingInline: "12px",
|
|
112
|
+
},
|
|
113
|
+
"& .line[data-highlighted]": {
|
|
114
|
+
backgroundColor: colors.blue[3],
|
|
115
|
+
boxShadow: `inset 3px 0 ${colors.blue[9]}`,
|
|
116
|
+
},
|
|
117
|
+
"& .line-note": {
|
|
118
|
+
display: "block",
|
|
119
|
+
position: "relative",
|
|
120
|
+
cursor: "help",
|
|
121
|
+
outline: "none",
|
|
122
|
+
},
|
|
123
|
+
"& .line-note::before": lineNoteDot,
|
|
124
|
+
"& .line-note:hover > .line:not([data-highlighted])": {
|
|
125
|
+
backgroundColor: colors.gray[3],
|
|
126
|
+
},
|
|
127
|
+
"& .line-note:focus-visible": {
|
|
128
|
+
boxShadow: `inset 0 0 0 2px ${colors.blue[8]}`,
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
};
|
package/src/viewer/Fence.tsx
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { CodeBlock } from "maui";
|
|
2
1
|
import type { Fence as FenceModel } from "../parseFence.js";
|
|
3
2
|
import type { SourceNavigation } from "../annotations.js";
|
|
4
3
|
import { CallStackDiff } from "./CallStackDiff.tsx";
|
|
5
4
|
import { ChangesCard } from "./ChangesCard.tsx";
|
|
5
|
+
import { CodeBlock } from "./CodeBlock.tsx";
|
|
6
6
|
import { CodeDiff } from "./CodeDiff.tsx";
|
|
7
7
|
import { FileExcerpt } from "./FileExcerpt.tsx";
|
|
8
8
|
import { HtmlBlock } from "./HtmlBlock.tsx";
|
|
9
9
|
import { HtmlPlaceholder } from "./HtmlPlaceholder.tsx";
|
|
10
10
|
import { MermaidBlock } from "./MermaidBlock.tsx";
|
|
11
|
+
import { ServiceDiagramBlock } from "./ServiceDiagramBlock.tsx";
|
|
11
12
|
import { useViewerMode } from "./viewerMode.ts";
|
|
12
13
|
|
|
13
14
|
export function Fence(props: { fence: FenceModel } & SourceNavigation) {
|
|
@@ -22,6 +23,15 @@ export function Fence(props: { fence: FenceModel } & SourceNavigation) {
|
|
|
22
23
|
onSelectAnnotation={props.onSelectAnnotation}
|
|
23
24
|
/>
|
|
24
25
|
);
|
|
26
|
+
if (fence.kind === "services")
|
|
27
|
+
return (
|
|
28
|
+
<ServiceDiagramBlock
|
|
29
|
+
source={fence.source}
|
|
30
|
+
parsed={fence.parsed}
|
|
31
|
+
selectedAnnotation={props.selectedAnnotation}
|
|
32
|
+
onSelectAnnotation={props.onSelectAnnotation}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
25
35
|
if (fence.kind === "html") {
|
|
26
36
|
if (!trustedHtml) return <HtmlPlaceholder />;
|
|
27
37
|
return <HtmlBlock source={fence.source} />;
|
|
@@ -30,6 +40,7 @@ export function Fence(props: { fence: FenceModel } & SourceNavigation) {
|
|
|
30
40
|
return (
|
|
31
41
|
<ChangesCard
|
|
32
42
|
rev={fence.rev}
|
|
43
|
+
review={fence.review}
|
|
33
44
|
selectedAnnotation={props.selectedAnnotation}
|
|
34
45
|
onSelectAnnotation={props.onSelectAnnotation}
|
|
35
46
|
/>
|
|
@@ -38,6 +49,8 @@ export function Fence(props: { fence: FenceModel } & SourceNavigation) {
|
|
|
38
49
|
return (
|
|
39
50
|
<CallStackDiff
|
|
40
51
|
lines={fence.lines}
|
|
52
|
+
highlights={fence.highlights}
|
|
53
|
+
notes={fence.notes}
|
|
41
54
|
selectedAnnotation={props.selectedAnnotation}
|
|
42
55
|
onSelectAnnotation={props.onSelectAnnotation}
|
|
43
56
|
/>
|
|
@@ -55,5 +68,12 @@ export function Fence(props: { fence: FenceModel } & SourceNavigation) {
|
|
|
55
68
|
/>
|
|
56
69
|
);
|
|
57
70
|
}
|
|
58
|
-
return
|
|
71
|
+
return (
|
|
72
|
+
<CodeBlock
|
|
73
|
+
lang={fence.lang}
|
|
74
|
+
source={fence.source}
|
|
75
|
+
highlights={fence.highlights}
|
|
76
|
+
notes={fence.notes}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
59
79
|
}
|