Skip to content

feat: add copy for format data#1074

Merged
amhsirak merged 1 commit into
developfrom
copy-formats
Jun 1, 2026
Merged

feat: add copy for format data#1074
amhsirak merged 1 commit into
developfrom
copy-formats

Conversation

@RohitR311

@RohitR311 RohitR311 commented May 23, 2026

Copy link
Copy Markdown
Collaborator

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

  • New Features
    • Added copy-to-clipboard functionality across Run Content, Crawl Results, and Search Results. Copy buttons now appear for text content, HTML, Markdown, Links, and Smart Queries with visual "Copied!" confirmation.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Walkthrough

Added a reusable CopyButton component that copies provided content to the clipboard with a temporary "Copied!" state, then integrated it across multiple accordions in RunContent, Crawl Results, and Search Results UI sections. Text, HTML, and Markdown panels now wrap displayed content in relative containers rendering the copy button alongside the output.

Changes

Clipboard Copy UI Integration

Layer / File(s) Summary
CopyButton component and icon imports
src/components/run/RunContent.tsx
Imports ContentCopy and Check icons. Introduces reusable CopyButton React component that writes content to navigator.clipboard, toggles an internal copied state, and renders an IconButton with tooltip indicating copy status.
RunContent output accordions with copy UI
src/components/run/RunContent.tsx
Integrates CopyButton into Text Content, HTML, Markdown, Links (with deduplication via Set), and Smart Queries accordions. Each accordion wraps content in a relative container and renders the copy button alongside displayed output.
Crawl Results accordions with copy UI
src/components/run/RunContent.tsx
Integrates CopyButton into Crawl Results' Text Content, HTML, Markdown, and Links accordions. Content is stringified when applicable, and each accordion displays a copy button in a relative wrapper. Links accordion deduplicates validLinks using Set.
Search Results accordions with copy UI
src/components/run/RunContent.tsx
Integrates CopyButton into Search Results' Text Content, HTML, Markdown, and Links accordions. Each accordion wraps content in a relative container with a copy button. Links accordion deduplicates validLinks using Set.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • getmaxun/maxun#1057: Both PRs modify src/components/run/RunContent.tsx to add clipboard copy UI, one via a reusable CopyButton component across accordions, the other via an IconButton for copying search URLs.

Suggested reviewers

  • amhsirak

Poem

🐰 A button so neat, with copy so sweet,
Spreads clipboard joy where accordions meet,
Text, HTML, Links—all trimmed with care,
From RunContent's depths to Results laid bare!
Copied! it sings, a temporary cheer. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add copy for format data' is closely related to the main changes, which add copy-to-clipboard functionality for formatted data across multiple components (RunContent, Crawl Results, and Search Results).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copy-formats

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3263dca and f64eac8.

📒 Files selected for processing (1)
  • src/components/run/RunContent.tsx

Comment on lines +57 to +86
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>
);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

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">
       <IconButton

As 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.

Suggested change
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.

Comment on lines +2192 to +2216
<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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

@RohitR311 RohitR311 requested a review from amhsirak June 1, 2026 16:49
@RohitR311 RohitR311 added Type: Feature New features Scope: UI/UX Issues/PRs related to UI/UX labels Jun 1, 2026
@amhsirak amhsirak merged commit 09b55e6 into develop Jun 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: UI/UX Issues/PRs related to UI/UX Type: Feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants