feat(ui): Add copy button for code (#178)

* feat(ui): stand up CopyableBlock component
* feat(ui): implement clipboard writing
* feat(ui): make code blocks copyable
* refactor(imports): use SyntaxHighlighter directly
* feat(ui): make copy button slightly transparent
* feat(ui): tweak copy alert text
This commit is contained in:
Jeremy Kahn 2023-10-11 21:18:28 -05:00 committed by GitHub
parent ff988d9da5
commit c170edb692
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 31 deletions

View File

@ -0,0 +1,53 @@
import Box, { BoxProps } from '@mui/material/Box'
import Tooltip from '@mui/material/Tooltip'
import Fab from '@mui/material/Fab'
import ContentCopy from '@mui/icons-material/ContentCopy'
import { useContext, useRef } from 'react'
import { ShellContext } from 'contexts/ShellContext'
interface CopyableBlockProps extends BoxProps {}
export const CopyableBlock = ({ children }: CopyableBlockProps) => {
const { showAlert } = useContext(ShellContext)
const boxRef = useRef<HTMLDivElement>(null)
const handleCopyClick = async () => {
const div = boxRef?.current
if (!div) return
await navigator.clipboard.writeText(div.innerText)
showAlert('Copied to clipboard', { severity: 'success' })
}
return (
<Box
ref={boxRef}
sx={{
position: 'relative',
'&:hover button': {
opacity: 0.75,
},
}}
>
{children}
<Tooltip title="Copy to clipboard">
<Fab
color="default"
size="small"
onClick={handleCopyClick}
sx={theme => ({
position: 'absolute',
top: '1em',
right: '1em',
opacity: 0,
transition: theme.transitions.create(['opacity', 'transform']),
})}
>
<ContentCopy />
</Fab>
</Tooltip>
</Box>
)
}

View File

@ -0,0 +1 @@
export * from './CopyableBlock'

View File

@ -1,4 +1,5 @@
import { HTMLAttributes } from 'react'
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
import YouTube from 'react-youtube'
import Box from '@mui/material/Box'
import Tooltip from '@mui/material/Tooltip'
@ -27,10 +28,10 @@ import {
isInlineMedia,
} from 'models/chat'
import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { SyntaxHighlighter } from '../../components/SyntaxHighlighter'
import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
import { InlineMedia } from './InlineMedia'
import './Message.sass'
export interface MessageProps {
@ -65,14 +66,17 @@ const componentMap = {
// https://github.com/remarkjs/react-markdown#use-custom-components-syntax-highlight
code({ node, inline, className, children, style, ...props }: CodeProps) {
const match = /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter
children={String(children).replace(/\n$/, '')}
language={match[1]}
style={materialDark}
PreTag="div"
{...props}
/>
<CopyableBlock>
<SyntaxHighlighter
children={String(children).replace(/\n$/, '')}
language={match[1]}
style={materialDark}
PreTag="div"
{...props}
/>
</CopyableBlock>
) : (
<code className={className} {...props}>
{children}

View File

@ -1,3 +0,0 @@
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
export { SyntaxHighlighter }

View File

@ -1 +0,0 @@
export * from './SyntaxHighlighter'

View File

@ -1,3 +1,4 @@
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog'
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
@ -5,8 +6,7 @@ import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText'
import DialogTitle from '@mui/material/DialogTitle'
import { SyntaxHighlighter } from '../../components/SyntaxHighlighter'
import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
interface EmbedCodeDialogProps {
showEmbedCode: boolean
@ -29,22 +29,24 @@ export const EmbedCodeDialog = ({
<Dialog open={showEmbedCode} onClose={handleEmbedCodeWindowClose}>
<DialogTitle>Room embed code</DialogTitle>
<DialogContent>
<SyntaxHighlighter
language="html"
style={materialDark}
PreTag="div"
lineProps={{
style: {
wordBreak: 'break-all',
whiteSpace: 'pre-wrap',
},
}}
wrapLines={true}
>
{`<iframe src="${embedUrl}" allow="${iframeFeatureAllowList.join(
';'
)}" width="800" height="800" />`}
</SyntaxHighlighter>
<CopyableBlock>
<SyntaxHighlighter
language="html"
style={materialDark}
PreTag="div"
lineProps={{
style: {
wordBreak: 'break-all',
whiteSpace: 'pre-wrap',
},
}}
wrapLines={true}
>
{`<iframe src="${embedUrl}" allow="${iframeFeatureAllowList.join(
';'
)}" width="800" height="800" />`}
</SyntaxHighlighter>
</CopyableBlock>
<DialogContentText
sx={{
mb: 2,