this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Replace (most) alert/confirms with alternative UI

Everything might break lol

+423 -124
+67 -8
src/app.css
··· 1401 1401 animation: appear-smooth 0.15s ease-in-out; 1402 1402 width: 16em; 1403 1403 max-width: 90vw; 1404 - overflow: hidden; 1404 + /* overflow: hidden; */ 1405 1405 } 1406 1406 .szh-menu[aria-label='Submenu'] { 1407 1407 background-color: var(--bg-blur-color); ··· 1418 1418 text-shadow: 0 1px 0 var(--bg-color); 1419 1419 line-height: 1.2; 1420 1420 /* border-bottom: 1px solid var(--outline-color); */ 1421 + border-radius: 8px 8px 0 0; 1421 1422 } 1422 1423 .szh-menu__header.plain { 1423 1424 margin-bottom: 0; ··· 1425 1426 } 1426 1427 .szh-menu__header * { 1427 1428 vertical-align: middle; 1429 + } 1430 + .szh-menu.menu-emphasized { 1431 + border-color: var(--outline-hover-color); 1432 + box-shadow: 0 3px 16px -3px var(--drop-shadow-color), 1433 + 0 3px 32px var(--drop-shadow-color), 0 3px 48px var(--drop-shadow-color); 1434 + background-color: var(--bg-color); 1435 + animation-duration: 0.3s; 1436 + animation-timing-function: ease-in-out; 1437 + width: auto; 1438 + } 1439 + .szh-menu .footer { 1440 + margin: 8px 0 -8px; 1441 + padding: 8px 16px; 1442 + color: var(--text-insignificant-color); 1443 + font-size: 90%; 1444 + background-color: var(--bg-faded-color); 1445 + text-shadow: 0 1px 0 var(--bg-color); 1446 + line-height: 1.2; 1447 + display: flex; 1448 + gap: 8px; 1449 + align-items: center; 1450 + border-radius: 0 0 8px 8px; 1428 1451 } 1429 1452 .szh-menu .szh-menu__item { 1430 1453 display: flex; ··· 1498 1521 font-size: inherit; 1499 1522 } 1500 1523 .szh-menu .menu-horizontal { 1501 - display: flex; 1524 + display: grid; 1525 + /* two columns only */ 1526 + grid-template-columns: repeat(2, 1fr); 1502 1527 } 1503 - .szh-menu .menu-horizontal .szh-menu__item { 1504 - flex: 1; 1505 - } 1506 - .szh-menu .menu-horizontal .szh-menu__item:not(:only-child):first-child { 1528 + .szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):first-child, 1529 + .szh-menu .menu-horizontal > *:not(:only-child):first-child .szh-menu__item { 1507 1530 padding-right: 4px !important; 1508 1531 } 1509 1532 .szh-menu 1510 1533 .menu-horizontal 1511 - .szh-menu__item:not(:only-child):not(:first-child):not(:last-child) { 1534 + > .szh-menu__item:not(:only-child):not(:first-child):not(:last-child), 1535 + .szh-menu 1536 + .menu-horizontal 1537 + > *:not(:only-child):not(:first-child):not(:last-child) 1538 + .szh-menu__item { 1512 1539 padding-left: 8px !important; 1513 1540 padding-right: 4px !important; 1514 1541 } 1515 - .szh-menu .menu-horizontal .szh-menu__item:not(:only-child):last-child { 1542 + .szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):last-child, 1543 + .szh-menu .menu-horizontal > *:not(:only-child):last-child .szh-menu__item { 1516 1544 padding-left: 8px !important; 1517 1545 } 1518 1546 .szh-menu .szh-menu__item .menu-shortcut { ··· 1531 1559 } 1532 1560 .szh-menu .szh-menu__item--hover .danger-icon { 1533 1561 color: var(--red-color); 1562 + opacity: 1; 1563 + } 1564 + .szh-menu 1565 + .szh-menu__item:not(.szh-menu__item--disabled):not( 1566 + .szh-menu__item--hover 1567 + ).danger { 1568 + color: var(--red-color); 1569 + } 1570 + .szh-menu 1571 + .szh-menu__item:not(.szh-menu__item--disabled):not( 1572 + .szh-menu__item--hover 1573 + ).danger 1574 + .icon { 1534 1575 opacity: 1; 1535 1576 } 1536 1577 ··· 1656 1697 } 1657 1698 .toastify-bottom { 1658 1699 margin-bottom: env(safe-area-inset-bottom); 1700 + } 1701 + 1702 + /* TOAST - ALERT */ 1703 + 1704 + :root .toastify.alert { 1705 + z-index: 1001; 1706 + box-shadow: 0 8px 32px var(--text-insignificant-color); 1707 + background-color: var(--bg-color); 1708 + color: var(--text-color); 1709 + cursor: pointer; 1710 + pointer-events: auto; 1711 + padding: 16px 32px; 1712 + font-size: max(calc(16px * 1.1), var(--text-size)); 1713 + text-align: center; 1714 + line-height: 1.25; 1715 + } 1716 + :root .toastify.alert:is(:hover, :active) { 1717 + background-color: var(--bg-faded-color); 1659 1718 } 1660 1719 1661 1720 /* AVATARS STACK */
+61 -38
src/components/account-info.jsx
··· 21 21 import Link from './link'; 22 22 import ListAddEdit from './list-add-edit'; 23 23 import Loader from './loader'; 24 + import MenuConfirm from './menu-confirm'; 24 25 import Modal from './modal'; 25 26 import TranslationBlock from './translation-block'; 26 27 ··· 734 735 </div> 735 736 </SubMenu> 736 737 )} 737 - <MenuItem 738 + <MenuConfirm 739 + subMenu 740 + confirm={!blocking} 741 + confirmLabel={ 742 + <> 743 + <Icon icon="block" /> 744 + <span>Block @{username}?</span> 745 + </> 746 + } 747 + menuItemClassName="danger" 738 748 onClick={() => { 739 - if (!blocking && !confirm(`Block @${username}?`)) { 740 - return; 741 - } 749 + // if (!blocking && !confirm(`Block @${username}?`)) { 750 + // return; 751 + // } 742 752 setRelationshipUIState('loading'); 743 753 (async () => { 744 754 try { ··· 784 794 <span>Block @{username}…</span> 785 795 </> 786 796 )} 787 - </MenuItem> 797 + </MenuConfirm> 788 798 {/* <MenuItem> 789 799 <Icon icon="flag" /> 790 800 <span>Report @{username}…</span> ··· 796 806 <Loader abrupt /> 797 807 )} 798 808 {!!relationship && ( 799 - <button 800 - type="button" 801 - class={`${following || requested ? 'light swap' : ''}`} 802 - data-swap-state={following || requested ? 'danger' : ''} 809 + <MenuConfirm 810 + confirm={following || requested} 811 + confirmLabel={ 812 + <span> 813 + {requested 814 + ? 'Withdraw follow request?' 815 + : `Unfollow @${info.acct || info.username}?`} 816 + </span> 817 + } 818 + menuItemClassName="danger" 819 + align="end" 803 820 disabled={loading} 804 821 onClick={() => { 805 822 setRelationshipUIState('loading'); ··· 808 825 let newRelationship; 809 826 810 827 if (following || requested) { 811 - const yes = confirm( 812 - requested 813 - ? 'Withdraw follow request?' 814 - : `Unfollow @${info.acct || info.username}?`, 815 - ); 828 + // const yes = confirm( 829 + // requested 830 + // ? 'Withdraw follow request?' 831 + // : `Unfollow @${info.acct || info.username}?`, 832 + // ); 816 833 817 - if (yes) { 818 - newRelationship = 819 - await currentMasto.v1.accounts.unfollow( 820 - accountID.current, 821 - ); 822 - } 834 + // if (yes) { 835 + newRelationship = await currentMasto.v1.accounts.unfollow( 836 + accountID.current, 837 + ); 838 + // } 823 839 } else { 824 840 newRelationship = await currentMasto.v1.accounts.follow( 825 841 accountID.current, ··· 835 851 })(); 836 852 }} 837 853 > 838 - {following ? ( 839 - <> 840 - <span>Following</span> 841 - <span>Unfollow…</span> 842 - </> 843 - ) : requested ? ( 844 - <> 845 - <span>Requested</span> 846 - <span>Withdraw…</span> 847 - </> 848 - ) : locked ? ( 849 - <> 850 - <Icon icon="lock" /> <span>Follow</span> 851 - </> 852 - ) : ( 853 - 'Follow' 854 - )} 855 - </button> 854 + <button 855 + type="button" 856 + class={`${following || requested ? 'light swap' : ''}`} 857 + data-swap-state={following || requested ? 'danger' : ''} 858 + disabled={loading} 859 + > 860 + {following ? ( 861 + <> 862 + <span>Following</span> 863 + <span>Unfollow…</span> 864 + </> 865 + ) : requested ? ( 866 + <> 867 + <span>Requested</span> 868 + <span>Withdraw…</span> 869 + </> 870 + ) : locked ? ( 871 + <> 872 + <Icon icon="lock" /> <span>Follow</span> 873 + </> 874 + ) : ( 875 + 'Follow' 876 + )} 877 + </button> 878 + </MenuConfirm> 856 879 )} 857 880 </span> 858 881 </p>
+42 -26
src/components/drafts.jsx
··· 10 10 11 11 import Icon from './icon'; 12 12 import Loader from './loader'; 13 + import MenuConfirm from './menu-confirm'; 13 14 14 15 function Drafts({ onClose }) { 15 16 const { masto } = api(); ··· 89 90 {niceDateTime(updatedAtDate)} 90 91 </time> 91 92 </b> 92 - <button 93 - type="button" 94 - class="small light" 93 + <MenuConfirm 94 + confirmLabel={<span>Delete this draft?</span>} 95 + menuItemClassName="danger" 96 + align="end" 95 97 disabled={uiState === 'loading'} 96 98 onClick={() => { 97 99 (async () => { 98 100 try { 99 - const yes = confirm('Delete this draft?'); 100 - if (yes) { 101 - await db.drafts.del(key); 102 - reload(); 103 - } 101 + // const yes = confirm('Delete this draft?'); 102 + // if (yes) { 103 + await db.drafts.del(key); 104 + reload(); 105 + // } 104 106 } catch (e) { 105 107 alert('Error deleting draft! Please try again.'); 106 108 } 107 109 })(); 108 110 }} 109 111 > 110 - Delete&hellip; 111 - </button> 112 + <button 113 + type="button" 114 + class="small light" 115 + disabled={uiState === 'loading'} 116 + > 117 + Delete&hellip; 118 + </button> 119 + </MenuConfirm> 112 120 </div> 113 121 <button 114 122 type="button" ··· 145 153 ); 146 154 })} 147 155 </ul> 148 - <p> 149 - <button 150 - type="button" 151 - class="light danger" 152 - disabled={uiState === 'loading'} 153 - onClick={() => { 154 - (async () => { 155 - const yes = confirm('Delete all drafts?'); 156 - if (yes) { 156 + {drafts.length > 1 && ( 157 + <p> 158 + <MenuConfirm 159 + confirmLabel={<span>Delete all drafts?</span>} 160 + menuItemClassName="danger" 161 + disabled={uiState === 'loading'} 162 + onClick={() => { 163 + (async () => { 164 + // const yes = confirm('Delete all drafts?'); 165 + // if (yes) { 157 166 setUIState('loading'); 158 167 try { 159 168 await db.drafts.delMany( ··· 166 175 alert('Error deleting drafts! Please try again.'); 167 176 setUIState('error'); 168 177 } 169 - } 170 - })(); 171 - }} 172 - > 173 - Delete all drafts&hellip; 174 - </button> 175 - </p> 178 + // } 179 + })(); 180 + }} 181 + > 182 + <button 183 + type="button" 184 + class="light danger" 185 + disabled={uiState === 'loading'} 186 + > 187 + Delete all&hellip; 188 + </button> 189 + </MenuConfirm> 190 + </p> 191 + )} 176 192 </> 177 193 ) : ( 178 194 <p>No drafts found.</p>
+1
src/components/icon.jsx
··· 87 87 layout4: () => import('@iconify-icons/mingcute/layout-4-line'), 88 88 layout5: () => import('@iconify-icons/mingcute/layout-5-line'), 89 89 announce: () => import('@iconify-icons/mingcute/announcement-line'), 90 + alert: () => import('@iconify-icons/mingcute/alert-line'), 90 91 }; 91 92 92 93 function Icon({
+15 -7
src/components/list-add-edit.jsx
··· 3 3 import { api } from '../utils/api'; 4 4 5 5 import Icon from './icon'; 6 + import MenuConfirm from './menu-confirm'; 6 7 7 8 function ListAddEdit({ list, onClose }) { 8 9 const { masto } = api(); ··· 103 104 {editMode ? 'Save' : 'Create'} 104 105 </button> 105 106 {editMode && ( 106 - <button 107 - type="button" 108 - class="light danger" 107 + <MenuConfirm 109 108 disabled={uiState === 'loading'} 109 + align="end" 110 + menuItemClassName="danger" 111 + confirmLabel="Delete this list?" 110 112 onClick={() => { 111 - const yes = confirm('Delete this list?'); 112 - if (!yes) return; 113 + // const yes = confirm('Delete this list?'); 114 + // if (!yes) return; 113 115 setUiState('loading'); 114 116 115 117 (async () => { ··· 127 129 })(); 128 130 }} 129 131 > 130 - Delete… 131 - </button> 132 + <button 133 + type="button" 134 + class="light danger" 135 + disabled={uiState === 'loading'} 136 + > 137 + Delete… 138 + </button> 139 + </MenuConfirm> 132 140 )} 133 141 </div> 134 142 </form>
+43
src/components/menu-confirm.jsx
··· 1 + import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu'; 2 + import { cloneElement } from 'preact'; 3 + 4 + function MenuConfirm({ 5 + subMenu = false, 6 + confirm = true, 7 + confirmLabel, 8 + menuItemClassName, 9 + menuFooter, 10 + ...props 11 + }) { 12 + const { children, onClick, ...restProps } = props; 13 + if (!confirm) { 14 + if (subMenu) return <MenuItem {...props} />; 15 + if (onClick) { 16 + return cloneElement(children, { 17 + onClick, 18 + }); 19 + } 20 + return children; 21 + } 22 + const Parent = subMenu ? SubMenu : Menu; 23 + return ( 24 + <Parent 25 + openTrigger="clickOnly" 26 + direction="bottom" 27 + overflow="auto" 28 + gap={-8} 29 + shift={8} 30 + menuClassName="menu-emphasized" 31 + {...restProps} 32 + menuButton={subMenu ? undefined : children} 33 + label={subMenu ? children : undefined} 34 + > 35 + <MenuItem className={menuItemClassName} onClick={onClick}> 36 + {confirmLabel} 37 + </MenuItem> 38 + {menuFooter} 39 + </Parent> 40 + ); 41 + } 42 + 43 + export default MenuConfirm;
+120 -26
src/components/status.jsx
··· 28 28 import AccountBlock from '../components/account-block'; 29 29 import EmojiText from '../components/emoji-text'; 30 30 import Loader from '../components/loader'; 31 + import MenuConfirm from '../components/menu-confirm'; 31 32 import Modal from '../components/modal'; 32 33 import NameText from '../components/name-text'; 33 34 import Poll from '../components/poll'; ··· 325 326 }; 326 327 }; 327 328 329 + // Check if media has no descriptions 330 + const mediaNoDesc = useMemo(() => { 331 + return mediaAttachments.some( 332 + (attachment) => !attachment.description?.trim?.(), 333 + ); 334 + }, [mediaAttachments]); 328 335 const boostStatus = async () => { 329 336 if (!sameInstance || !authenticated) { 330 337 alert(unauthInteractionErrorMessage); ··· 332 339 } 333 340 try { 334 341 if (!reblogged) { 335 - // Check if media has no descriptions 336 - const hasNoDescriptions = mediaAttachments.some( 337 - (attachment) => !attachment.description?.trim?.(), 338 - ); 339 342 let confirmText = 'Boost this post?'; 340 - if (hasNoDescriptions) { 343 + if (mediaNoDesc) { 341 344 confirmText += '\n\n⚠️ Some media have no descriptions.'; 342 345 } 343 346 const yes = confirm(confirmText); ··· 367 370 return false; 368 371 } 369 372 }; 373 + const confirmBoostStatus = async () => { 374 + if (!sameInstance || !authenticated) { 375 + alert(unauthInteractionErrorMessage); 376 + return false; 377 + } 378 + try { 379 + // Optimistic 380 + states.statuses[sKey] = { 381 + ...status, 382 + reblogged: !reblogged, 383 + reblogsCount: reblogsCount + (reblogged ? -1 : 1), 384 + }; 385 + if (reblogged) { 386 + const newStatus = await masto.v1.statuses.unreblog(id); 387 + saveStatus(newStatus, instance); 388 + return true; 389 + } else { 390 + const newStatus = await masto.v1.statuses.reblog(id); 391 + saveStatus(newStatus, instance); 392 + return true; 393 + } 394 + } catch (e) { 395 + console.error(e); 396 + // Revert optimistism 397 + states.statuses[sKey] = status; 398 + return false; 399 + } 400 + }; 370 401 371 402 const favouriteStatus = async () => { 372 403 if (!sameInstance || !authenticated) { ··· 490 521 {!isSizeLarge && sameInstance && ( 491 522 <> 492 523 <div class="menu-horizontal"> 493 - <MenuItem 524 + <MenuConfirm 525 + subMenu 526 + confirmLabel={ 527 + <> 528 + <Icon icon="rocket" /> 529 + <span>Unboost?</span> 530 + </> 531 + } 532 + menuFooter={ 533 + mediaNoDesc && 534 + !reblogged && ( 535 + <div class="footer"> 536 + <Icon icon="alert" /> 537 + Some media have no descriptions. 538 + </div> 539 + ) 540 + } 494 541 disabled={!canBoost} 495 542 onClick={async () => { 496 543 try { 497 - const done = await boostStatus(); 544 + const done = await confirmBoostStatus(); 498 545 if (!isSizeLarge && done) { 499 546 showToast(reblogged ? 'Unboosted' : 'Boosted'); 500 547 } ··· 508 555 }} 509 556 /> 510 557 <span>{reblogged ? 'Unboost' : 'Boost…'}</span> 511 - </MenuItem> 558 + </MenuConfirm> 512 559 <MenuItem 513 560 onClick={() => { 514 561 try { ··· 660 707 <span>Edit</span> 661 708 </MenuItem> 662 709 {isSizeLarge && ( 663 - <MenuItem 710 + <MenuConfirm 711 + subMenu 712 + confirmLabel={ 713 + <> 714 + <Icon icon="trash" /> 715 + <span>Delete this post?</span> 716 + </> 717 + } 718 + menuItemClassName="danger" 664 719 onClick={() => { 665 - const yes = confirm('Delete this post?'); 666 - if (yes) { 667 - (async () => { 668 - try { 669 - await masto.v1.statuses.remove(id); 670 - const cachedStatus = getStatus(id, instance); 671 - cachedStatus._deleted = true; 672 - showToast('Deleted'); 673 - } catch (e) { 674 - console.error(e); 675 - showToast('Unable to delete'); 676 - } 677 - })(); 678 - } 720 + // const yes = confirm('Delete this post?'); 721 + // if (yes) { 722 + (async () => { 723 + try { 724 + await masto.v1.statuses.remove(id); 725 + const cachedStatus = getStatus(id, instance); 726 + cachedStatus._deleted = true; 727 + showToast('Deleted'); 728 + } catch (e) { 729 + console.error(e); 730 + showToast('Unable to delete'); 731 + } 732 + })(); 733 + // } 679 734 }} 680 735 > 681 736 <Icon icon="trash" /> 682 737 <span>Delete…</span> 683 - </MenuItem> 738 + </MenuConfirm> 684 739 )} 685 740 </div> 686 741 )} ··· 1157 1212 onClick={replyStatus} 1158 1213 /> 1159 1214 </div> 1160 - <div class="action has-count"> 1215 + {/* <div class="action has-count"> 1161 1216 <StatusButton 1162 1217 checked={reblogged} 1163 1218 title={['Boost', 'Unboost']} ··· 1168 1223 onClick={boostStatus} 1169 1224 disabled={!canBoost} 1170 1225 /> 1171 - </div> 1226 + </div> */} 1227 + <Menu 1228 + portal={{ 1229 + target: 1230 + document.querySelector('.status-deck') || document.body, 1231 + }} 1232 + align="start" 1233 + gap={4} 1234 + overflow="auto" 1235 + viewScroll="close" 1236 + boundingBoxPadding="8 8 8 8" 1237 + shift={-8} 1238 + menuClassName="menu-emphasized" 1239 + menuButton={({ open }) => ( 1240 + <div class="action has-count"> 1241 + <StatusButton 1242 + checked={reblogged} 1243 + title={['Boost', 'Unboost']} 1244 + alt={['Boost', 'Boosted']} 1245 + class="reblog-button" 1246 + icon="rocket" 1247 + count={reblogsCount} 1248 + // onClick={boostStatus} 1249 + disabled={open || !canBoost} 1250 + /> 1251 + </div> 1252 + )} 1253 + > 1254 + <MenuItem onClick={confirmBoostStatus}> 1255 + <Icon icon="rocket" /> 1256 + <span>Boost to everyone?</span> 1257 + </MenuItem> 1258 + {mediaNoDesc && ( 1259 + <div class="footer"> 1260 + <Icon icon="alert" /> 1261 + Some media have no descriptions. 1262 + </div> 1263 + )} 1264 + </Menu> 1172 1265 <div class="action has-count"> 1173 1266 <StatusButton 1174 1267 checked={favourited} ··· 1682 1775 title={buttonTitle} 1683 1776 class={`plain ${className} ${checked ? 'checked' : ''}`} 1684 1777 onClick={(e) => { 1778 + if (!onClick) return; 1685 1779 e.preventDefault(); 1686 1780 e.stopPropagation(); 1687 1781 onClick(e);
+13 -4
src/pages/accounts.jsx
··· 6 6 import Avatar from '../components/avatar'; 7 7 import Icon from '../components/icon'; 8 8 import Link from '../components/link'; 9 + import MenuConfirm from '../components/menu-confirm'; 9 10 import NameText from '../components/name-text'; 10 11 import { api } from '../utils/api'; 11 12 import states from '../utils/states'; ··· 126 127 <span>Set as default</span> 127 128 </MenuItem> 128 129 )} 129 - <MenuItem 130 + <MenuConfirm 131 + subMenu 132 + confirmLabel={ 133 + <> 134 + <Icon icon="exit" /> 135 + <span>Log out @{account.info.acct}?</span> 136 + </> 137 + } 130 138 disabled={!isCurrent} 139 + menuItemClassName="danger" 131 140 onClick={() => { 132 - const yes = confirm('Log out?'); 133 - if (!yes) return; 141 + // const yes = confirm('Log out?'); 142 + // if (!yes) return; 134 143 accounts.splice(i, 1); 135 144 store.local.setJSON('accounts', accounts); 136 145 // location.reload(); ··· 139 148 > 140 149 <Icon icon="exit" /> 141 150 <span>Log out…</span> 142 - </MenuItem> 151 + </MenuConfirm> 143 152 </Menu> 144 153 </div> 145 154 </li>
+11 -7
src/pages/hashtag.jsx
··· 10 10 11 11 import Icon from '../components/icon'; 12 12 import Menu2 from '../components/menu2'; 13 + import MenuConfirm from '../components/menu-confirm'; 13 14 import Timeline from '../components/timeline'; 14 15 import { api } from '../utils/api'; 15 16 import showToast from '../utils/show-toast'; ··· 149 150 > 150 151 {!!info && hashtags.length === 1 && ( 151 152 <> 152 - <MenuItem 153 + <MenuConfirm 154 + subMenu 155 + confirm={info.following} 156 + confirmLabel={`Unfollow #${hashtag}?`} 153 157 disabled={followUIState === 'loading' || !authenticated} 154 158 onClick={() => { 155 159 setFollowUIState('loading'); 156 160 if (info.following) { 157 - const yes = confirm(`Unfollow #${hashtag}?`); 158 - if (!yes) { 159 - setFollowUIState('default'); 160 - return; 161 - } 161 + // const yes = confirm(`Unfollow #${hashtag}?`); 162 + // if (!yes) { 163 + // setFollowUIState('default'); 164 + // return; 165 + // } 162 166 masto.v1.tags 163 167 .unfollow(hashtag) 164 168 .then(() => { ··· 198 202 <Icon icon="plus" /> <span>Follow</span> 199 203 </> 200 204 )} 201 - </MenuItem> 205 + </MenuConfirm> 202 206 <MenuDivider /> 203 207 </> 204 208 )}
+16 -8
src/pages/list.jsx
··· 11 11 import Link from '../components/link'; 12 12 import ListAddEdit from '../components/list-add-edit'; 13 13 import Menu2 from '../components/menu2'; 14 + import MenuConfirm from '../components/menu-confirm'; 14 15 import Modal from '../components/modal'; 15 16 import Timeline from '../components/timeline'; 16 17 import { api } from '../utils/api'; ··· 263 264 const [removed, setRemoved] = useState(false); 264 265 265 266 return ( 266 - <button 267 - type="button" 268 - class={`light ${removed ? '' : 'danger'}`} 269 - disabled={uiState === 'loading'} 267 + <MenuConfirm 268 + confirm={!removed} 269 + confirmLabel={<span>Remove @{account.username} from list?</span>} 270 + align="end" 271 + menuItemClassName="danger" 270 272 onClick={() => { 271 273 if (removed) { 272 274 setUIState('loading'); ··· 282 284 } 283 285 })(); 284 286 } else { 285 - const yes = confirm(`Remove ${account.username} from this list?`); 286 - if (!yes) return; 287 + // const yes = confirm(`Remove ${account.username} from this list?`); 288 + // if (!yes) return; 287 289 setUIState('loading'); 288 290 289 291 (async () => { ··· 300 302 } 301 303 }} 302 304 > 303 - {removed ? 'Add' : 'Remove…'} 304 - </button> 305 + <button 306 + type="button" 307 + class={`light ${removed ? '' : 'danger'}`} 308 + disabled={uiState === 'loading'} 309 + > 310 + {removed ? 'Add' : 'Remove…'} 311 + </button> 312 + </MenuConfirm> 305 313 ); 306 314 } 307 315
+34
src/utils/toast-alert.js
··· 1 + // Replace alert() with toastify-js 2 + import Toastify from 'toastify-js'; 3 + 4 + const nativeAlert = window.alert; 5 + if (!window.__nativeAlert) window.__nativeAlert = nativeAlert; 6 + 7 + window.alert = function (message) { 8 + console.debug( 9 + 'ALERT: This is a custom alert() function. Native alert() is still available as window.__nativeAlert()', 10 + ); 11 + // If Error object, show the message 12 + if (message instanceof Error && message?.message) { 13 + message = message.message; 14 + } 15 + // If not string, stringify it 16 + if (typeof message !== 'string') { 17 + message = JSON.stringify(message); 18 + } 19 + 20 + const toast = Toastify({ 21 + text: message, 22 + className: 'alert', 23 + gravity: 'top', 24 + position: 'center', 25 + duration: 10_000, 26 + offset: { 27 + y: 48, 28 + }, 29 + onClick: () => { 30 + toast.hideToast(); 31 + }, 32 + }); 33 + toast.showToast(); 34 + };