display svg images in code blocks

main
Cogent Apps 2023-03-16 09:48:46 +00:00
parent 47892360b2
commit 869ba3ec1f
3 changed files with 7996 additions and 16 deletions

View File

@ -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;

7917
app/public/prose.css 100644

File diff suppressed because it is too large Load Diff

View File

@ -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;
@ -15,7 +57,7 @@ export interface MarkdownProps {
export function Markdown(props: MarkdownProps) { export function Markdown(props: MarkdownProps) {
const intl = useIntl(); const intl = useIntl();
const classes = useMemo(() => { const classes = useMemo(() => {
const classes = ['prose', 'dark:prose-invert']; const classes = ['prose', 'dark:prose-invert'];
@ -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>