Skip to content

Commit 03d4410

Browse files
Adding copy button to code blocks (RooCodeInc#3011)
* feat: add copy button to code blocks * Adding ability to copy Code blocks * feat: add OpenRouter base URL and balance display component --------- Co-authored-by: ShlomoCode <[email protected]>
1 parent 79b76fd commit 03d4410

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
lines changed

.changeset/nervous-eels-look.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": minor
3+
---
4+
5+
Added copy button to code blocks.

webview-ui/src/components/common/MarkdownBlock.tsx

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { memo, useEffect } from "react"
1+
import React, { memo, useEffect, useRef, useState } from "react"
22
import type { ComponentProps } from "react"
33
import { useRemark } from "react-remark"
44
import 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+
94112
const 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+
200252
const 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

Comments
 (0)