Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9e4558a

Browse files
authored
feat: parse resource metadata values as markdown (coder#10521)
1 parent 43a8674 commit 9e4558a

File tree

3 files changed

+122
-8
lines changed

3 files changed

+122
-8
lines changed

site/src/components/Markdown/Markdown.tsx

+71-5
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import TableCell from "@mui/material/TableCell";
55
import TableContainer from "@mui/material/TableContainer";
66
import TableHead from "@mui/material/TableHead";
77
import TableRow from "@mui/material/TableRow";
8-
import { FC, memo } from "react";
9-
import ReactMarkdown from "react-markdown";
8+
import { type Interpolation, type Theme } from "@emotion/react";
9+
import isEqual from "lodash/isEqual";
10+
import { type FC, memo } from "react";
11+
import ReactMarkdown, { type Options } from "react-markdown";
1012
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
1113
import gfm from "remark-gfm";
1214
import { colors } from "theme/colors";
1315
import { darcula } from "react-syntax-highlighter/dist/cjs/styles/prism";
14-
import { type Interpolation, type Theme } from "@emotion/react";
1516

1617
interface MarkdownProps {
1718
/**
@@ -20,10 +21,15 @@ interface MarkdownProps {
2021
children: string;
2122

2223
className?: string;
24+
25+
/**
26+
* Can override the behavior of the generated elements
27+
*/
28+
components?: Options["components"];
2329
}
2430

2531
export const Markdown: FC<MarkdownProps> = (props) => {
26-
const { children, className } = props;
32+
const { children, className, components = {} } = props;
2733

2834
return (
2935
<ReactMarkdown
@@ -106,14 +112,74 @@ export const Markdown: FC<MarkdownProps> = (props) => {
106112
th: ({ children }) => {
107113
return <TableCell>{children}</TableCell>;
108114
},
115+
116+
...components,
117+
}}
118+
>
119+
{children}
120+
</ReactMarkdown>
121+
);
122+
};
123+
124+
interface MarkdownInlineProps {
125+
/**
126+
* The Markdown text to parse and render
127+
*/
128+
children: string;
129+
130+
className?: string;
131+
132+
/**
133+
* Can override the behavior of the generated elements
134+
*/
135+
components?: Options["components"];
136+
}
137+
138+
/**
139+
* Supports a strict subset of Markdown that bahaves well as inline/confined content.
140+
*/
141+
export const InlineMarkdown: FC<MarkdownInlineProps> = (props) => {
142+
const { children, className, components = {} } = props;
143+
144+
return (
145+
<ReactMarkdown
146+
className={className}
147+
allowedElements={["p", "em", "strong", "a", "pre", "code"]}
148+
unwrapDisallowed
149+
components={{
150+
p: ({ children }) => <>{children}</>,
151+
152+
a: ({ href, target, children }) => (
153+
<Link href={href} target={target}>
154+
{children}
155+
</Link>
156+
),
157+
158+
code: ({ node, className, children, style, ...props }) => (
159+
<code
160+
css={(theme) => ({
161+
padding: "1px 4px",
162+
background: theme.palette.divider,
163+
borderRadius: 4,
164+
color: theme.palette.text.primary,
165+
fontSize: 14,
166+
})}
167+
{...props}
168+
>
169+
{children}
170+
</code>
171+
),
172+
173+
...components,
109174
}}
110175
>
111176
{children}
112177
</ReactMarkdown>
113178
);
114179
};
115180

116-
export const MemoizedMarkdown = memo(Markdown);
181+
export const MemoizedMarkdown = memo(Markdown, isEqual);
182+
export const MemoizedInlineMarkdown = memo(InlineMarkdown, isEqual);
117183

118184
const markdownStyles: Interpolation<Theme> = (theme: Theme) => ({
119185
fontSize: 16,

site/src/components/Resources/ResourceCard.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { type FC, useState } from "react";
1+
import { type FC, type PropsWithChildren, useState } from "react";
22
import IconButton from "@mui/material/IconButton";
33
import Tooltip from "@mui/material/Tooltip";
44
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
5+
import { Children } from "react";
56
import type { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
67
import { DropdownArrow } from "../DropdownArrow/DropdownArrow";
78
import { CopyableValue } from "../CopyableValue/CopyableValue";
9+
import { MemoizedInlineMarkdown } from "../Markdown/Markdown";
810
import { Stack } from "../Stack/Stack";
911
import { ResourceAvatar } from "./ResourceAvatar";
1012
import { SensitiveValue } from "./SensitiveValue";
@@ -72,6 +74,14 @@ export interface ResourceCardProps {
7274
agentRow: (agent: WorkspaceAgent) => JSX.Element;
7375
}
7476

77+
const p = ({ children }: PropsWithChildren) => {
78+
const childrens = Children.toArray(children);
79+
if (childrens.every((child) => typeof child === "string")) {
80+
return <CopyableValue value={childrens.join("")}>{children}</CopyableValue>;
81+
}
82+
return <>{children}</>;
83+
};
84+
7585
export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
7686
const [shouldDisplayAllMetadata, setShouldDisplayAllMetadata] =
7787
useState(false);
@@ -136,9 +146,9 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
136146
{meta.sensitive ? (
137147
<SensitiveValue value={meta.value} />
138148
) : (
139-
<CopyableValue value={meta.value}>
149+
<MemoizedInlineMarkdown components={{ p }}>
140150
{meta.value}
141-
</CopyableValue>
151+
</MemoizedInlineMarkdown>
142152
)}
143153
</div>
144154
</div>

site/src/components/Resources/Resources.stories.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,44 @@ const reallyLong = {
5858
sensitive: false,
5959
};
6060

61+
export const Markdown: Story = {
62+
args: {
63+
resources: [
64+
{
65+
...nullDevice,
66+
type: "workspace",
67+
id: "1",
68+
name: "Workspace",
69+
metadata: [
70+
{ key: "text", value: "hello", sensitive: false },
71+
{ key: "link", value: "[hello](#)", sensitive: false },
72+
{ key: "b/i", value: "_hello_, **friend**!", sensitive: false },
73+
{ key: "coder", value: "`beep boop`", sensitive: false },
74+
],
75+
},
76+
77+
// bits of Markdown that are intentionally not supported here
78+
{
79+
...nullDevice,
80+
type: "unsupported",
81+
id: "2",
82+
name: "Unsupported",
83+
metadata: [
84+
{
85+
key: "multiple paragraphs",
86+
value: `home,
87+
88+
home on the range`,
89+
sensitive: false,
90+
},
91+
{ key: "heading", value: "# HI", sensitive: false },
92+
{ key: "image", value: "![go](/icon/go.svg)", sensitive: false },
93+
],
94+
},
95+
],
96+
},
97+
};
98+
6199
export const BunchOfDevicesWithMetadata: Story = {
62100
args: {
63101
resources: [

0 commit comments

Comments
 (0)