import React, { Fragment, useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { confirmAlert } from 'react-confirm-alert'; import { createPortal } from 'react-dom'; import { Helmet } from 'react-helmet-async'; import { Link, useNavigate } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; import { Virtuoso } from 'react-virtuoso'; import { useAccount, useAccountComments, useFeed, usePublishCommentEdit, useSubplebbits } 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, PostMenu } from '../styled/views/Board.styled'; import { AuthorDeleteAlert, Footer } from '../styled/views/Thread.styled'; import { PostMenuCatalog } from '../styled/views/Catalog.styled'; import EditModal from '../modals/EditModal'; import EditLabel from '../EditLabel'; import ImageBanner from '../ImageBanner'; import ModerationModal from '../modals/ModerationModal'; import OfflineIndicator from '../OfflineIndicator'; import PendingLabel from '../PendingLabel'; import Post from '../Post'; import PostLoader from '../PostLoader'; import PostOnHover from '../PostOnHover'; import StateLabel from '../StateLabel'; import ReplyModal from '../modals/ReplyModal'; import SettingsModal from '../modals/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 handleQuoteHover from '../../utils/handleQuoteHover'; import handleStyleChange from '../../utils/handleStyleChange'; import removeHighlight from '../../utils/removeHighlight'; import useAnonModeRef from '../../hooks/useAnonModeRef'; import useError from '../../hooks/useError'; import useFeedStateString from '../../hooks/useFeedStateString'; import useSuccess from '../../hooks/useSuccess'; import useAnonModeStore from '../../hooks/stores/useAnonModeStore'; import packageJson from '../../../package.json' const {version} = packageJson const Subscriptions = () => { const { defaultSubplebbits, editedComment, isSettingsOpen, setIsSettingsOpen, setModeratingCommentCid, selectedAddress, setCaptchaResponse, setChallengesArray, setIsAuthorDelete, setIsAuthorEdit, setIsCaptchaOpen, isModerationOpen, setIsModerationOpen, setReplyQuoteCid, setResolveCaptchaPromise, setSelectedAddress, setSelectedParentCid, setSelectedShortCid, selectedStyle, setSelectedThread, setSelectedText, setSelectedTitle, setTriggerInsertion, } = useGeneralStore(state => state); const { anonymousMode } = useAnonModeStore(); const account = useAccount(); const navigate = useNavigate(); const [, setNewErrorMessage] = useError(); const [, setNewSuccessMessage] = useSuccess(); const threadMenuRefs = useRef({}); const replyMenuRefs = useRef({}); const postMenuCatalogRef = useRef(null); const backlinkRefs = useRef({}); const quoteRefs = useRef({}); const postRefs = useRef({}); const backlinkRefsMobile = useRef({}); const quoteRefsMobile = useRef({}); const postOnHoverRef = useRef(null); const selectedThreadCidRef = useRef(null); const [isReplyOpen, setIsReplyOpen] = useState(false); const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [originalCommentContent, setOriginalCommentContent] = useState(null); const [prevScrollPos, setPrevScrollPos] = useState(0); const [visible, setVisible] = useState(true); const [isImageSearchOpen, setIsImageSearchOpen] = useState(false); const [commentCid, setCommentCid] = useState(null); const [menuPosition, setMenuPosition] = useState({top: 0, left: 0}); const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false); const [openMenuCid, setOpenMenuCid] = useState(null); const [outOfViewCid, setOutOfViewCid] = useState(null); const [outOfViewPosition, setOutOfViewPosition] = useState({top: 0, left: 0}); const [postOnHoverHeight, setPostOnHoverHeight] = useState(0); const [deletePost, setDeletePost] = useState(false); const [moderatorPermissions, setModeratorPermissions] = useState({}); const [executeAnonMode, setExecuteAnonMode] = useState(false); const [cidTracker, setCidTracker] = useState({}); const setSelectedThreadCid = (cid) => { selectedThreadCidRef.current = cid; } useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode); const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: account?.subscriptions, sortType: 'active'}); const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); const {subplebbits} = useSubplebbits({subplebbitAddresses: account?.subscriptions, sortType: 'active'}); const stateString = useFeedStateString(subplebbits); useEffect(() => { let permissions = {}; selectedFeed.forEach(thread => { const subplebbit = subplebbits.find(s => s && s.address === thread.subplebbitAddress); if (subplebbit && subplebbit.roles) { const role = subplebbit.roles[account?.author.address]?.role; if (role === 'moderator' || role === 'admin' || role === 'owner') { permissions[thread.subplebbitAddress] = true; } else { permissions[thread.subplebbitAddress] = false; } } }); setModeratorPermissions(permissions); }, [account?.author.address, selectedFeed, subplebbits]); const handleOptionClick = () => { setOpenMenuCid(null); }; const handleOutsideClick = useCallback((e) => { if (openMenuCid !== null && !postMenuCatalogRef.current.contains(e.target)) { setOpenMenuCid(null); } }, [openMenuCid, postMenuCatalogRef]); useEffect(() => { if (openMenuCid !== null) { document.addEventListener('click', handleOutsideClick); } else { document.removeEventListener('click', handleOutsideClick); } return () => { document.removeEventListener('click', handleOutsideClick); }; }, [openMenuCid, handleOutsideClick]); useEffect(() => { if (postOnHoverRef.current) { const rect = postOnHoverRef.current.getBoundingClientRect(); setPostOnHoverHeight(rect.height); } }, [outOfViewCid]); const errorString = useMemo(() => { for (const subplebbit of subplebbits) { if (subplebbit?.updatingState !== 'failed') { return } } for (const subplebbit of subplebbits) { if (subplebbit?.error) { return `Failed fetching subplebbit: ${subplebbit?.error.toString().slice(0, 300)}` } } }, [subplebbits]) useEffect(() => { if (errorString) { setNewErrorMessage(errorString); } }, [errorString, setNewErrorMessage]); useEffect(() => { setSelectedFeed(feed.sort((a, b) => b.timestamp - a.timestamp)); }, [feed]); 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]); const allReplies = useMemo(() => { return selectedFeed.flatMap(thread => filteredRepliesByThread[thread.cid]?.displayedReplies || []); }, [selectedFeed, filteredRepliesByThread]); // let post.jsx access full cid of user-typed short cid useEffect(() => { const newCidTracker = {}; allReplies.forEach((reply) => { newCidTracker[reply.shortCid] = reply.cid; }); setCidTracker(newCidTracker); }, [allReplies]); // 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)); } }; const onChallengeVerification = (challengeVerification) => { if (challengeVerification.challengeSuccess === true) { setNewSuccessMessage('Challenge Success'); } else if (challengeVerification.challengeSuccess === false) { setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`); console.log('challenge failed', challengeVerification); } }; const onChallenge = async (challenges, comment) => { let challengeAnswers = []; try { challengeAnswers = await getChallengeAnswersFromUser(challenges) } catch (error) { setNewErrorMessage(error.message); console.log(error); } if (challengeAnswers) { await comment.publishChallengeAnswers(challengeAnswers) } }; const getChallengeAnswersFromUser = async (challenges) => { setChallengesArray(challenges); return new Promise((resolve, reject) => { const imageString = challenges?.challenges[0].challenge; const imageSource = `data:image/png;base64,${imageString}`; const challengeImg = new Image(); challengeImg.src = imageSource; challengeImg.onload = () => { setIsCaptchaOpen(true); const handleKeyDown = async (event) => { if (event.key === 'Enter') { const currentCaptchaResponse = useGeneralStore.getState().captchaResponse; resolve(currentCaptchaResponse); setIsCaptchaOpen(false); document.removeEventListener('keydown', handleKeyDown); event.preventDefault(); } }; setCaptchaResponse(''); document.addEventListener('keydown', handleKeyDown); setResolveCaptchaPromise(resolve); }; challengeImg.onerror = () => { reject(setNewErrorMessage('Could not load challenges')); }; }); }; const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({ commentCid: commentCid, content: editedComment || undefined, subplebbitAddress: selectedAddress, onChallenge, onChallengeVerification, onError: (error) => { setNewErrorMessage(error.message); console.log(error); }, }); useEffect(() => { if (anonymousMode) { setExecuteAnonMode(true); } }, [anonymousMode, selectedThreadCidRef]); const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); const handleAuthorDeleteClick = (comment) => { handleOptionClick(comment.cid); confirmAlert({ customUI: ({ onClose }) => { return (

Are you sure you want to delete this post?

); } }); }; const handleAuthorEditClick = (comment) => { handleOptionClick(comment.cid); setIsAuthorEdit(true); setIsAuthorDelete(false); setCommentCid(comment.cid); setOriginalCommentContent(comment.content); setIsEditModalOpen(true); } useEffect(() => { setPublishCommentEditOptions((prevOptions) => ({ ...prevOptions, commentCid: commentCid, content: editedComment || undefined, })); }, [commentCid, editedComment]); useEffect(() => { if (editedComment !== '') { setTriggerPublishCommentEdit(true); } }, [editedComment, setIsAuthorEdit]); useEffect(() => { let isActive = true; if (publishCommentEditOptions && triggerPublishCommentEdit) { (async () => { await publishCommentEdit(); if (isActive) { setTriggerPublishCommentEdit(false); } })(); } return () => { isActive = false; }; }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]); // desktop navbar board select functionality const handleClickTitle = (title, address) => { setSelectedTitle(title); setSelectedAddress(address); setSelectedFeed(feed.filter(feed => feed.title === title)); }; // mobile navbar board select functionality const handleSelectChange = (event) => { const selected = event.target.value; if (selected === 'subscriptions') { navigate(`/p/subscriptions`); return; } else if (selected === 'all') { navigate(`/p/all`); return; } const selectedTitle = defaultSubplebbits?.find((subplebbit) => subplebbit.address === selected).title; setSelectedTitle(selectedTitle); setSelectedAddress(selected); navigate(`/p/${selected}`); }; return ( <> Subscriptions - plebchan setIsReplyOpen(false)} /> setIsSettingsOpen(false)} /> {setIsModerationOpen(false); setDeletePost(false)}} deletePost={deletePost} /> setIsEditModalOpen(false)} originalCommentContent={originalCommentContent} /> <> [ All  /  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} ))} ] [ alert( 'To create a board, first you have to run a full node.\nYou can run a full node by simply browsing with the plebbit desktop app. After you download it, open it, wait for it loading, then click on "Home" in the top left, then "Create Community".\n\nAfter you create the community, you can go back to plebchan at any time to see it as a board by pasting its address (begins with p/12D3KooW...) in the search bar, which is in the Home.\n\nNote:\n\n- Your community will be online for as long as you leave the app open, because it functions like a server for the community.\n- The longer you leave the app open, the more data you are seeding to the protocol, which helps performance for everybody.\n - All the data in the plebbit protocol is just text, which is extremely lightweight. All media is generated by links, which is text, embedded by the clients.\n\nDownload the plebbit app here: https://github.com/plebbit/plebbit-react/releases\n\nYou can also use a CLI: https://github.com/plebbit/plebbit-cli\n\nRunning boards in the plebchan app is a planned feature.\n\n' ) }>Create Board ] [ setIsSettingsOpen(true)}>Settings ] [ Home ]
Board     alert( 'To create a board, first you have to run a full node.\nYou can run a full node by simply browsing with the plebbit desktop app. After you download it, open it, wait for it loading, then click on "Home" in the top left, then "Create Community".\n\nAfter you create the community, you can go back to plebchan at any time to see it as a board by pasting its address (begins with p/12D3KooW...) in the search bar, which is in the Home.\n\nNote:\n\n- Your community will be online for as long as you leave the app open, because it functions like a server for the community.\n- The longer you leave the app open, the more data you are seeding to the protocol, which helps performance for everybody.\n - All the data in the plebbit protocol is just text, which is extremely lightweight. All media is generated by links, which is text, embedded by the clients.\n\nDownload the plebbit app here: https://github.com/plebbit/plebbit-react/releases\n\nYou can also use a CLI: https://github.com/plebbit/plebbit-cli\n\nRunning boards in the plebchan app is a planned feature.\n\n' ) }>Create Board
setIsSettingsOpen(true)}>Settings   {handleStyleChange({target: {value: "Yotsuba"}}); window.scrollTo(0, 0);}}>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:  
[ Catalog ]
{feed.length > 0 ? ( null ) : (
{stateString}
)}
Catalog
{ feed.length < 1 ? ( null ) : ( { const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {}; const commentMediaInfo = getCommentMediaInfo(thread); const fallbackImgUrl = "assets/filedeleted-res.gif"; const isModerator = moderatorPermissions[thread.subplebbitAddress]; 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(); setSelectedThreadCid(thread.cid); setSelectedAddress(thread.subplebbitAddress); let text = document.getSelection().toString(); text = text ? `>${text}` : text; setSelectedText(text); if (isReplyOpen) { setReplyQuoteCid(thread.shortCid); setTriggerInsertion(Date.now()); } else { 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 ]   { threadMenuRefs.current[thread.cid] = el; }} className='post-menu-button' rotated={openMenuCid === thread.cid} onClick={(event) => { event.stopPropagation(); const rect = threadMenuRefs.current[thread.cid].getBoundingClientRect(); setMenuPosition({top: rect.top + window.scrollY, left: rect.left}); setOpenMenuCid(prevCid => (prevCid === thread.cid ? null : thread.cid)); }} > ▶ {createPortal( {postMenuCatalogRef.current = el}} onClick={(event) => event.stopPropagation()} style={{position: "absolute", top: menuPosition.top + 7, left: menuPosition.left}}>
  • handleOptionClick(thread.cid)}>Hide thread
  • {thread.author.address === account?.author.address || thread.author.address === account?.signer.address ? ( <>
  • {handleAuthorEditClick(thread); setSelectedAddress(thread.subplebbitAddress);}}>Edit post
  • {handleAuthorDeleteClick(thread); setSelectedAddress(thread.subplebbitAddress);}}>Delete post
  • ) : null} {isModerator ? ( <> {thread.author.address === account?.author.address || thread.author.address === account?.signer.address ? ( null ) : (
  • { setSelectedAddress(thread.subplebbitAddress); setModeratingCommentCid(thread.cid) setIsModerationOpen(true); handleOptionClick(thread.cid); setDeletePost(true); }}> Delete post
  • )}
  • { setSelectedAddress(thread.subplebbitAddress); setModeratingCommentCid(thread.cid) setIsModerationOpen(true); handleOptionClick(thread.cid); }}> Mod tools
  • ) : null} {(commentMediaInfo && ( commentMediaInfo.type === 'image' || (commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail))) ? (
  • {setIsImageSearchOpen(true)}} onMouseLeave={() => {setIsImageSearchOpen(false)}}> Image search »
    • handleOptionClick(thread.cid)}> Google
    • handleOptionClick(thread.cid)}> Yandex
    • handleOptionClick(thread.cid)}> SauceNAO
  • ) : null }
, document.body )}
{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(); setSelectedThreadCid(thread.cid); setSelectedAddress(thread.subplebbitAddress); let text = document.getSelection().toString(); text = text ? `>${text}` : text; setSelectedText(text); if (isReplyOpen) { setReplyQuoteCid(reply.shortCid); setTriggerInsertion(Date.now()); } else { setIsReplyOpen(true); setSelectedShortCid(reply.shortCid); setSelectedParentCid(reply.cid); } }} title="Reply to this post">{reply.shortCid} ) : ( )}  p/ {thread.subplebbitAddress.includes(".eth") ? (thread.subplebbitAddress) : ( {thread.subplebbitAddress.slice(0, 10) + "(...)"} )}   { replyMenuRefs.current[reply.cid] = el; }} className='post-menu-button' rotated={openMenuCid === reply.cid} onClick={(event) => { event.stopPropagation(); const rect = replyMenuRefs.current[reply.cid].getBoundingClientRect(); setMenuPosition({top: rect.top + window.scrollY, left: rect.left}); setOpenMenuCid(prevCid => (prevCid === reply.cid ? null : reply.cid)); }} > ▶ {createPortal( {postMenuCatalogRef.current = el}} onClick={(event) => event.stopPropagation()} style={{position: "absolute", top: menuPosition.top + 7, left: menuPosition.left}}>
  • handleOptionClick(reply.cid)}>Hide post
  • {reply.author.address === account?.author.address || reply.author.address === account?.signer.address ? ( <>
  • {handleAuthorEditClick(reply); setSelectedAddress(thread.subplebbitAddress);}}>Edit post
  • {handleAuthorDeleteClick(reply); setSelectedAddress(thread.subplebbitAddress);}}>Delete post
  • ) : null} {isModerator ? ( <> {reply.author.address === account?.author.address || reply.author.address === account?.signer.address ? ( null ) : (
  • { setSelectedAddress(thread.subplebbitAddress); setModeratingCommentCid(reply.cid) setIsModerationOpen(true); handleOptionClick(reply.cid); setDeletePost(true); }}> Delete post
  • )}
  • { setSelectedAddress(thread.subplebbitAddress); setModeratingCommentCid(reply.cid) setIsModerationOpen(true); handleOptionClick(reply.cid); }}> Mod tools
  • ) : null} {(replyMediaInfo && ( replyMediaInfo.type === 'image' || (replyMediaInfo.type === 'webpage' && replyMediaInfo.thumbnail))) ? (
  • {setIsImageSearchOpen(true)}} onMouseLeave={() => {setIsImageSearchOpen(false)}}> Image search »
    • handleOptionClick(reply.cid)}> Google
    • handleOptionClick(reply.cid)}> Yandex
    • handleOptionClick(reply.cid)}> SauceNAO
  • ) : null }
, document.body )}
{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" ref={el => { quoteRefs.current[reply.cid] = el; }} onClick={(event) => handleQuoteClick(reply, shortParentCid, event)} onMouseOver={(event) => { event.stopPropagation(); handleQuoteHover(reply, shortParentCid, () => { setOutOfViewCid(reply.parentCid); const rect = quoteRefs.current[reply.cid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }); }} onMouseLeave={() => { removeHighlight(); setOutOfViewCid(null); }}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} { postRefs.current[quoteShortParentCid] = ref; }} postQuoteOnClick={(quoteShortParentCid) => { handleQuoteClick(reply, quoteShortParentCid, null) }} postQuoteOnOver={(quoteShortParentCid) => { const quoteParentCid = cidTracker[quoteShortParentCid]; if (outOfViewCid !== quoteParentCid) { handleQuoteHover(reply, quoteShortParentCid, () => { setOutOfViewCid(quoteParentCid); const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }) }} } postQuoteOnLeave={() => { removeHighlight(); setOutOfViewCid(null); }} /> (...)

Comment too long.  setSelectedThread(thread.cid)} className="ttl-link">Click here  to view.
:
{}} key={`r-pm-${index}`} className="quotelink" ref={el => { quoteRefs.current[reply.cid] = el; }} onClick={(event) => handleQuoteClick(reply, shortParentCid, event)} onMouseOver={(event) => { event.stopPropagation(); handleQuoteHover(reply, shortParentCid, () => { setOutOfViewCid(reply.parentCid); const rect = quoteRefs.current[reply.cid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }); }} onMouseLeave={() => { removeHighlight(); setOutOfViewCid(null); }}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} { postRefs.current[quoteShortParentCid] = ref; }} postQuoteOnClick={(quoteShortParentCid) => { handleQuoteClick(reply, quoteShortParentCid, null) }} postQuoteOnOver={(quoteShortParentCid) => { const quoteParentCid = cidTracker[quoteShortParentCid]; if (outOfViewCid !== quoteParentCid) { handleQuoteHover(reply, quoteShortParentCid, () => { setOutOfViewCid(quoteParentCid); const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }) }} } postQuoteOnLeave={() => { removeHighlight(); setOutOfViewCid(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(); setSelectedThreadCid(thread.cid); setSelectedAddress(thread.subplebbitAddress); let text = document.getSelection().toString(); text = text ? `>${text}` : text; setSelectedText(text); if (isReplyOpen) { setReplyQuoteCid(thread.shortCid); setTriggerInsertion(Date.now()); } else { 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(); setSelectedThreadCid(thread.cid); setSelectedAddress(thread.subplebbitAddress); let text = document.getSelection().toString(); text = text ? `>${text}` : text; setSelectedText(text); if (isReplyOpen) { setReplyQuoteCid(reply.shortCid); setTriggerInsertion(Date.now()); } else { setIsReplyOpen(true); setSelectedShortCid(reply.shortCid); setSelectedParentCid(reply.cid); } }} title="Reply to this post">{reply.shortCid} ) : ( )}
{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" ref={el => { quoteRefsMobile.current[reply.cid] = el; }} onClick={(event) => handleQuoteClick(reply, shortParentCid, event)} onMouseOver={(event) => { event.stopPropagation(); handleQuoteHover(reply, shortParentCid, () => { setOutOfViewCid(reply.parentCid); const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }); }} onMouseLeave={() => { removeHighlight(); setOutOfViewCid(null); }}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} { postRefs.current[quoteShortParentCid] = ref; }} postQuoteOnClick={(quoteShortParentCid) => { handleQuoteClick(reply, quoteShortParentCid, null) }} postQuoteOnOver={(quoteShortParentCid) => { const quoteParentCid = cidTracker[quoteShortParentCid]; if (outOfViewCid !== quoteParentCid) { handleQuoteHover(reply, quoteShortParentCid, () => { setOutOfViewCid(quoteParentCid); const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }) }} } postQuoteOnLeave={() => { removeHighlight(); setOutOfViewCid(null); }} /> (...)

Comment too long.  setSelectedThread(thread.cid)} className="ttl-link">Click here  to view.
:
{}} key={`mob-r-pm-${index}`} className="quotelink" ref={el => { quoteRefsMobile.current[reply.cid] = el; }} onClick={(event) => handleQuoteClick(reply, shortParentCid, event)} onMouseOver={(event) => { event.stopPropagation(); handleQuoteHover(reply, shortParentCid, () => { setOutOfViewCid(reply.parentCid); const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }); }} onMouseLeave={() => { removeHighlight(); setOutOfViewCid(null); }}> {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} { postRefs.current[quoteShortParentCid] = ref; }} postQuoteOnClick={(quoteShortParentCid) => { handleQuoteClick(reply, quoteShortParentCid, null) }} postQuoteOnOver={(quoteShortParentCid) => { const quoteParentCid = cidTracker[quoteShortParentCid]; if (outOfViewCid !== quoteParentCid) { handleQuoteHover(reply, quoteShortParentCid, () => { setOutOfViewCid(quoteParentCid); const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }) }} } postQuoteOnLeave={() => { removeHighlight(); setOutOfViewCid(null); }} />
) : null} {reply.replyCount > 0 ? (
{reply.replies?.pages?.topAll.comments .sort((a, b) => a.timestamp - b.timestamp) .map((reply, index) => (
{ backlinkRefsMobile.current[reply.cid] = el; }}> {}} onClick={(event) => handleQuoteClick(reply, reply.shortCid, event)} onMouseOver={(event) => { event.stopPropagation(); handleQuoteHover(reply, reply.shortCid, () => { setOutOfViewCid(reply.cid) const rect = backlinkRefsMobile.current[reply.cid].getBoundingClientRect(); const distanceToRight = window.innerWidth - rect.right; const distanceToTop = rect.top; const distanceToBottom = window.innerHeight - rect.bottom; let top; if (distanceToTop < postOnHoverHeight / 2) { top = window.scrollY - 5; } else if (distanceToBottom < postOnHoverHeight / 2) { top = window.scrollY - postOnHoverHeight + window.innerHeight - 10; } else { top = rect.top + window.scrollY - postOnHoverHeight / 2; } if (distanceToRight < 200) { setOutOfViewPosition({ top, right: window.innerWidth - rect.left - 10, maxWidth: rect.left - 5 }); } else { setOutOfViewPosition({ top, left: rect.left + rect.width + 5, maxWidth: window.innerWidth - rect.left - rect.width - 5 }); } }); }} onMouseLeave={() => { removeHighlight(); setOutOfViewCid(null); }} className="quote-link"> c/{reply.shortCid}  
))}
) : null}
)})}
); }} endReached={tryLoadMore} useWindowScroll={true} components={{ Footer: hasMore ? () => : null }} /> )}
{outOfViewCid && outOfViewPosition && createPortal(
, document.body)}
); } export default Subscriptions;