Skip to content

Commit f848a9c

Browse files
Merge pull request #136 from CodeForPhilly/09h8oh-codex/add-copy-to-clipboard-button-for-favorites
Add copy favorites button under sort
2 parents aec3979 + 02c6fe4 commit f848a9c

File tree

1 file changed

+155
-9
lines changed

1 file changed

+155
-9
lines changed

src/components/Explorer.vue

Lines changed: 155 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,25 @@
310310
v-model="sort"
311311
@close="toggleSort"
312312
/>
313-
</div>
314313
</div>
315314
</div>
316-
<form
317-
v-if="!favorites"
318-
class="filters"
319-
id="form"
320-
@submit.prevent="submit"
315+
</div>
316+
<div v-if="favorites" class="copy-clipboard" aria-live="polite">
317+
<button
318+
class="primary primary-bar copy-button"
319+
:class="{ 'copied': isCopied }"
320+
@click="copyFavorites"
321+
:disabled="isCopied"
322+
>
323+
<span v-if="isCopied">✓ Copied!</span>
324+
<span v-else>Copy to Clipboard</span>
325+
</button>
326+
</div>
327+
<form
328+
v-if="!favorites"
329+
class="filters"
330+
id="form"
331+
@submit.prevent="submit"
321332
>
322333
<div class="inner-controls">
323334
<div class="search-mobile-box">
@@ -873,6 +884,7 @@ export default {
873884
question: 0,
874885
questionDetails,
875886
twoUpIndex,
887+
isCopied: false,
876888
};
877889
},
878890
computed: {
@@ -1474,6 +1486,94 @@ export default {
14741486
this.results = this.results.filter((result) => result._id !== _id);
14751487
}
14761488
},
1489+
copyFavorites() {
1490+
// Create HTML version with italicized scientific names
1491+
const htmlLines = this.results.map(
1492+
(p) => `<li>${p["Common Name"]} (<i>${p["Scientific Name"]}</i>)</li>`
1493+
);
1494+
const htmlContent = `<ul>
1495+
${htmlLines.join("\n")}
1496+
</ul>`;
1497+
1498+
// Plain text version as fallback (without HTML formatting)
1499+
const plainTextLines = this.results.map(
1500+
(p) => `${p["Common Name"]} (${p["Scientific Name"]})`
1501+
);
1502+
const plainText = plainTextLines.join("\n");
1503+
1504+
// Set copied state for button feedback
1505+
this.isCopied = true;
1506+
1507+
// First try to copy with HTML formatting using document.execCommand
1508+
let copySuccessful = false;
1509+
1510+
// Only try HTML approach if execCommand is available
1511+
if (document.execCommand) {
1512+
try {
1513+
// Create a temporary div to hold our HTML content
1514+
const tempDiv = document.createElement('div');
1515+
tempDiv.innerHTML = htmlContent;
1516+
tempDiv.style.position = 'absolute';
1517+
tempDiv.style.left = '-9999px';
1518+
document.body.appendChild(tempDiv);
1519+
1520+
// Select the content
1521+
const range = document.createRange();
1522+
range.selectNode(tempDiv);
1523+
const selection = window.getSelection();
1524+
selection.removeAllRanges();
1525+
selection.addRange(range);
1526+
1527+
// Execute copy command
1528+
copySuccessful = document.execCommand('copy');
1529+
1530+
// Clean up
1531+
selection.removeAllRanges();
1532+
document.body.removeChild(tempDiv);
1533+
} catch (err) {
1534+
console.error("Failed to copy with HTML formatting", err);
1535+
copySuccessful = false;
1536+
}
1537+
}
1538+
1539+
// If HTML copy failed, try clipboard API with plain text
1540+
if (!copySuccessful && navigator.clipboard && navigator.clipboard.writeText) {
1541+
navigator.clipboard.writeText(plainText).catch((err) => {
1542+
console.error("Failed to copy with clipboard API", err);
1543+
this.isCopied = false;
1544+
});
1545+
}
1546+
// Last resort: try plain text with execCommand if everything else failed
1547+
else if (!copySuccessful) {
1548+
try {
1549+
const textarea = document.createElement("textarea");
1550+
textarea.value = plainText;
1551+
textarea.style.position = 'absolute';
1552+
textarea.style.left = '-9999px';
1553+
document.body.appendChild(textarea);
1554+
textarea.select();
1555+
1556+
copySuccessful = document.execCommand("copy");
1557+
if (!copySuccessful) {
1558+
console.error("Failed to copy with execCommand");
1559+
this.isCopied = false;
1560+
}
1561+
} catch (err) {
1562+
console.error("Failed to copy favorites", err);
1563+
this.isCopied = false;
1564+
} finally {
1565+
const textareaElement = document.querySelector('textarea[style*="position: absolute"]');
1566+
if (textareaElement && document.body.contains(textareaElement)) {
1567+
document.body.removeChild(textareaElement);
1568+
}
1569+
}
1570+
}
1571+
1572+
// Reset button after 2 seconds
1573+
setTimeout(() => {
1574+
this.isCopied = false;
1575+
}, 2000);
1576+
},
14771577
renderFavorite(_id) {
14781578
return this.$store.state.favorites.has(_id)
14791579
? "favorite"
@@ -1649,6 +1749,13 @@ button.text {
16491749
margin-bottom: 16px;
16501750
}
16511751
1752+
.copy-clipboard {
1753+
max-width: 350px;
1754+
margin: auto;
1755+
margin-bottom: 16px;
1756+
}
1757+
1758+
16521759
button.favorites {
16531760
width: 100%;
16541761
display: block;
@@ -1658,12 +1765,51 @@ button.favorites {
16581765
margin-right: 24px;
16591766
}
16601767
1661-
button.favorites[disabled] {
1768+
button.favorites[disabled], button.copy-button[disabled] {
16621769
opacity: 0.5;
1770+
cursor: not-allowed;
16631771
}
16641772
1665-
button.favorites .favorites-label {
1666-
display: none;
1773+
.copy-button {
1774+
transition: all 0.3s ease;
1775+
position: relative;
1776+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1777+
}
1778+
1779+
.copy-button:active {
1780+
transform: translateY(2px);
1781+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
1782+
}
1783+
1784+
.copy-button.copied {
1785+
background-color: #38a169 !important;
1786+
transition: all 0.3s ease;
1787+
animation: pulse 0.5s ease-in-out;
1788+
}
1789+
1790+
@keyframes pulse {
1791+
0% { transform: scale(1); }
1792+
50% { transform: scale(1.05); }
1793+
100% { transform: scale(1); }
1794+
}
1795+
1796+
.copy-button span {
1797+
transition: opacity 0.2s ease;
1798+
}
1799+
1800+
@media (hover: hover) {
1801+
.copy-button:hover:not([disabled]) {
1802+
background-color: #c85d25;
1803+
transform: translateY(1px);
1804+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
1805+
}
1806+
}
1807+
1808+
.copy-clipboard {
1809+
position: relative;
1810+
margin-top: 10px;
1811+
display: flex;
1812+
justify-content: center;
16671813
}
16681814
16691815
.list-button {

0 commit comments

Comments
 (0)