From 268eb1667bd62b326069a387980a610a79873bb0 Mon Sep 17 00:00:00 2001 From: 00xJS <56056764+00xJS@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:40:41 -0700 Subject: [PATCH] Add Copy all button to the bulk results Ten events meant ten Copy clicks. The results header now carries a Copy all button that puts every generated description on the clipboard at once, blank -line separated, labelled with the count. It only appears once there is more than one description, so a single result keeps the header uncluttered. The clipboard call is factored into one helper shared with the per-card buttons rather than duplicated. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- index.html | 13 ++++++++++++- main.js | 48 +++++++++++++++++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 15 deletions(-) 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();