Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down
13 changes: 12 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
48 changes: 35 additions & 13 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,15 @@ document.addEventListener('DOMContentLoaded', () => {
}

const renderOutput = parts => {
output.innerHTML = `<h2>Generated Descriptions</h2>${parts.join('')}`;
// Copy All only earns its place once there is more than one description.
const actions = parts.length > 1
? `<div class="output-actions">
<button type="button" class="btn btn--ghost btn--sm" data-copy-all>Copy all ${parts.length}</button>
<span class="copy-feedback" role="status" aria-live="polite"></span>
</div>`
: '';
output.innerHTML =
`<div class="output-head"><h2>Generated Descriptions</h2>${actions}</div>${parts.join('')}`;
};

// -------------------------------------------------- manual generation ----
Expand Down Expand Up @@ -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';
Expand All @@ -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();
Expand Down
Loading