Skip to content

Commit 8bd1066

Browse files
simonwclaude
andauthored
Fix copy button on Mobile Safari, simplify implementation (#171)
Remove touchstart handler that was intercepting button taps and calling synthetic click(), which broke the user gesture chain required by Mobile Safari for clipboard access. Simplify copy button to match text-indentation.html pattern. https://tools.simonwillison.net/claude-code-timeline?url=https%3A%2F%2Fgist.githubusercontent.com%2Fsimonw%2F86d8fcbad4f70661616db0d783e1c43e%2Fraw%2Fd2edbbeb3484169aa769e9eb984e99dbc9760c15%2Findex.jsonl#tz=local&q=&type=all&ct=all&role=all&hide=0&truncate=1&sel=81 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent de78b50 commit 8bd1066

1 file changed

Lines changed: 21 additions & 94 deletions

File tree

render-markdown.html

Lines changed: 21 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -76,24 +76,15 @@
7676
}
7777

7878
#copy-button {
79-
position: relative;
80-
margin: 0 0 0.5em 0;
81-
height: 40px;
82-
min-width: 120px;
83-
background-color: #2ea44f;
84-
border-radius: 4px;
85-
border: none;
86-
display: flex;
87-
align-items: center;
88-
justify-content: center;
79+
background: #2ea44f;
8980
}
9081

91-
#copy-button:active {
92-
background-color: #22863a;
82+
#copy-button:hover {
83+
background: #22863a;
9384
}
9485

95-
.copy-text {
96-
font-size: 14px;
86+
#copy-button.success {
87+
background: #22863a;
9788
}
9889

9990
.controls {
@@ -127,26 +118,6 @@
127118
font-size: 1.2em;
128119
}
129120

130-
/* Message after successful copy */
131-
.copied-message {
132-
position: absolute;
133-
background-color: rgba(0, 0, 0, 0.7);
134-
color: white;
135-
padding: 4px 8px;
136-
border-radius: 4px;
137-
font-size: 14px;
138-
top: -30px;
139-
left: 50%;
140-
transform: translateX(-50%);
141-
opacity: 0;
142-
transition: opacity 0.3s;
143-
pointer-events: none;
144-
}
145-
146-
.show-message {
147-
opacity: 1;
148-
}
149-
150121
@media (max-width: 768px) {
151122
h1 {
152123
font-size: 1.5em;
@@ -377,15 +348,13 @@ <h1>Render Markdown</h1>
377348
<div id="preview"></div>
378349

379350
<h3>HTML Output</h3>
380-
<button id="copy-button" aria-label="Copy HTML to clipboard">
381-
<span class="copy-text">Copy HTML</span>
382-
</button>
351+
<button id="copy-button">Copy HTML</button>
383352

384353
<div class="output-container">
385-
<textarea id="output" readonly></textarea>
354+
<textarea id="output"></textarea>
386355
</div>
387356

388-
<script type="module">
357+
<script>
389358
// Initialize elements
390359
const button = document.getElementById('render-button');
391360
const stripHidden = document.getElementById('strip_hidden');
@@ -527,57 +496,23 @@ <h3>HTML Output</h3>
527496
// Add copy to clipboard functionality
528497
const copyButton = document.getElementById('copy-button');
529498

530-
function showCopySuccess() {
531-
const textSpan = copyButton.querySelector('.copy-text');
532-
const originalText = textSpan.textContent;
533-
textSpan.textContent = '✓ Copied!';
534-
copyButton.style.backgroundColor = '#22863a';
535-
setTimeout(() => {
536-
textSpan.textContent = originalText;
537-
copyButton.style.backgroundColor = '';
538-
}, 2000);
539-
}
540-
541-
function copyWithExecCommand() {
542-
// Focus the textarea and select all content
543-
output.focus();
544-
output.select();
545-
output.setSelectionRange(0, output.value.length);
546-
547-
try {
548-
const successful = document.execCommand('copy');
549-
if (successful) {
550-
showCopySuccess();
551-
return true;
552-
}
553-
} catch (err) {
554-
console.error('execCommand copy failed:', err);
555-
}
556-
return false;
557-
}
558-
559499
copyButton.addEventListener('click', async () => {
560500
if (!output.value) {
561501
return;
562502
}
563-
564-
// Try modern clipboard API first
565-
if (navigator.clipboard && navigator.clipboard.writeText) {
566-
try {
567-
await navigator.clipboard.writeText(output.value);
568-
showCopySuccess();
569-
return;
570-
} catch (err) {
571-
console.warn('navigator.clipboard.writeText failed, trying fallback:', err);
572-
}
573-
}
574-
575-
// Fallback to execCommand
576-
if (!copyWithExecCommand()) {
577-
// Last resort: prompt user to copy manually
578-
output.focus();
579-
output.select();
580-
alert('Press Ctrl+C (Cmd+C on Mac) to copy the selected text.');
503+
try {
504+
await navigator.clipboard.writeText(output.value);
505+
copyButton.textContent = 'Copied!';
506+
copyButton.classList.add('success');
507+
setTimeout(() => {
508+
copyButton.textContent = 'Copy HTML';
509+
copyButton.classList.remove('success');
510+
}, 2000);
511+
} catch (err) {
512+
copyButton.textContent = 'Failed to copy';
513+
setTimeout(() => {
514+
copyButton.textContent = 'Copy HTML';
515+
}, 2000);
581516
}
582517
});
583518

@@ -591,14 +526,6 @@ <h3>HTML Output</h3>
591526
localStorage.setItem('saved', input.value);
592527
});
593528

594-
// Add double-tap zoom prevention
595-
document.addEventListener('touchstart', function(event) {
596-
if (event.target.tagName === 'BUTTON' || event.target.tagName === 'INPUT') {
597-
event.preventDefault();
598-
event.target.click();
599-
}
600-
}, { passive: false });
601-
602529
// Add service worker for offline capabilities if needed
603530
if ('serviceWorker' in navigator) {
604531
window.addEventListener('load', () => {

0 commit comments

Comments
 (0)