import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; import { Virtuoso } from 'react-virtuoso'; import { useAccount, useAccountComments, useFeed } from '@plebbit/plebbit-react-hooks'; import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils' import { debounce } from 'lodash'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; import { Container, NavBar, Header, Break, TopBar, BoardForm } from '../styled/Board.styled'; import { Footer } from '../styled/Thread.styled'; import ImageBanner from '../ImageBanner'; import Post from '../Post'; import PostLoader from '../PostLoader'; import ReplyModal from '../ReplyModal'; import SettingsModal from '../SettingsModal'; import findShortParentCid from '../../utils/findShortParentCid'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import getDate from '../../utils/getDate'; import handleAddressClick from '../../utils/handleAddressClick'; import handleImageClick from '../../utils/handleImageClick'; import handleQuoteClick from '../../utils/handleQuoteClick'; import handleStyleChange from '../../utils/handleStyleChange'; import useError from '../../hooks/useError'; import packageJson from '../../../package.json' const {version} = packageJson const Subscriptions = () => { const { defaultSubplebbits, isSettingsOpen, setIsSettingsOpen, selectedAddress, setSelectedAddress, setSelectedParentCid, setSelectedShortCid, selectedStyle, setSelectedThread, selectedTitle, setSelectedTitle, } = useGeneralStore(state => state); const account = useAccount(); const [isReplyOpen, setIsReplyOpen] = useState(false); const navigate = useNavigate(); const [prevScrollPos, setPrevScrollPos] = useState(0); const [visible, setVisible] = useState(true); const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: account?.subscriptions, sortType: 'new'}); const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); const { subplebbitAddress } = useParams(); const [errorMessage] = useState(null); useError(errorMessage, [errorMessage]); const flattenedRepliesByThread = useMemo(() => { return selectedFeed.reduce((acc, thread) => { const replies = flattenCommentsPages(thread.replies); acc[thread.cid] = replies; return acc; }, {}); }, [selectedFeed]); const allParentCids = useMemo(() => { const allRepliesCids = Object.values(flattenedRepliesByThread).flatMap(replies => replies.map(reply => reply.cid)); const allThreadCids = selectedFeed.map(thread => thread.cid); return [...allThreadCids, ...allRepliesCids]; }, [flattenedRepliesByThread, selectedFeed]); const filter = useMemo(() => ({ parentCids: allParentCids }), [allParentCids]); const { accountComments } = useAccountComments({ filter }); const filteredRepliesByThread = useMemo(() => { const maxRepliesPerThread = 5; const accountRepliesNotYetInCommentReplies = selectedFeed.reduce((acc, thread) => { const replyCids = new Set(flattenedRepliesByThread[thread.cid].map(reply => reply.cid)); acc[thread.cid] = accountComments.filter(accountReply => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid); return acc; }, {}); return selectedFeed.reduce((acc, thread) => { const combinedReplies = [...flattenedRepliesByThread[thread.cid], ...accountRepliesNotYetInCommentReplies[thread.cid]].sort((a, b) => a.timestamp - b.timestamp); acc[thread.cid] = { displayedReplies: combinedReplies.slice(0, maxRepliesPerThread), omittedCount: Math.max(combinedReplies.length - maxRepliesPerThread, 0), }; return acc; }, {}); }, [flattenedRepliesByThread, accountComments, selectedFeed]); const pendingReplyCounts = useMemo(() => { return selectedFeed.reduce((acc, thread) => { const replyCids = new Set(flattenedRepliesByThread[thread.cid].map(reply => reply.cid)); acc[thread.cid] = accountComments.filter(accountReply => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid).length; return acc; }, {}); }, [flattenedRepliesByThread, accountComments, selectedFeed]); // temporary title from JSON, gets subplebbitAddress from URL useEffect(() => { setSelectedAddress(subplebbitAddress); const selectedSubplebbit = defaultSubplebbits.find((subplebbit) => subplebbit.address === subplebbitAddress); if (selectedSubplebbit) { setSelectedTitle(selectedSubplebbit.title); } }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]); // sets useFeed to address from URL useEffect(() => { setSelectedFeed(feed); }, [feed]); // mobile navbar scroll effect useEffect(() => { const debouncedHandleScroll = debounce(() => { const currentScrollPos = window.pageYOffset; setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10); setPrevScrollPos(currentScrollPos); }, 50); window.addEventListener('scroll', debouncedHandleScroll); return () => window.removeEventListener('scroll', debouncedHandleScroll); }, [prevScrollPos, visible]); const tryLoadMore = async () => { try { await loadMore(); } catch (e) { await new Promise(resolve => setTimeout(resolve, 1000)); } }; // desktop navbar board select functionality const handleClickTitle = (title, address) => { setSelectedTitle(title); setSelectedAddress(address); setSelectedFeed(feed.filter(feed => feed.title === title)); if (subplebbitAddress === address) { window.location.reload(); } }; // mobile navbar board select functionality const handleSelectChange = (event) => { const selected = event.target.value; const selectedTitle = defaultSubplebbits?.find((subplebbit) => subplebbit.address === selected).title; setSelectedTitle(selectedTitle); setSelectedAddress(selected); navigate(`/p/${selected}`); }; return ( <> {((selectedTitle ? selectedTitle : selectedAddress) + " - plebchan")} setIsReplyOpen(false)} /> setIsSettingsOpen(false)} /> <> [ {}}>Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( {index === 0 ? null : "\u00a0"} handleClickTitle(subplebbit.title, subplebbit.address)} >{subplebbit.title ? subplebbit.title : subplebbit.address} {index !== defaultSubplebbits.length - 1 ? " /" : null} ))} ] [ ] [ setIsSettingsOpen(true)}>Settings ] [ handleStyleChange({target: {value: "Yotsuba"}} )}>Home ]
Board    
setIsSettingsOpen(true)}>Settings   handleStyleChange({target: {value: "Yotsuba"}} )}>Home
 
 
<>
<>
Subscriptions
{feed.length < 1 ? (
You haven't subscribed to any board yet.
) : (
You have subscribed to {account.subscriptions.length} board{account.subscriptions.length > 1 ? "s" : null}.
)}

Style:  
{ feed.length < 1 ? ( // null ) : ( { const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {}; const commentMediaInfo = getCommentMediaInfo(thread); const fallbackImgUrl = "assets/filedeleted-res.gif"; return (

{commentMediaInfo?.url ? (
{commentMediaInfo?.type === "webpage" ? (
{thread.thumbnailUrl ? ( {commentMediaInfo.type} e.target.src = fallbackImgUrl} /> ) : null}
) : null} {commentMediaInfo?.type === "image" ? (
{commentMediaInfo.type} e.target.src = fallbackImgUrl} />
) : null} {commentMediaInfo?.type === "video" ? ( ) : null} {commentMediaInfo?.type === "audio" ? ( ) : null}
) : null} {thread.title ? ( thread.title.length > 75 ? {thread.title.slice(0, 75) + " (...)"} : {thread.title} ) : null}  {thread.author.displayName ? thread.author.displayName.length > 20 ? {thread.author.displayName.slice(0, 20) + " (...)"} : {thread.author.displayName} : Anonymous}   (u/ handleAddressClick(thread.author.shortAddress)} > {thread.author.shortAddress} )   {getDate(thread.timestamp)}   c/ { if (e.button === 2) return; e.preventDefault(); setIsReplyOpen(true); setSelectedShortCid(thread.shortCid); setSelectedParentCid(thread.cid); }} title="Reply to this post">{thread.shortCid}  p/ {thread.subplebbitAddress.includes(".eth") ? thread.subplebbitAddress : ( {thread.subplebbitAddress.slice(0, 10) + "(...)"} )}    [ setSelectedThread(thread.cid)} className="reply-link" >Reply ]   {thread.content ? ( thread.content?.length > 1000 ?
(...)

Post too long.  setSelectedThread(thread.cid)} className="ttl-link">Click here  to view.
:
) : null}
{omittedCount > 0 ? ( {omittedCount} post{omittedCount > 1 ? "s" : ""} omitted. Click  setSelectedThread(thread.cid)} className="ttl-link">here  to view. ) : null} {displayedReplies?.map((reply, index) => { const replyMediaInfo = getCommentMediaInfo(reply); const fallbackImgUrl = "assets/filedeleted-res.gif"; const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); return (
{'>>'}
{reply.author.displayName ? reply.author.displayName.length > 20 ? {reply.author.displayName.slice(0, 20) + " (...)"} : {reply.author.displayName} : Anonymous}   handleAddressClick(reply.author.shortAddress)} > (u/ {reply.author?.shortAddress ? ( {reply.author?.shortAddress} ) : ( {account?.author?.address.slice(0, 10) + "(...)"} ) } )   {getDate(reply.timestamp)}   c/ {reply.shortCid ? ( { if (e.button === 2) return; e.preventDefault(); setIsReplyOpen(true); setSelectedShortCid(reply.shortCid); setSelectedParentCid(reply.cid); }} title="Reply to this post">{reply.shortCid} ) : ( Pending )}  p/ {thread.subplebbitAddress.includes(".eth") ? thread.subplebbitAddress : ( {thread.subplebbitAddress.slice(0, 10) + "(...)"} )}  
{replyMediaInfo?.url ? (
{replyMediaInfo?.type === "webpage" ? (
{reply.thumbnailUrl ? ( {replyMediaInfo.type} e.target.src = fallbackImgUrl} /> ) : null}
) : null} {replyMediaInfo?.type === "image" ? (
{replyMediaInfo.type} e.target.src = fallbackImgUrl} />
) : null} {replyMediaInfo?.type === "video" ? ( ) : null} {replyMediaInfo?.type === "audio" ? ( ) : null}
) : null} {reply.content ? ( reply.content?.length > 500 ?
{}} key={`r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} (...)

Comment too long.  setSelectedThread(thread.cid)} className="ttl-link">Click here  to view.
:
{}} key={`r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null}
) : null}
) })}
{index === 0 ? (
) : (
)}
{thread.author.displayName ? thread.author.displayName.length > 20 ? {thread.author.displayName.slice(0, 20) + " (...)"} : {thread.author.displayName} : Anonymous}   handleAddressClick(thread.author.shortAddress)} > (u/ {thread.author.shortAddress}
{thread.title ? ( thread.title.length > 30 ? {thread.title.slice(0, 30) + " (...)"} : {thread.title} ) : null}
p/ {thread.subplebbitAddress.includes(".eth") ? thread.subplebbitAddress : ( {thread.subplebbitAddress.slice(0, 10) + "(...)"} )} {getDate(thread.timestamp)}   c/ { if (e.button === 2) return; e.preventDefault(); setIsReplyOpen(true); setSelectedShortCid(thread.shortCid); setSelectedParentCid(thread.cid); }} title="Reply to this post">{thread.shortCid}
{thread.link ? (
{commentMediaInfo?.url ? ( commentMediaInfo.type === "webpage" ? (
{thread.thumbnailUrl ? ( thumbnail e.target.src = fallbackImgUrl} /> ) : null}
{commentMediaInfo?.type}
) : commentMediaInfo.type === "image" ? (
{commentMediaInfo.type} e.target.src = fallbackImgUrl} />
{commentMediaInfo?.type}
) : commentMediaInfo.type === "video" ? ( ) : commentMediaInfo.type === "audio" ? ( ) : null ) : null}
) : null} {thread.content ? ( thread.content?.length > 500 ?
(...)

Post too long.  setSelectedThread(thread.cid)} className="ttl-link">Click here  to view.
:
) : null}
{ (thread.replyCount + pendingReplyCounts[thread.cid]) === 0 ? ("No replies") : (thread.replyCount + pendingReplyCounts[thread.cid]) === 1 ? ("1 reply") : (thread.replyCount + pendingReplyCounts[thread.cid]) > 1 ? ((thread.replyCount + pendingReplyCounts[thread.cid]) + " replies") : null } setSelectedThread(thread.cid)} className="button-mobile" >View Thread
{displayedReplies?.map((reply, index) => { const replyMediaInfo = getCommentMediaInfo(reply); const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); return (
{reply.author.displayName ? reply.author.displayName.length > 20 ? {reply.author.displayName.slice(0, 20) + " (...)"} : {reply.author.displayName} : Anonymous}   handleAddressClick(reply.author.shortAddress)} > (u/ {reply.author?.shortAddress ? ( {reply.author?.shortAddress} ) : ( {account?.author?.address.slice(0, 8) + "(...)"} ) } ) 
 p/ {thread.subplebbitAddress.includes(".eth") ? thread.subplebbitAddress : ( {thread.subplebbitAddress.slice(0, 10) + "(...)"} )} {getDate(reply.timestamp)}  c/ {reply.shortCid ? ( { if (e.button === 2) return; e.preventDefault(); setIsReplyOpen(true); setSelectedShortCid(reply.shortCid); setSelectedParentCid(reply.cid); }} title="Reply to this post">{reply.shortCid} ) : ( Pending )}
{reply.link ? (
{replyMediaInfo?.url ? ( replyMediaInfo.type === "webpage" ? (
{reply.thumbnailUrl ? ( thumbnail e.target.src = fallbackImgUrl} /> ) : null}
{replyMediaInfo.type}
) : replyMediaInfo.type === "image" ? (
{replyMediaInfo.type} e.target.src = fallbackImgUrl} />
{replyMediaInfo.type}
) : replyMediaInfo.type === "video" ? ( ) : replyMediaInfo.type === "audio" ? ( ) : null ) : null}
) : null} {reply.content ? ( reply.content?.length > 500 ?
{}} key={`mob-r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} (...)

Comment too long.  setSelectedThread(thread.cid)} className="ttl-link">Click here  to view.
:
{}} key={`mob-r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null}
) : null} {reply.replyCount > 0 ? (
{reply.replies?.pages?.topAll.comments .sort((a, b) => a.timestamp - b.timestamp) .map((reply, index) => (
{}} onClick={(event) => handleQuoteClick(reply, reply.shortCid, event)} className="quote-link"> c/{reply.shortCid}  
))}
) : null}
)})}
); }} endReached={tryLoadMore} useWindowScroll={true} components={{ Footer: hasMore ? () => : null }} /> )}
); } export default Subscriptions;