Skip to content

Commit a6319d7

Browse files
Elvis Saraviaclaude
authored andcommitted
Fix Copy Page feature for mobile browsers
Enhanced the copyToClipboard function with better mobile browser compatibility: - Position textarea in viewport (0,0) instead of off-screen for mobile compatibility - Remove readonly attribute which prevents selection on mobile browsers - Add 100ms delay before execCommand to allow mobile selection to process - Implement iOS-specific selection handling using Range API - Set minimum 16px font size to prevent iOS zoom - Make textarea invisible using opacity and 1px dimensions - Add proper cleanup with delayed element removal Tested successfully on mobile browsers over local network. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent f476087 commit a6319d7

File tree

1 file changed

+62
-19
lines changed

1 file changed

+62
-19
lines changed

components/CopyPageDropdown.tsx

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const CopyPageDropdown: React.FC = () => {
3434

3535
// Cross-platform copy function with mobile fallback
3636
const copyToClipboard = async (text: string): Promise<void> => {
37-
// Try modern Clipboard API first
37+
// Try modern Clipboard API first (works in HTTPS contexts)
3838
if (navigator.clipboard && window.isSecureContext) {
3939
try {
4040
await navigator.clipboard.writeText(text);
@@ -44,28 +44,71 @@ const CopyPageDropdown: React.FC = () => {
4444
}
4545
}
4646

47-
// Fallback for mobile browsers and older browsers
48-
const textArea = document.createElement('textarea');
49-
textArea.value = text;
47+
// Enhanced fallback for mobile browsers
48+
return new Promise<void>((resolve, reject) => {
49+
const textArea = document.createElement('textarea');
50+
textArea.value = text;
5051

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);
52+
// Position off-screen but keep it in viewport for mobile compatibility
53+
textArea.style.position = 'fixed';
54+
textArea.style.top = '0';
55+
textArea.style.left = '0';
56+
textArea.style.width = '1px';
57+
textArea.style.height = '1px';
58+
textArea.style.padding = '0';
59+
textArea.style.border = 'none';
60+
textArea.style.outline = 'none';
61+
textArea.style.boxShadow = 'none';
62+
textArea.style.background = 'transparent';
63+
textArea.style.fontSize = '16px'; // Prevent iOS zoom
64+
textArea.style.opacity = '0';
65+
textArea.style.pointerEvents = 'none';
5666

57-
// Focus and select the text
58-
textArea.focus();
59-
textArea.select();
67+
// Don't use readonly - it prevents selection on some mobile browsers
68+
document.body.appendChild(textArea);
6069

61-
try {
62-
const successful = document.execCommand('copy');
63-
if (!successful) {
64-
throw new Error('execCommand failed');
70+
// Focus and select
71+
textArea.focus();
72+
73+
const isIOS = /ipad|iphone/i.test(navigator.userAgent);
74+
75+
if (isIOS) {
76+
// iOS-specific handling
77+
const range = document.createRange();
78+
range.selectNodeContents(textArea);
79+
const selection = window.getSelection();
80+
if (selection) {
81+
selection.removeAllRanges();
82+
selection.addRange(range);
83+
}
84+
textArea.setSelectionRange(0, text.length);
85+
} else {
86+
// Standard selection for Android and others
87+
textArea.select();
88+
textArea.setSelectionRange(0, text.length);
6589
}
66-
} finally {
67-
document.body.removeChild(textArea);
68-
}
90+
91+
// Wait a bit for selection to take effect on mobile
92+
setTimeout(() => {
93+
try {
94+
const successful = document.execCommand('copy');
95+
96+
// Clean up after a short delay
97+
setTimeout(() => {
98+
document.body.removeChild(textArea);
99+
}, 100);
100+
101+
if (successful) {
102+
resolve();
103+
} else {
104+
reject(new Error('Copy command was unsuccessful'));
105+
}
106+
} catch (error) {
107+
document.body.removeChild(textArea);
108+
reject(error);
109+
}
110+
}, 100);
111+
});
69112
};
70113

71114
// Fetch page content from API

0 commit comments

Comments
 (0)