import React, { useMemo, VFC } from "react";
import { compiler as markdownToJsxCompiler } from "markdown-to-jsx";
import styled, { css } from "styled-components";
import Prism from "prismjs";
import type { AppThemeScheme, WithThemeSchemeProp } from "../types";
import { Card } from "./Card.styled";
import { NamedColors } from "../utils/style";
type MarkdownToJsxProps = {
themeScheme: AppThemeScheme;
markdown: string;
};
export const MarkdownToJsx: VFC<MarkdownToJsxProps> = ({
themeScheme,
markdown,
}) => {
const MarkdownElements = useMemo(
() => () =>
markdownToJsxCompiler(markdown, {
wrapper: ({ children }) => <>{children}</>,
forceBlock: true,
overrides: {
a: ({ children, href, title, ...props }) => (
<StyledAnchor
{...props}
href={href}
themeScheme={themeScheme}
title={title}
target={href.startsWith("http") ? "_blank" : undefined}
rel={
href.startsWith("http")
? "noopener noreferer noreferrer"
: undefined
}
>
{children}
</StyledAnchor>
),
p: ({ children }) => (
<StyledParagraph themeScheme={themeScheme}>
{children}
</StyledParagraph>
),
blockquote: ({ children, ...props }) => (
<StyledBlockquoteCard {...props} themeScheme={themeScheme}>
{children}
</StyledBlockquoteCard>
),
code: ({ className, children: code, ...props }) => {
const language = (className || "lang-plaintext").replace(
"lang-",
""
);
const html = Prism.highlight(
code,
Prism.languages[language],
language
);
return (
<pre data-language={language} className={` language-${language}`}>
<StyledCodeTag
{...props}
themeScheme={themeScheme}
dangerouslySetInnerHTML={{
__html: html,
}}
/>
</pre>
);
},
pre: ({ children, ...props }) => {
return (
<StyledCodeBlock {...props} themeScheme={themeScheme}>
{children}
</StyledCodeBlock>
);
},
ul: ({ children, ...props }) => (
<ul style={{ paddingLeft: 24 }} {...props}>
{children}
</ul>
),
li: ({ children, ...props }) => (
<li style={{ marginTop: 4 }} {...props}>
{children}
</li>
),
ImageWithTheme: ({
alt,
lightSrc,
darkSrc,
minHeight = undefined,
}: {
alt: string;
lightSrc: string;
darkSrc: string;
minHeight?: number;
}) => (
<figure>
<img
alt={alt}
src={
themeScheme === "light"
? JSON.parse(darkSrc)
: JSON.parse(lightSrc)
}
style={{
width: "100%",
height: "auto",
minHeight: `${minHeight}px`,
}}
/>
<figcaption
style={{
textAlign: "center",
opacity: 0.67,
fontSize: 12,
fontStyle: "italic",
}}
>
{JSON.parse(alt)}
</figcaption>
</figure>
),
},
}),
[markdown]
);
return (
<StyledMarkdownContainer className={"md"}>
<MarkdownElements />
</StyledMarkdownContainer>
);
};
const StyledMarkdownContainer = styled.div`
width: 100%;
font-size: 18px;
line-height: 26px;
& > blockquote:first-child,
& > div:first-child,
& > pre:first-child,
& > code:first-child,
& > body:first-child,
& > dd:first-child,
& > dl:first-child,
& > figure:first-child,
& > h1:first-child,
& > h2:first-child,
& > h3:first-child,
& > h4:first-child,
& > h5:first-child,
& > p:first-child {
margin-top: 0 !important;
}
`;
const StyledAnchor = styled.a<WithThemeSchemeProp>`
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_LINK[themeScheme]};
`};
font-weight: bold;
text-decoration: none;
&:hover {
text-decoration: underline;
}
`;
const StyledParagraph = styled.p<WithThemeSchemeProp>`
margin-top: 16px;
${({ themeScheme }) => css`
color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
`};
`;
const StyledBlockquoteCard = styled(Card)<WithThemeSchemeProp>`
& > p {
margin: 0;
margin-top: 16px;
}
& > div,
& > blockquote {
margin-top: 8px;
}
& > pre {
margin: 0;
margin-top: 8px;
}
& > blockquote:first-child,
& > div:first-child,
& > pre:first-child,
& > code:first-child,
& > body:first-child,
& > dd:first-child,
& > dl:first-child,
& > figure:first-child,
& > h1:first-child,
& > h2:first-child,
& > h3:first-child,
& > h4:first-child,
& > h5:first-child,
& > p:first-child {
margin-top: 0 !important;
}
`;
const codeElBaseCss = css<WithThemeSchemeProp>`
border-radius: 4px;
font-size: 16px;
line-height: 16px;
white-space: break-spaces;
`;
const StyledCodeTag = styled.code<WithThemeSchemeProp>`
min-height: 20px;
padding: 2px 4px;
${codeElBaseCss};
${({ themeScheme }) => css`
background-color: ${NamedColors.CARD[themeScheme]};
border: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
`};
`;
const StyledCodeBlock = styled.pre<WithThemeSchemeProp>`
min-width: 100%;
width: 100%;
max-width: 100%;
padding: 8px 12px;
${codeElBaseCss};
${({ themeScheme }) => css`
background-color: ${NamedColors.CARD[themeScheme]};
border: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
`};
& > code {
width: 100%;
padding: 0 !important;
background-color: transparent !important;
border-radius: 0 !important;
border: unset !important;
line-height: 26px;
}
`;