this repo has no description
0
fork

Configure Feed

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

New: Reactions Modal

+203
+1
src/components/icon.jsx
··· 76 76 emoji2: 'mingcute:emoji-2-line', 77 77 filter: 'mingcute:filter-2-line', 78 78 chart: 'mingcute:chart-line-line', 79 + react: 'mingcute:react-line', 79 80 }; 80 81 81 82 const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js');
+38
src/components/status.css
··· 1222 1222 bottom: 8px; 1223 1223 right: 8px; 1224 1224 } 1225 + 1226 + /* REACTIONS */ 1227 + 1228 + #reactions-container main ul { 1229 + list-style: none; 1230 + margin: 0; 1231 + padding: 8px 0; 1232 + display: flex; 1233 + flex-wrap: wrap; 1234 + flex-direction: row; 1235 + column-gap: 1.5em; 1236 + row-gap: 16px; 1237 + } 1238 + #reactions-container main ul li { 1239 + display: flex; 1240 + flex-grow: 1; 1241 + flex-basis: 16em; 1242 + align-items: center; 1243 + margin: 0; 1244 + padding: 0; 1245 + gap: 8px; 1246 + } 1247 + #reactions-container main ul li .account-block-acct { 1248 + font-size: 80%; 1249 + color: var(--text-insignificant-color); 1250 + display: block; 1251 + } 1252 + #reactions-container .reactions-block { 1253 + display: flex; 1254 + flex-direction: column; 1255 + align-self: center; 1256 + } 1257 + #reactions-container .reactions-block .favourite-icon { 1258 + color: var(--favourite-color); 1259 + } 1260 + #reactions-container .reactions-block .reblog-icon { 1261 + color: var(--reblog-color); 1262 + }
+164
src/components/status.jsx
··· 14 14 import pThrottle from 'p-throttle'; 15 15 import { memo } from 'preact/compat'; 16 16 import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; 17 + import { InView } from 'react-intersection-observer'; 17 18 import 'swiped-events'; 18 19 import { useLongPress } from 'use-long-press'; 19 20 import useResizeObserver from 'use-resize-observer'; 20 21 import { useSnapshot } from 'valtio'; 21 22 23 + import AccountBlock from '../components/account-block'; 22 24 import Loader from '../components/loader'; 23 25 import Modal from '../components/modal'; 24 26 import NameText from '../components/name-text'; ··· 228 230 if (!snapStates.settings.contentTranslation) enableTranslate = false; 229 231 230 232 const [showEdited, setShowEdited] = useState(false); 233 + const [showReactions, setShowReactions] = useState(false); 231 234 232 235 const spoilerContentRef = useRef(null); 233 236 useResizeObserver({ ··· 444 447 </MenuItem> 445 448 )} 446 449 {(!isSizeLarge || !!editedAt) && <MenuDivider />} 450 + {isSizeLarge && ( 451 + <MenuItem onClick={() => setShowReactions(true)}> 452 + <Icon icon="react" /> 453 + <span> 454 + Boosted/Favourited by<span class="more-insignificant">…</span> 455 + </span> 456 + </MenuItem> 457 + )} 447 458 {!isSizeLarge && sameInstance && ( 448 459 <> 449 460 <MenuItem onClick={replyStatus}> ··· 1123 1134 /> 1124 1135 </Modal> 1125 1136 )} 1137 + {showReactions && ( 1138 + <Modal 1139 + class="light" 1140 + onClick={(e) => { 1141 + if (e.target === e.currentTarget) { 1142 + setShowReactions(false); 1143 + } 1144 + }} 1145 + > 1146 + <ReactionsModal statusID={id} instance={instance} /> 1147 + </Modal> 1148 + )} 1126 1149 </article> 1127 1150 ); 1128 1151 } ··· 1535 1558 ); 1536 1559 })} 1537 1560 </ol> 1561 + )} 1562 + </main> 1563 + </div> 1564 + ); 1565 + } 1566 + 1567 + const REACTIONS_LIMIT = 80; 1568 + function ReactionsModal({ statusID, instance }) { 1569 + const { masto } = api({ instance }); 1570 + const [uiState, setUIState] = useState('default'); 1571 + const [accounts, setAccounts] = useState([]); 1572 + const [showMore, setShowMore] = useState(false); 1573 + 1574 + const reblogIterator = useRef(); 1575 + const favouriteIterator = useRef(); 1576 + 1577 + async function fetchAccounts(firstLoad) { 1578 + setShowMore(false); 1579 + setUIState('loading'); 1580 + (async () => { 1581 + try { 1582 + if (firstLoad) { 1583 + reblogIterator.current = masto.v1.statuses.listRebloggedBy(statusID, { 1584 + limit: REACTIONS_LIMIT, 1585 + }); 1586 + favouriteIterator.current = masto.v1.statuses.listFavouritedBy( 1587 + statusID, 1588 + { 1589 + limit: REACTIONS_LIMIT, 1590 + }, 1591 + ); 1592 + } 1593 + const [{ value: reblogResults }, { value: favouriteResults }] = 1594 + await Promise.allSettled([ 1595 + reblogIterator.current.next(), 1596 + favouriteIterator.current.next(), 1597 + ]); 1598 + if (reblogResults.value?.length || favouriteResults.value?.length) { 1599 + if (reblogResults.value?.length) { 1600 + for (const account of reblogResults.value) { 1601 + const theAccount = accounts.find((a) => a.id === account.id); 1602 + if (!theAccount) { 1603 + accounts.push({ 1604 + ...account, 1605 + _types: ['reblog'], 1606 + }); 1607 + } else { 1608 + theAccount._types.push('reblog'); 1609 + } 1610 + } 1611 + } 1612 + if (favouriteResults.value?.length) { 1613 + for (const account of favouriteResults.value) { 1614 + const theAccount = accounts.find((a) => a.id === account.id); 1615 + if (!theAccount) { 1616 + accounts.push({ 1617 + ...account, 1618 + _types: ['favourite'], 1619 + }); 1620 + } else { 1621 + theAccount._types.push('favourite'); 1622 + } 1623 + } 1624 + } 1625 + setAccounts(accounts); 1626 + setShowMore(!reblogResults.done || !favouriteResults.done); 1627 + } else { 1628 + setShowMore(false); 1629 + } 1630 + setUIState('default'); 1631 + } catch (e) { 1632 + console.error(e); 1633 + setUIState('error'); 1634 + } 1635 + })(); 1636 + } 1637 + 1638 + useEffect(() => { 1639 + fetchAccounts(true); 1640 + }, []); 1641 + 1642 + return ( 1643 + <div id="reactions-container" class="sheet"> 1644 + <header> 1645 + <h2>Boosted/Favourited by…</h2> 1646 + </header> 1647 + <main> 1648 + {accounts.length > 0 ? ( 1649 + <> 1650 + <ul class="reactions-list"> 1651 + {accounts.map((account) => { 1652 + const { _types } = account; 1653 + return ( 1654 + <li key={account.id + _types}> 1655 + <div class="reactions-block"> 1656 + {_types.map((type) => ( 1657 + <Icon 1658 + icon={ 1659 + { 1660 + reblog: 'rocket', 1661 + favourite: 'heart', 1662 + }[type] 1663 + } 1664 + class={`${type}-icon`} 1665 + /> 1666 + ))} 1667 + </div> 1668 + <AccountBlock account={account} instance={instance} /> 1669 + </li> 1670 + ); 1671 + })} 1672 + </ul> 1673 + {uiState === 'default' && 1674 + (showMore ? ( 1675 + <InView 1676 + onChange={(inView) => { 1677 + if (inView) { 1678 + fetchAccounts(); 1679 + } 1680 + }} 1681 + > 1682 + <button 1683 + type="button" 1684 + class="plain block" 1685 + onClick={() => fetchAccounts()} 1686 + > 1687 + Show more&hellip; 1688 + </button> 1689 + </InView> 1690 + ) : ( 1691 + <p class="ui-state insignificant">The end.</p> 1692 + ))} 1693 + </> 1694 + ) : uiState === 'loading' ? ( 1695 + <p class="ui-state"> 1696 + <Loader abrupt /> 1697 + </p> 1698 + ) : uiState === 'error' ? ( 1699 + <p class="ui-state">Unable to load accounts</p> 1700 + ) : ( 1701 + <p class="ui-state insignificant">No one yet.</p> 1538 1702 )} 1539 1703 </main> 1540 1704 </div>