Skip to content

Commit f476087

Browse files
Elvis Saraviaclaude
authored andcommitted
Fix Copy Page feature for mobile browsers
Implemented a cross-platform clipboard solution with fallback mechanism to resolve mobile browser compatibility issues. Changes: - Added copyToClipboard() helper function that tries modern Clipboard API first - Falls back to document.execCommand('copy') for mobile browsers with user agent restrictions - Updated handleCopyAsMarkdown() and handleCopyFromModal() to use new helper - Fixes "user agent or platform" permission errors on iOS Safari and mobile Chrome The fallback method uses an invisible textarea element positioned off-screen, which is the standard approach for mobile clipboard compatibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5e99d9c commit f476087

1 file changed

Lines changed: 38 additions & 2 deletions

File tree

components/CopyPageDropdown.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,42 @@ const CopyPageDropdown: React.FC = () => {
3232
return `${cleanPath}.en.mdx`;
3333
};
3434

35+
// Cross-platform copy function with mobile fallback
36+
const copyToClipboard = async (text: string): Promise<void> => {
37+
// Try modern Clipboard API first
38+
if (navigator.clipboard && window.isSecureContext) {
39+
try {
40+
await navigator.clipboard.writeText(text);
41+
return;
42+
} catch (error) {
43+
console.warn('Clipboard API failed, trying fallback:', error);
44+
}
45+
}
46+
47+
// Fallback for mobile browsers and older browsers
48+
const textArea = document.createElement('textarea');
49+
textArea.value = text;
50+
51+
// Make the textarea invisible and position it off-screen
52+
textArea.style.position = 'fixed';
53+
textArea.style.left = '-999999px';
54+
textArea.style.top = '-999999px';
55+
document.body.appendChild(textArea);
56+
57+
// Focus and select the text
58+
textArea.focus();
59+
textArea.select();
60+
61+
try {
62+
const successful = document.execCommand('copy');
63+
if (!successful) {
64+
throw new Error('execCommand failed');
65+
}
66+
} finally {
67+
document.body.removeChild(textArea);
68+
}
69+
};
70+
3571
// Fetch page content from API
3672
const fetchPageContent = async (): Promise<string> => {
3773
const pagePath = getPagePath();
@@ -53,7 +89,7 @@ const CopyPageDropdown: React.FC = () => {
5389
try {
5490
setCopyStatus('copying');
5591
const content = await fetchPageContent();
56-
await navigator.clipboard.writeText(content);
92+
await copyToClipboard(content);
5793
setCopyStatus('success');
5894
setTimeout(() => {
5995
setCopyStatus('idle');
@@ -105,7 +141,7 @@ const CopyPageDropdown: React.FC = () => {
105141
// Copy markdown from modal
106142
const handleCopyFromModal = async () => {
107143
try {
108-
await navigator.clipboard.writeText(markdownContent);
144+
await copyToClipboard(markdownContent);
109145
alert('Content copied to clipboard!');
110146
} catch (error) {
111147
console.error('Failed to copy:', error);

0 commit comments

Comments
 (0)