mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Merge pull request #855 from hannesrudolph/patch-5
Add copy button to prompt preview view
This commit is contained in:
commit
da2fe80a9b
5 changed files with 118 additions and 25 deletions
5
.changeset/cyan-insects-marry.md
Normal file
5
.changeset/cyan-insects-marry.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"roo-cline": patch
|
||||
---
|
||||
|
||||
Add a copy button to the recent tasks
|
||||
|
|
@ -2,6 +2,7 @@ import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-u
|
|||
import deepEqual from "fast-deep-equal"
|
||||
import React, { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useSize } from "react-use"
|
||||
import { useCopyToClipboard } from "../../utils/clipboard"
|
||||
import {
|
||||
ClineApiReqInfo,
|
||||
ClineAskUseMcpServer,
|
||||
|
|
@ -985,6 +986,7 @@ export const ProgressIndicator = () => (
|
|||
|
||||
const Markdown = memo(({ markdown, partial }: { markdown?: string; partial?: boolean }) => {
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const { copyWithFeedback } = useCopyToClipboard(200) // shorter feedback duration for copy button flash
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -1021,15 +1023,16 @@ const Markdown = memo(({ markdown, partial }: { markdown?: string; partial?: boo
|
|||
background: "var(--vscode-editor-background)",
|
||||
transition: "background 0.2s ease-in-out",
|
||||
}}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(markdown)
|
||||
// Flash the button background briefly to indicate success
|
||||
const button = document.activeElement as HTMLElement
|
||||
if (button) {
|
||||
button.style.background = "var(--vscode-button-background)"
|
||||
setTimeout(() => {
|
||||
button.style.background = ""
|
||||
}, 200)
|
||||
onClick={async () => {
|
||||
const success = await copyWithFeedback(markdown)
|
||||
if (success) {
|
||||
const button = document.activeElement as HTMLElement
|
||||
if (button) {
|
||||
button.style.background = "var(--vscode-button-background)"
|
||||
setTimeout(() => {
|
||||
button.style.background = ""
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
}}
|
||||
title="Copy as markdown">
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useExtensionState } from "../../context/ExtensionStateContext"
|
|||
import { vscode } from "../../utils/vscode"
|
||||
import { memo } from "react"
|
||||
import { formatLargeNumber } from "../../utils/format"
|
||||
import { useCopyToClipboard } from "../../utils/clipboard"
|
||||
|
||||
type HistoryPreviewProps = {
|
||||
showHistoryView: () => void
|
||||
|
|
@ -10,6 +11,7 @@ type HistoryPreviewProps = {
|
|||
|
||||
const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
||||
const { taskHistory } = useExtensionState()
|
||||
const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard()
|
||||
const handleHistorySelect = (id: string) => {
|
||||
vscode.postMessage({ type: "showTaskWithId", text: id })
|
||||
}
|
||||
|
|
@ -31,8 +33,30 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
|||
|
||||
return (
|
||||
<div style={{ flexShrink: 0 }}>
|
||||
{showCopyFeedback && <div className="copy-modal">Prompt Copied to Clipboard</div>}
|
||||
<style>
|
||||
{`
|
||||
.copy-modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--vscode-notifications-background);
|
||||
color: var(--vscode-notifications-foreground);
|
||||
padding: 12px 20px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
z-index: 1000;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
.copy-button {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.history-preview-item:hover .copy-button {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.history-preview-item {
|
||||
background-color: color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 65%, transparent);
|
||||
border-radius: 4px;
|
||||
|
|
@ -79,8 +103,14 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
|||
key={item.id}
|
||||
className="history-preview-item"
|
||||
onClick={() => handleHistorySelect(item.id)}>
|
||||
<div style={{ padding: "12px" }}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<div style={{ padding: "12px", position: "relative" }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "8px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
|
|
@ -90,6 +120,14 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
|||
}}>
|
||||
{formatDate(item.ts)}
|
||||
</span>
|
||||
<button
|
||||
title="Copy Prompt"
|
||||
aria-label="Copy Prompt"
|
||||
className="copy-button"
|
||||
data-appearance="icon"
|
||||
onClick={(e) => copyWithFeedback(item.task, e)}>
|
||||
<span className="codicon codicon-copy"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import React, { memo, useMemo, useState, useEffect } from "react"
|
|||
import { Fzf } from "fzf"
|
||||
import { formatLargeNumber } from "../../utils/format"
|
||||
import { highlightFzfMatch } from "../../utils/highlight"
|
||||
import { useCopyToClipboard } from "../../utils/clipboard"
|
||||
|
||||
type HistoryViewProps = {
|
||||
onDone: () => void
|
||||
|
|
@ -18,7 +19,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [sortOption, setSortOption] = useState<SortOption>("newest")
|
||||
const [lastNonRelevantSort, setLastNonRelevantSort] = useState<SortOption | null>("newest")
|
||||
const [showCopyModal, setShowCopyModal] = useState(false)
|
||||
const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard()
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery && sortOption !== "mostRelevant" && !lastNonRelevantSort) {
|
||||
|
|
@ -38,17 +39,6 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
vscode.postMessage({ type: "deleteTaskWithId", text: id })
|
||||
}
|
||||
|
||||
const handleCopyTask = async (e: React.MouseEvent, task: string) => {
|
||||
e.stopPropagation()
|
||||
try {
|
||||
await navigator.clipboard.writeText(task)
|
||||
setShowCopyModal(true)
|
||||
setTimeout(() => setShowCopyModal(false), 2000)
|
||||
} catch (error) {
|
||||
console.error("Failed to copy to clipboard:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (timestamp: number) => {
|
||||
const date = new Date(timestamp)
|
||||
return date
|
||||
|
|
@ -144,7 +134,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
}
|
||||
`}
|
||||
</style>
|
||||
{showCopyModal && <div className="copy-modal">Prompt Copied to Clipboard</div>}
|
||||
{showCopyFeedback && <div className="copy-modal">Prompt Copied to Clipboard</div>}
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
|
|
@ -271,7 +261,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
title="Copy Prompt"
|
||||
className="copy-button"
|
||||
data-appearance="icon"
|
||||
onClick={(e) => handleCopyTask(e, item.task)}>
|
||||
onClick={(e) => copyWithFeedback(item.task, e)}>
|
||||
<span className="codicon codicon-copy"></span>
|
||||
</button>
|
||||
<button
|
||||
|
|
|
|||
57
webview-ui/src/utils/clipboard.ts
Normal file
57
webview-ui/src/utils/clipboard.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { useState, useCallback } from "react"
|
||||
|
||||
/**
|
||||
* Options for copying text to clipboard
|
||||
*/
|
||||
interface CopyOptions {
|
||||
/** Duration in ms to show success feedback (default: 2000) */
|
||||
feedbackDuration?: number
|
||||
/** Optional callback when copy succeeds */
|
||||
onSuccess?: () => void
|
||||
/** Optional callback when copy fails */
|
||||
onError?: (error: Error) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy text to clipboard with error handling
|
||||
*/
|
||||
export const copyToClipboard = async (text: string, options?: CopyOptions): Promise<boolean> => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
options?.onSuccess?.()
|
||||
return true
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error("Failed to copy to clipboard")
|
||||
options?.onError?.(err)
|
||||
console.error("Failed to copy to clipboard:", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook for managing clipboard copy state with feedback
|
||||
*/
|
||||
export const useCopyToClipboard = (feedbackDuration = 2000) => {
|
||||
const [showCopyFeedback, setShowCopyFeedback] = useState(false)
|
||||
|
||||
const copyWithFeedback = useCallback(
|
||||
async (text: string, e?: React.MouseEvent) => {
|
||||
e?.stopPropagation()
|
||||
|
||||
const success = await copyToClipboard(text, {
|
||||
onSuccess: () => {
|
||||
setShowCopyFeedback(true)
|
||||
setTimeout(() => setShowCopyFeedback(false), feedbackDuration)
|
||||
},
|
||||
})
|
||||
|
||||
return success
|
||||
},
|
||||
[feedbackDuration],
|
||||
)
|
||||
|
||||
return {
|
||||
showCopyFeedback,
|
||||
copyWithFeedback,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue