1- import React , { memo , useEffect } from "react"
1+ import React , { memo , useEffect , useRef , useState } from "react"
22import type { ComponentProps } from "react"
33import { useRemark } from "react-remark"
44import rehypeHighlight , { Options } from "rehype-highlight"
@@ -91,15 +91,34 @@ const remarkPreventBoldFilenames = () => {
9191 }
9292}
9393
94+ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
95+
96+ const CopyButton = styled ( VSCodeButton ) `
97+ position: absolute;
98+ top: 5px;
99+ right: 5px;
100+ z-index: 1;
101+ opacity: 0;
102+ `
103+
104+ const CodeBlockContainer = styled . div `
105+ position: relative;
106+
107+ &:hover ${ CopyButton } {
108+ opacity: 1;
109+ }
110+ `
111+
94112const StyledMarkdown = styled . div `
95113 pre {
96114 background-color: ${ CODE_BLOCK_BG_COLOR } ;
97115 border-radius: 3px;
98- margin: 13x 0;
116+ margin: 13px 0;
99117 padding: 10px 10px;
100118 max-width: calc(100vw - 20px);
101119 overflow-x: auto;
102120 overflow-y: hidden;
121+ padding-right: 70px;
103122 }
104123
105124 pre > code {
@@ -197,8 +216,42 @@ const StyledPre = styled.pre<{ theme: any }>`
197216 . join ( "" ) }
198217`
199218
219+ const PreWithCopyButton = ( {
220+ children,
221+ theme,
222+ ...preProps
223+ } : { theme : Record < string , string > } & React . HTMLAttributes < HTMLPreElement > ) => {
224+ const preRef = useRef < HTMLPreElement > ( null )
225+ const [ copied , setCopied ] = useState ( false )
226+
227+ const handleCopy = ( ) => {
228+ if ( preRef . current ) {
229+ const codeElement = preRef . current . querySelector ( "code" )
230+ const textToCopy = codeElement ? codeElement . textContent : preRef . current . textContent
231+
232+ if ( ! textToCopy ) return
233+ navigator . clipboard . writeText ( textToCopy ) . then ( ( ) => {
234+ setCopied ( true )
235+ setTimeout ( ( ) => setCopied ( false ) , 1500 )
236+ } )
237+ }
238+ }
239+
240+ return (
241+ < CodeBlockContainer >
242+ < CopyButton appearance = "icon" onClick = { handleCopy } aria-label = { copied ? "Copied" : "Copy" } >
243+ < span className = { `codicon codicon-${ copied ? "check" : "copy" } ` } > </ span >
244+ </ CopyButton >
245+ < StyledPre { ...preProps } theme = { theme } ref = { preRef } >
246+ { children }
247+ </ StyledPre >
248+ </ CodeBlockContainer >
249+ )
250+ }
251+
200252const MarkdownBlock = memo ( ( { markdown } : MarkdownBlockProps ) => {
201253 const { theme } = useExtensionState ( )
254+
202255 const [ reactContent , setMarkdown ] = useRemark ( {
203256 remarkPlugins : [
204257 remarkPreventBoldFilenames ,
@@ -223,17 +276,17 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
223276 ] ,
224277 rehypeReactOptions : {
225278 components : {
226- pre : ( { node , children, ...preProps } : any ) => {
279+ pre : ( { children, ...preProps } : React . HTMLAttributes < HTMLPreElement > ) => {
227280 if ( Array . isArray ( children ) && children . length === 1 && React . isValidElement ( children [ 0 ] ) ) {
228281 const child = children [ 0 ] as React . ReactElement < { className ?: string } >
229282 if ( child . props ?. className ?. includes ( "language-mermaid" ) ) {
230283 return child
231284 }
232285 }
233286 return (
234- < StyledPre { ...preProps } theme = { theme } >
287+ < PreWithCopyButton { ...preProps } theme = { theme || { } } >
235288 { children }
236- </ StyledPre >
289+ </ PreWithCopyButton >
237290 )
238291 } ,
239292 code : ( props : ComponentProps < "code" > ) => {
@@ -253,7 +306,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
253306 } , [ markdown , setMarkdown , theme ] )
254307
255308 return (
256- < div style = { { } } >
309+ < div >
257310 < StyledMarkdown > { reactContent } </ StyledMarkdown >
258311 </ div >
259312 )
0 commit comments