display svg images in code blocks
parent
47892360b2
commit
869ba3ec1f
|
@ -30,7 +30,7 @@
|
||||||
<link href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,600,700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,600,700&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css"
|
||||||
integrity="sha384-Xi8rHCmBmhbuyyhbI88391ZKP2dmfnOl4rT9ZfRI7mLTdk1wblIUnrIq35nqwEvC" crossorigin="anonymous">
|
integrity="sha384-Xi8rHCmBmhbuyyhbI88391ZKP2dmfnOl4rT9ZfRI7mLTdk1wblIUnrIq35nqwEvC" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tailwindcss/typography@0.4.1/dist/typography.min.css" />
|
<link rel="stylesheet" href="/prose.css" />
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background: #292933;
|
background: #292933;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,4 @@
|
||||||
|
import styled from '@emotion/styled';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
|
@ -8,6 +9,47 @@ import { Button, CopyButton } from '@mantine/core';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
const Code = styled.div`
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-style: normal !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
background: #191919;
|
||||||
|
height: 2.5rem;
|
||||||
|
padding: 0.1rem 0.1rem 0 0.5rem;
|
||||||
|
|
||||||
|
.mantine-Button-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ImagePreview = styled.div`
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export interface MarkdownProps {
|
export interface MarkdownProps {
|
||||||
content: string;
|
content: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -28,30 +70,51 @@ export function Markdown(props: MarkdownProps) {
|
||||||
|
|
||||||
const elem = useMemo(() => (
|
const elem = useMemo(() => (
|
||||||
<div className={classes.join(' ')}>
|
<div className={classes.join(' ')}>
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm, remarkMath]}
|
<ReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
rehypePlugins={[rehypeKatex]}
|
rehypePlugins={[rehypeKatex]}
|
||||||
components={{
|
components={{
|
||||||
code({ node, inline, className, children, ...props }) {
|
code({ node, inline, className, children, ...props }) {
|
||||||
const match = /language-(\w+)/.exec(className || '')
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
return !inline ? (
|
const code = String(children);
|
||||||
<div>
|
return !inline ? (<>
|
||||||
<CopyButton value={String(children)}>
|
<Code>
|
||||||
{({ copy, copied }) => (
|
<Header>
|
||||||
<Button variant="subtle" size="sm" compact onClick={copy}>
|
{code.startsWith('<svg') && code.includes('</svg>') && (
|
||||||
<i className="fa fa-clipboard" />
|
<Button variant="subtle" size="sm" compact onClick={() => {
|
||||||
<span>{copied ? <FormattedMessage defaultMessage="Copied" /> : <FormattedMessage defaultMessage="Copy" />}</span>
|
const blob = new Blob([code], { type: 'image/svg+xml' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'image.svg';
|
||||||
|
a.click();
|
||||||
|
}}>
|
||||||
|
<i className="fa fa-download" />
|
||||||
|
<span><FormattedMessage defaultMessage="Download SVG" /></span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</CopyButton>
|
<CopyButton value={code}>
|
||||||
|
{({ copy, copied }) => (
|
||||||
|
<Button variant="subtle" size="sm" compact onClick={copy}>
|
||||||
|
<i className="fa fa-clipboard" />
|
||||||
|
<span>{copied ? <FormattedMessage defaultMessage="Copied" /> : <FormattedMessage defaultMessage="Copy" />}</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CopyButton>
|
||||||
|
</Header>
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
children={String(children).replace(/\n$/, '')}
|
children={code}
|
||||||
style={vscDarkPlus as any}
|
style={vscDarkPlus as any}
|
||||||
language={match?.[1] || 'text'}
|
language={match?.[1] || 'text'}
|
||||||
PreTag="div"
|
PreTag="div"
|
||||||
{...props}
|
{...props} />
|
||||||
/>
|
</Code>
|
||||||
</div>
|
{code.startsWith('<svg') && code.includes('</svg>') && (
|
||||||
) : (
|
<ImagePreview>
|
||||||
|
<img src={`data:image/svg+xml;base64,${btoa(code)}`} />
|
||||||
|
</ImagePreview>
|
||||||
|
)}
|
||||||
|
</>) : (
|
||||||
<code className={className} {...props}>
|
<code className={className} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</code>
|
</code>
|
||||||
|
|
Loading…
Reference in New Issue