diff --git a/README.md b/README.md
index 6d1dbf1..4d4a00a 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ If you're lucky, you might encounter a shiny one ✨
✅ "Check in" on Campfire when you arrive
```
-Each result comes with the Pokémon's artwork and a **Copy** button.
+Each result comes with the Pokémon's artwork and a **Copy** button. When there's more than one, a **Copy all** button in the results header puts the whole batch on the clipboard, blank-line separated.
---
diff --git a/index.html b/index.html
index 892e5e0..b3c5f8b 100644
--- a/index.html
+++ b/index.html
@@ -581,10 +581,21 @@
}
/* ===== generated output ===== */
- #output>h2 {
+ .output-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 10px;
margin-bottom: 14px;
}
+ .output-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ }
+
.description {
display: flex;
flex-wrap: wrap;
diff --git a/main.js b/main.js
index 4e0f511..a91e0b6 100644
--- a/main.js
+++ b/main.js
@@ -353,7 +353,15 @@ document.addEventListener('DOMContentLoaded', () => {
}
const renderOutput = parts => {
- output.innerHTML = `
Generated Descriptions
${parts.join('')}`;
+ // Copy All only earns its place once there is more than one description.
+ const actions = parts.length > 1
+ ? `
+
+
+
`
+ : '';
+ output.innerHTML =
+ `Generated Descriptions
${actions}${parts.join('')}`;
};
// -------------------------------------------------- manual generation ----
@@ -553,19 +561,11 @@ document.addEventListener('DOMContentLoaded', () => {
bulkCustomLocation.addEventListener('input', debounce(() => persist({ bulkCustomLocation: bulkCustomLocation.value }), 400));
customLocationInput.addEventListener('input', debounce(() => persist({ customLocation: customLocationInput.value }), 400));
- // One delegated handler for every Copy button, present and future.
- output.addEventListener('click', async event => {
- const button = event.target.closest('[data-copy]');
- if (!button) return;
-
- const card = button.closest('.description');
- const textarea = card?.querySelector('textarea');
- const feedback = card?.querySelector('.copy-feedback');
- if (!textarea || !feedback) return;
-
+ async function copyToClipboard(text, feedback, successLabel = 'Copied') {
+ if (!feedback) return;
try {
- await navigator.clipboard.writeText(textarea.value);
- feedback.textContent = 'Copied';
+ await navigator.clipboard.writeText(text);
+ feedback.textContent = successLabel;
feedback.style.color = 'var(--live)';
} catch (error) {
feedback.textContent = 'Copy failed';
@@ -574,6 +574,28 @@ document.addEventListener('DOMContentLoaded', () => {
}
feedback.style.display = 'inline';
setTimeout(() => { feedback.style.display = 'none'; }, 5000);
+ }
+
+ // One delegated handler for every Copy button, present and future.
+ output.addEventListener('click', event => {
+ const copyAll = event.target.closest('[data-copy-all]');
+ if (copyAll) {
+ const texts = [...output.querySelectorAll('.description textarea')].map(area => area.value);
+ if (!texts.length) return;
+ return copyToClipboard(
+ texts.join('\n\n'),
+ copyAll.parentElement.querySelector('.copy-feedback'),
+ `Copied ${texts.length}`
+ );
+ }
+
+ const button = event.target.closest('[data-copy]');
+ if (!button) return;
+ const card = button.closest('.description');
+ return copyToClipboard(
+ card?.querySelector('textarea')?.value ?? '',
+ card?.querySelector('.copy-feedback')
+ );
});
restore();