310
310
v-model =" sort"
311
311
@close =" toggleSort"
312
312
/>
313
- </div >
314
313
</div >
315
314
</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"
321
332
>
322
333
<div class =" inner-controls" >
323
334
<div class =" search-mobile-box" >
@@ -873,6 +884,7 @@ export default {
873
884
question: 0 ,
874
885
questionDetails,
875
886
twoUpIndex,
887
+ isCopied: false ,
876
888
};
877
889
},
878
890
computed: {
@@ -1474,6 +1486,94 @@ export default {
1474
1486
this .results = this .results .filter ((result ) => result ._id !== _id);
1475
1487
}
1476
1488
},
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
+ },
1477
1577
renderFavorite (_id ) {
1478
1578
return this .$store .state .favorites .has (_id)
1479
1579
? " favorite"
@@ -1649,6 +1749,13 @@ button.text {
1649
1749
margin- bottom: 16px ;
1650
1750
}
1651
1751
1752
+ .copy - clipboard {
1753
+ max- width: 350px ;
1754
+ margin: auto;
1755
+ margin- bottom: 16px ;
1756
+ }
1757
+
1758
+
1652
1759
button .favorites {
1653
1760
width: 100 % ;
1654
1761
display: block;
@@ -1658,12 +1765,51 @@ button.favorites {
1658
1765
margin- right: 24px ;
1659
1766
}
1660
1767
1661
- button .favorites [disabled] {
1768
+ button .favorites [disabled], button . copy - button[disabled] {
1662
1769
opacity: 0.5 ;
1770
+ cursor: not- allowed;
1663
1771
}
1664
1772
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;
1667
1813
}
1668
1814
1669
1815
.list - button {
0 commit comments