-
Notifications
You must be signed in to change notification settings - Fork 896
feat: Add table support and syntax highlights for markdowns #3910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { ComponentMeta, Story } from "@storybook/react" | ||
import { Markdown, MarkdownProps } from "./Markdown" | ||
|
||
export default { | ||
title: "components/Markdown", | ||
component: Markdown, | ||
} as ComponentMeta<typeof Markdown> | ||
|
||
const Template: Story<MarkdownProps> = ({ children }) => <Markdown>{children}</Markdown> | ||
|
||
export const WithCode = Template.bind({}) | ||
WithCode.args = { | ||
children: ` | ||
## Required permissions / policy | ||
|
||
The following sample policy allows Coder to create EC2 instances and modify instances provisioned by Coder: | ||
|
||
\`\`\`json | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Sid": "VisualEditor0", | ||
"Effect": "Allow", | ||
"Action": [ | ||
"ec2:GetDefaultCreditSpecification", | ||
"ec2:DescribeIamInstanceProfileAssociations", | ||
"ec2:DescribeTags", | ||
"ec2:CreateTags", | ||
"ec2:RunInstances", | ||
"ec2:DescribeInstanceCreditSpecifications", | ||
"ec2:DescribeImages", | ||
"ec2:ModifyDefaultCreditSpecification", | ||
"ec2:DescribeVolumes" | ||
], | ||
"Resource": "*" | ||
}, | ||
{ | ||
"Sid": "CoderResources", | ||
"Effect": "Allow", | ||
"Action": [ | ||
"ec2:DescribeInstances", | ||
"ec2:DescribeInstanceAttribute", | ||
"ec2:UnmonitorInstances", | ||
"ec2:TerminateInstances", | ||
"ec2:StartInstances", | ||
"ec2:StopInstances", | ||
"ec2:DeleteTags", | ||
"ec2:MonitorInstances", | ||
"ec2:CreateTags", | ||
"ec2:RunInstances", | ||
"ec2:ModifyInstanceAttribute", | ||
"ec2:ModifyInstanceCreditSpecification" | ||
], | ||
"Resource": "arn:aws:ec2:*:*:instance/*", | ||
"Condition": { | ||
"StringEquals": { | ||
"aws:ResourceTag/Coder_Provisioned": "true" | ||
} | ||
} | ||
} | ||
] | ||
} | ||
\`\`\``, | ||
} | ||
|
||
export const WithTable = Template.bind({}) | ||
WithTable.args = { | ||
children: ` | ||
| heading | b | c | d | | ||
| - | :- | -: | :-: | | ||
| cell 1 | cell 2 | 3 | 4 | `, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import Link from "@material-ui/core/Link" | ||
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles" | ||
import Table from "@material-ui/core/Table" | ||
import TableBody from "@material-ui/core/TableBody" | ||
import TableCell from "@material-ui/core/TableCell" | ||
import TableContainer from "@material-ui/core/TableContainer" | ||
import TableHead from "@material-ui/core/TableHead" | ||
import TableRow from "@material-ui/core/TableRow" | ||
import { FC } from "react" | ||
import ReactMarkdown from "react-markdown" | ||
import SyntaxHighlighter from "react-syntax-highlighter" | ||
import { dracula as dark } from "react-syntax-highlighter/dist/cjs/styles/hljs" | ||
import gfm from "remark-gfm" | ||
|
||
export interface MarkdownProps { | ||
children: string | ||
} | ||
|
||
export const Markdown: FC<{ children: string }> = ({ children }) => { | ||
const theme: Theme = useTheme() | ||
const styles = useStyles() | ||
|
||
return ( | ||
<ReactMarkdown | ||
remarkPlugins={[gfm]} | ||
components={{ | ||
a: ({ href, target, children }) => ( | ||
<Link href={href} target={target}> | ||
{children} | ||
</Link> | ||
), | ||
|
||
// Adding node so the ...props don't have it | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
code: ({ node, inline, className, children, ...props }) => { | ||
const match = /language-(\w+)/.exec(className || "") | ||
return !inline && match ? ( | ||
<SyntaxHighlighter | ||
// Custom style to match our main colors | ||
style={{ | ||
...dark, | ||
hljs: { | ||
...dark.hljs, | ||
background: theme.palette.background.default, | ||
borderRadius: theme.shape.borderRadius, | ||
color: theme.palette.text.primary, | ||
}, | ||
}} | ||
language={match[1]} | ||
PreTag="div" | ||
{...props} | ||
> | ||
{String(children).replace(/\n$/, "")} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come we have to do this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is so the highlight component can struct it properly. I got this from the |
||
</SyntaxHighlighter> | ||
) : ( | ||
<code className={styles.codeWithoutLanguage} {...props}> | ||
{children} | ||
</code> | ||
) | ||
}, | ||
|
||
table: ({ children }) => { | ||
return ( | ||
<TableContainer> | ||
<Table>{children}</Table> | ||
</TableContainer> | ||
) | ||
}, | ||
|
||
tr: ({ children }) => { | ||
return <TableRow>{children}</TableRow> | ||
}, | ||
|
||
thead: ({ children }) => { | ||
return <TableHead>{children}</TableHead> | ||
}, | ||
|
||
tbody: ({ children }) => { | ||
return <TableBody>{children}</TableBody> | ||
}, | ||
|
||
td: ({ children }) => { | ||
return <TableCell>{children}</TableCell> | ||
}, | ||
|
||
th: ({ children }) => { | ||
return <TableCell>{children}</TableCell> | ||
}, | ||
}} | ||
> | ||
{children} | ||
</ReactMarkdown> | ||
) | ||
} | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
codeWithoutLanguage: { | ||
display: "block", | ||
overflowX: "auto", | ||
padding: "0.5em", | ||
background: theme.palette.background.default, | ||
borderRadius: theme.shape.borderRadius, | ||
color: theme.palette.text.primary, | ||
}, | ||
})) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,3 +92,5 @@ export const TemplatePage: FC<React.PropsWithChildren<unknown>> = () => { | |
</> | ||
) | ||
} | ||
|
||
export default TemplatePage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ help me understand this (sorry if it's obvious 😂) we destructure
node
but don't use it, how come?does prefixing with
_
fix the ESLint warning?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we are getting the
...props
and we don't want props havingnode
.Yes, but if I use
_node
it will return_node not found
because_node
is not an attribute. I could donode: _node
but I think it is more confusing than letting it as it is and adding an ignoring comment.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh okay, that makes sense! Thanks for explaining!