feat: add copy for format data#1074
Conversation
WalkthroughAdded a reusable ChangesClipboard Copy UI Integration
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/run/RunContent.tsx`:
- Around line 2192-2216: In RunContent.tsx the displayed text in the Typography
(uses searchData[currentSearchIndex].text) isn't stringifying objects while
CopyButton does, causing a mismatch; update the display logic around the
Typography (and the value passed to CopyButton if needed) to use the same
normalization used for copying—e.g. detect when
searchData[currentSearchIndex].text is an object and replace it with
JSON.stringify(searchData[currentSearchIndex].text, null, 2) so both the
rendered pre block and CopyButton show/copy identical JSON text.
- Around line 57-86: The CopyButton component's setTimeout in handleCopy can
call setCopied after unmount; modify CopyButton to store the timeout id (e.g.,
via a ref) and clear any existing timeout before setting a new one, and add a
useEffect cleanup that clears the timeout on unmount; update references in
handleCopy, the copied state logic, and ensure the timeout ref is cleared after
executing to prevent memory leaks.
- Around line 57-86: The CopyButton's handleCopy calls
navigator.clipboard.writeText without availability checks or error handling;
update handleCopy in the CopyButton component to (1) guard for
navigator.clipboard and try navigator.clipboard.writeText(content) inside a
try/catch, (2) if unavailable or the call fails, fall back to creating a
temporary textarea, select its contents and call document.execCommand('copy'),
(3) set copied state and show failure handling on catch (reset state and/or
surface a tooltip/error) consistent with the ApiKey.tsx clipboard pattern;
reference the CopyButton component and its handleCopy function and mirror the
fallback/error behavior used in ApiKey.tsx.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7863b65e-10f7-4842-9348-48c79cd57312
📒 Files selected for processing (1)
src/components/run/RunContent.tsx
| const CopyButton: React.FC<{ content: string; darkMode: boolean }> = ({ content, darkMode }) => { | ||
| const [copied, setCopied] = useState(false); | ||
| const handleCopy = () => { | ||
| navigator.clipboard.writeText(content); | ||
| setCopied(true); | ||
| setTimeout(() => setCopied(false), 2000); | ||
| }; | ||
| return ( | ||
| <Tooltip title={copied ? 'Copied!' : 'Copy'} placement="left"> | ||
| <IconButton | ||
| onClick={handleCopy} | ||
| size="small" | ||
| sx={{ | ||
| position: 'absolute', | ||
| top: 8, | ||
| right: 20, | ||
| color: copied ? '#4caf50' : (darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.35)'), | ||
| backgroundColor: darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)', | ||
| '&:hover': { | ||
| color: copied ? '#4caf50' : '#FF00C3', | ||
| backgroundColor: darkMode ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)', | ||
| }, | ||
| zIndex: 1, | ||
| }} | ||
| > | ||
| {copied ? <Check fontSize="small" /> : <ContentCopy fontSize="small" />} | ||
| </IconButton> | ||
| </Tooltip> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Clean up timeout on unmount to prevent memory leaks.
The setTimeout used to reset the copied state is not cleaned up if the component unmounts before the 2-second delay completes. This could lead to a "setState on unmounted component" warning or minor memory leak.
♻️ Proposed fix using useEffect cleanup
const CopyButton: React.FC<{ content: string; darkMode: boolean }> = ({ content, darkMode }) => {
const [copied, setCopied] = useState(false);
+ const timeoutRef = React.useRef<NodeJS.Timeout>();
+
+ React.useEffect(() => {
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, []);
+
const handleCopy = () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
navigator.clipboard.writeText(content);
setCopied(true);
- setTimeout(() => setCopied(false), 2000);
+ timeoutRef.current = setTimeout(() => setCopied(false), 2000);
};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/run/RunContent.tsx` around lines 57 - 86, The CopyButton
component's setTimeout in handleCopy can call setCopied after unmount; modify
CopyButton to store the timeout id (e.g., via a ref) and clear any existing
timeout before setting a new one, and add a useEffect cleanup that clears the
timeout on unmount; update references in handleCopy, the copied state logic, and
ensure the timeout ref is cleared after executing to prevent memory leaks.
Add error handling and browser fallback for clipboard API.
The CopyButton component calls navigator.clipboard.writeText without checking availability or handling errors. The existing clipboard pattern in ApiKey.tsx (lines 96-126) guards navigator.clipboard, provides a document.execCommand('copy') fallback, and shows user notifications on success/failure. This new component should match that contract to avoid silent failures in older browsers or non-HTTPS contexts.
🔒 Proposed fix to add guards, fallback, and error handling
-const CopyButton: React.FC<{ content: string; darkMode: boolean }> = ({ content, darkMode }) => {
+const CopyButton: React.FC<{ content: string; darkMode: boolean }> = ({ content, darkMode }) => {
const [copied, setCopied] = useState(false);
+
const handleCopy = () => {
- navigator.clipboard.writeText(content);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
+ if (navigator.clipboard) {
+ navigator.clipboard.writeText(content)
+ .then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ })
+ .catch((err) => {
+ console.error('Failed to copy to clipboard:', err);
+ // Optionally show error notification here
+ });
+ } else {
+ // Fallback for older browsers
+ const textarea = document.createElement('textarea');
+ textarea.value = content;
+ textarea.style.position = 'fixed';
+ textarea.style.opacity = '0';
+ document.body.appendChild(textarea);
+ textarea.focus();
+ textarea.select();
+ try {
+ document.execCommand('copy');
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error('Fallback copy failed:', err);
+ } finally {
+ document.body.removeChild(textarea);
+ }
+ }
};
+
return (
<Tooltip title={copied ? 'Copied!' : 'Copy'} placement="left">
<IconButtonAs per the existing clipboard pattern in src/components/api/ApiKey.tsx lines 96-126, which guards navigator.clipboard availability and provides a document.execCommand fallback.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const CopyButton: React.FC<{ content: string; darkMode: boolean }> = ({ content, darkMode }) => { | |
| const [copied, setCopied] = useState(false); | |
| const handleCopy = () => { | |
| navigator.clipboard.writeText(content); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| }; | |
| return ( | |
| <Tooltip title={copied ? 'Copied!' : 'Copy'} placement="left"> | |
| <IconButton | |
| onClick={handleCopy} | |
| size="small" | |
| sx={{ | |
| position: 'absolute', | |
| top: 8, | |
| right: 20, | |
| color: copied ? '#4caf50' : (darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.35)'), | |
| backgroundColor: darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)', | |
| '&:hover': { | |
| color: copied ? '#4caf50' : '#FF00C3', | |
| backgroundColor: darkMode ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)', | |
| }, | |
| zIndex: 1, | |
| }} | |
| > | |
| {copied ? <Check fontSize="small" /> : <ContentCopy fontSize="small" />} | |
| </IconButton> | |
| </Tooltip> | |
| ); | |
| }; | |
| const CopyButton: React.FC<{ content: string; darkMode: boolean }> = ({ content, darkMode }) => { | |
| const [copied, setCopied] = useState(false); | |
| const handleCopy = () => { | |
| if (navigator.clipboard) { | |
| navigator.clipboard.writeText(content) | |
| .then(() => { | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| }) | |
| .catch((err) => { | |
| console.error('Failed to copy to clipboard:', err); | |
| // Optionally show error notification here | |
| }); | |
| } else { | |
| // Fallback for older browsers | |
| const textarea = document.createElement('textarea'); | |
| textarea.value = content; | |
| textarea.style.position = 'fixed'; | |
| textarea.style.opacity = '0'; | |
| document.body.appendChild(textarea); | |
| textarea.focus(); | |
| textarea.select(); | |
| try { | |
| document.execCommand('copy'); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| } catch (err) { | |
| console.error('Fallback copy failed:', err); | |
| } finally { | |
| document.body.removeChild(textarea); | |
| } | |
| } | |
| }; | |
| return ( | |
| <Tooltip title={copied ? 'Copied!' : 'Copy'} placement="left"> | |
| <IconButton | |
| onClick={handleCopy} | |
| size="small" | |
| sx={{ | |
| position: 'absolute', | |
| top: 8, | |
| right: 20, | |
| color: copied ? '`#4caf50`' : (darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.35)'), | |
| backgroundColor: darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)', | |
| '&:hover': { | |
| color: copied ? '`#4caf50`' : '`#FF00C3`', | |
| backgroundColor: darkMode ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)', | |
| }, | |
| zIndex: 1, | |
| }} | |
| > | |
| {copied ? <Check fontSize="small" /> : <ContentCopy fontSize="small" />} | |
| </IconButton> | |
| </Tooltip> | |
| ); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/run/RunContent.tsx` around lines 57 - 86, The CopyButton's
handleCopy calls navigator.clipboard.writeText without availability checks or
error handling; update handleCopy in the CopyButton component to (1) guard for
navigator.clipboard and try navigator.clipboard.writeText(content) inside a
try/catch, (2) if unavailable or the call fails, fall back to creating a
temporary textarea, select its contents and call document.execCommand('copy'),
(3) set copied state and show failure handling on catch (reset state and/or
surface a tooltip/error) consistent with the ApiKey.tsx clipboard pattern;
reference the CopyButton component and its handleCopy function and mirror the
fallback/error behavior used in ApiKey.tsx.
| <Box sx={{ position: 'relative' }}> | ||
| <Paper | ||
| sx={{ | ||
| whiteSpace: 'pre-wrap', | ||
| wordBreak: 'break-word', | ||
| fontFamily: 'monospace', | ||
| fontSize: '14px', | ||
| lineHeight: 1.6, | ||
| m: 0 | ||
| p: 2, | ||
| maxHeight: '500px', | ||
| overflow: 'auto', | ||
| backgroundColor: darkMode ? '#1e1e1e' : '#f5f5f5' | ||
| }} | ||
| > | ||
| {searchData[currentSearchIndex].text} | ||
| </Typography> | ||
| </Paper> | ||
| <Typography | ||
| component="pre" | ||
| sx={{ | ||
| whiteSpace: 'pre-wrap', | ||
| wordBreak: 'break-word', | ||
| fontFamily: 'monospace', | ||
| fontSize: '14px', | ||
| lineHeight: 1.6, | ||
| m: 0 | ||
| }} | ||
| > | ||
| {searchData[currentSearchIndex].text} | ||
| </Typography> | ||
| </Paper> | ||
| <CopyButton content={typeof searchData[currentSearchIndex].text === 'object' ? JSON.stringify(searchData[currentSearchIndex].text, null, 2) : searchData[currentSearchIndex].text} darkMode={darkMode} /> | ||
| </Box> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Align display and copy behavior for consistency.
The text display (line 2212) doesn't stringify objects, but the CopyButton (line 2215) does. If searchData[currentSearchIndex].text is an object, users will see [object Object] but copy valid JSON—creating a confusing mismatch. The HTML and Markdown sections handle this consistently by stringifying in both display and copy.
♻️ Proposed fix to stringify consistently
<Typography
component="pre"
sx={{
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
fontFamily: 'monospace',
fontSize: '14px',
lineHeight: 1.6,
m: 0
}}
>
- {searchData[currentSearchIndex].text}
+ {typeof searchData[currentSearchIndex].text === 'object'
+ ? JSON.stringify(searchData[currentSearchIndex].text, null, 2)
+ : searchData[currentSearchIndex].text}
</Typography>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/run/RunContent.tsx` around lines 2192 - 2216, In
RunContent.tsx the displayed text in the Typography (uses
searchData[currentSearchIndex].text) isn't stringifying objects while CopyButton
does, causing a mismatch; update the display logic around the Typography (and
the value passed to CopyButton if needed) to use the same normalization used for
copying—e.g. detect when searchData[currentSearchIndex].text is an object and
replace it with JSON.stringify(searchData[currentSearchIndex].text, null, 2) so
both the rendered pre block and CopyButton show/copy identical JSON text.
What this PR does?
Adds the copy to clipboard functionality for scrape, crawl and search format data except screenshots. This is needed when user wants to quickly copy the data and paste it somewhere. previously they had to manually drag and copy or download the entire file.
Summary by CodeRabbit