From c170edb692d1aa0bee4239272831206d975673bd Mon Sep 17 00:00:00 2001 From: Jeremy Kahn Date: Wed, 11 Oct 2023 21:18:28 -0500 Subject: [PATCH] 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 --- .../CopyableBlock/CopyableBlock.tsx | 53 +++++++++++++++++++ src/components/CopyableBlock/index.ts | 1 + src/components/Message/Message.tsx | 22 ++++---- .../SyntaxHighlighter/SyntaxHighlighter.tsx | 3 -- src/components/SyntaxHighlighter/index.ts | 1 - src/pages/Home/EmbedCodeDialog.tsx | 38 ++++++------- 6 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 src/components/CopyableBlock/CopyableBlock.tsx create mode 100644 src/components/CopyableBlock/index.ts delete mode 100644 src/components/SyntaxHighlighter/SyntaxHighlighter.tsx delete mode 100644 src/components/SyntaxHighlighter/index.ts diff --git a/src/components/CopyableBlock/CopyableBlock.tsx b/src/components/CopyableBlock/CopyableBlock.tsx new file mode 100644 index 0000000..4251b2e --- /dev/null +++ b/src/components/CopyableBlock/CopyableBlock.tsx @@ -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(null) + + const handleCopyClick = async () => { + const div = boxRef?.current + + if (!div) return + + await navigator.clipboard.writeText(div.innerText) + + showAlert('Copied to clipboard', { severity: 'success' }) + } + + return ( + + {children} + + ({ + position: 'absolute', + top: '1em', + right: '1em', + opacity: 0, + transition: theme.transitions.create(['opacity', 'transform']), + })} + > + + + + + ) +} diff --git a/src/components/CopyableBlock/index.ts b/src/components/CopyableBlock/index.ts new file mode 100644 index 0000000..e4dc2f9 --- /dev/null +++ b/src/components/CopyableBlock/index.ts @@ -0,0 +1 @@ +export * from './CopyableBlock' diff --git a/src/components/Message/Message.tsx b/src/components/Message/Message.tsx index 845532f..ed728c8 100644 --- a/src/components/Message/Message.tsx +++ b/src/components/Message/Message.tsx @@ -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 ? ( - + + + ) : ( {children} diff --git a/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx b/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx deleted file mode 100644 index 10cd977..0000000 --- a/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter' - -export { SyntaxHighlighter } diff --git a/src/components/SyntaxHighlighter/index.ts b/src/components/SyntaxHighlighter/index.ts deleted file mode 100644 index 0d31fb3..0000000 --- a/src/components/SyntaxHighlighter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SyntaxHighlighter' diff --git a/src/pages/Home/EmbedCodeDialog.tsx b/src/pages/Home/EmbedCodeDialog.tsx index abd6f9d..849364b 100644 --- a/src/pages/Home/EmbedCodeDialog.tsx +++ b/src/pages/Home/EmbedCodeDialog.tsx @@ -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 = ({ Room embed code - - {`