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, PostMenuMobile } from '../styled/views/Board.styled'; import { Footer } from '../styled/views/Thread.styled'; import { AlertModal } from '../styled/modals/AlertModal.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 Embed from '../Embed'; 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 VerifiedAuthor from '../VerifiedAuthor'; import CreateBoardModal from '../modals/CreateBoardModal'; 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 getFormattedTime from '../../utils/getFormattedTime'; import handleAddressClick from '../../utils/handleAddressClick'; import handleImageClick from '../../utils/handleImageClick'; import handleQuoteClick from '../../utils/handleQuoteClick'; import handleQuoteHover from '../../utils/handleQuoteHover'; import handleShareClick from '../../utils/handleShareClick'; 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; let lastVirtuosoStates = {}; const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : ''; const Subscriptions = () => { const { defaultSubplebbits, editedComment, editedComments, feedCacheStates, setFeedCacheState, 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 threadMenuRefsMobile = useRef({}); const replyMenuRefs = useRef({}); const replyMenuRefsMobile = useRef({}); const postMenuCatalogRef = useRef(null); const postMenuMobileRef = 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 virtuosoRef = useRef(); 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 [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false); const [commentCid, setCommentCid] = useState(null); const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 }); const [mobileMenuPosition, setMobileMenuPosition] = useState({ top: 0, left: 0 }); const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false); const [openMenuCid, setOpenMenuCid] = useState(null); const [openMobileMenuCid, setOpenMobileMenuCid] = 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 [isThreadThumbnailClicked, setIsThreadThumbnailClicked] = useState({}); const [isReplyThumbnailClicked, setIsReplyThumbnailClicked] = useState({}); const [isMobileThreadThumbnailClicked, setIsMobileThreadThumbnailClicked] = useState({}); const [isMobileReplyThumbnailClicked, setIsMobileReplyThumbnailClicked] = useState({}); const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false); const setSelectedThreadCid = (cid) => { selectedThreadCidRef.current = cid; }; const isFeedCached = feedCacheStates['subscriptions']; useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode); const { feed, 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(account?.subscriptions); useEffect(() => { if (feed) { setFeedCacheState('subscriptions', true); } }, [feed, setFeedCacheState]); const handleThumbnailClick = (index, type) => { switch (type) { case 'thread': setIsThreadThumbnailClicked((prevState) => ({ ...prevState, [index]: !prevState[index], })); break; case 'reply': setIsReplyThumbnailClicked((prevState) => ({ ...prevState, [index]: !prevState[index], })); break; case 'mobileThread': setIsMobileThreadThumbnailClicked((prevState) => ({ ...prevState, [index]: !prevState[index], })); break; case 'mobileReply': setIsMobileReplyThumbnailClicked((prevState) => ({ ...prevState, [index]: !prevState[index], })); break; default: break; } }; 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 handleMobileOptionClick = () => { setOpenMobileMenuCid(null); }; const handleOutsideClick = useCallback( (e) => { if (openMenuCid !== null && !postMenuCatalogRef.current.contains(e.target)) { setOpenMenuCid(null); } }, [openMenuCid, postMenuCatalogRef], ); const handleMobileOutsideClick = useCallback( (e) => { if (openMobileMenuCid !== null && !postMenuMobileRef.current.contains(e.target)) { setOpenMobileMenuCid(null); } }, [openMobileMenuCid, postMenuMobileRef], ); useEffect(() => { if (openMenuCid !== null) { document.addEventListener('click', handleOutsideClick); } else { document.removeEventListener('click', handleOutsideClick); } return () => { document.removeEventListener('click', handleOutsideClick); }; }, [openMenuCid, handleOutsideClick]); useEffect(() => { if (openMobileMenuCid !== null) { document.addEventListener('click', handleMobileOutsideClick); } else { document.removeEventListener('click', handleMobileOutsideClick); } return () => { document.removeEventListener('click', handleMobileOutsideClick); }; }, [openMobileMenuCid, handleMobileOutsideClick]); 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 new Set([...allThreadCids, ...allRepliesCids]); }, [flattenedRepliesByThread, selectedFeed]); const filter = useCallback((accountComment) => allParentCids.has(accountComment.parentCid), [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'); console.log('challenge success', challengeVerification); } 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}`); }; useEffect(() => { const setLastVirtuosoState = () => { virtuosoRef.current?.getState((snapshot) => { if (snapshot?.scrollTop === 0 || snapshot?.ranges?.length) { lastVirtuosoStates['subscriptions'] = snapshot; } }); }; window.addEventListener('scroll', setLastVirtuosoState); return () => window.removeEventListener('scroll', setLastVirtuosoState); }, []); const lastVirtuosoState = lastVirtuosoStates['subscriptions']; return ( <> Subscriptions - plebchan setIsCreateBoardOpen(false)} /> setIsReplyOpen(false)} /> setIsSettingsOpen(false)} /> { setIsModerationOpen(false); setDeletePost(false); }} deletePost={deletePost} /> setIsEditModalOpen(false)} originalCommentContent={originalCommentContent} /> <> [ window.scrollTo(0, 0)}> All  /  window.scrollTo(0, 0)}> 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} ))} ] [ { window.electron && window.electron.isElectron ? setIsCreateBoardOpen(true) : alert( 'You can create a board with the desktop version of plebchan:\nhttps://github.com/plebbit/plebchan/releases/latest\n\nIf you are comfortable with the command line, use plebbit-cli:\nhttps://github.com/plebbit/plebbit-cli\n\n', ); }} > Create Board ] [ setIsSettingsOpen(true)}> Settings ] [Home]
Board     alert( 'You can create a board with the desktop version of plebchan:\nhttps://github.com/plebbit/plebchan/releases/latest\n\nIf you are comfortable with the command line, use plebbit-cli:\nhttps://github.com/plebbit/plebbit-cli\n\n', ) } > Create Board
setIsSettingsOpen(true)}> Settings   { handleStyleChange({ target: { value: 'Yotsuba' } }); window.scrollTo(0, 0); }} > Home
 
 
<>
<>
Subscriptions
{account?.subscriptions.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]
{subplebbits.state === 'succeeded' ? null : (
{stateString}
)}
Catalog
{!feed ? null : ( { if (editedComments[thread.cid]) { thread = editedComments[thread.cid]; if (thread.removed) { thread.content = '[removed]'; thread.link = undefined; } } const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {}; const commentMediaInfo = getCommentMediaInfo(thread); const fallbackImgUrl = 'assets/filedeleted-res.gif'; const isModerator = moderatorPermissions[thread.subplebbitAddress]; let displayWidth, displayHeight, displayWidthMobile, displayHeightMobile; if (thread.linkWidth && thread.linkHeight) { let scale = Math.min(1, 250 / Math.max(thread.linkWidth, thread.linkHeight)); displayWidth = `${thread.linkWidth * scale}px`; displayHeight = `${thread.linkHeight * scale}px`; scale = Math.min(1, 100 / Math.max(thread.linkWidth, thread.linkHeight)); displayWidthMobile = `${thread.linkWidth * scale}px`; displayHeightMobile = `${thread.linkHeight * scale}px`; } else { displayWidth = '250px'; displayHeight = '250px'; displayWidthMobile = '100px'; displayHeightMobile = '100px'; } return (

{commentMediaInfo?.url ? (
Link:  {commentMediaInfo?.url.length > 30 ? commentMediaInfo?.url.slice(0, 30) + '(...)' : commentMediaInfo?.url} {commentMediaInfo?.type === 'iframe' ? null : ` (${commentMediaInfo?.type})`} {((isThreadThumbnailClicked[index] && (commentMediaInfo.type === 'iframe' || commentMediaInfo.type === 'video')) || (commentMediaInfo.type === 'iframe' && !commentMediaInfo.thumbnail)) && (  [ { handleThumbnailClick(index, 'thread'); }} > {isThreadThumbnailClicked[index] ? 'Close' : 'Embed'} ] )}
{commentMediaInfo?.type === 'iframe' && (
{isThreadThumbnailClicked[index] && commentMediaInfo.url ? ( ) : commentMediaInfo.thumbnail ? ( thumbnail { handleThumbnailClick(index, 'thread'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : null}
)} {commentMediaInfo?.type === 'webpage' ? (
{thread.thumbnailUrl ? ( {commentMediaInfo.type} { handleImageClick(e); handleThumbnailClick(index, 'thread'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : null}
) : null} {commentMediaInfo?.type === 'image' ? (
{commentMediaInfo.type} { handleImageClick(e); handleThumbnailClick(index, 'thread'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} />
) : null} {commentMediaInfo?.type === 'video' ? (
{isThreadThumbnailClicked[index] ? (
) : 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( {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'}, ) } > {{({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'}} )   {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); handleShareClick(thread.subplebbitAddress, thread.cid); }} > Share thread
  • {({ authorAddress }) => ( <> {authorAddress === account?.author.address || authorAddress === account?.signer.address ? ( <>
  • handleAuthorEditClick(thread)}>Edit post
  • handleAuthorDeleteClick(thread)}>Delete post
  • ) : null} {isModerator ? ( <> {authorAddress === account?.author.address || authorAddress === 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 »
  • ) : null}
  • { setIsClientRedirectMenuOpen(true); }} onMouseLeave={() => { setIsClientRedirectMenuOpen(false); }} > View on »
    • handleOptionClick(thread.cid)}> Plebbit
    • {/*
    • handleOptionClick(thread.cid)}> Seedit
    • */}
    • handleOptionClick(thread.cid)}> Plebones
, 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) => { if (editedComments[reply.cid]) { reply = editedComments[reply.cid]; if (reply.removed) { reply.content = '[removed]'; reply.link = undefined; } } const replyMediaInfo = getCommentMediaInfo(reply); const fallbackImgUrl = 'assets/filedeleted-res.gif'; const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); let replyDisplayWidth, replyDisplayHeight; if (reply.linkWidth && reply.linkHeight) { const scale = Math.min(1, 125 / Math.max(reply.linkWidth, reply.linkHeight)); replyDisplayWidth = `${reply.linkWidth * scale}px`; replyDisplayHeight = `${reply.linkHeight * scale}px`; } else { replyDisplayWidth = '125px'; replyDisplayHeight = '125px'; } return (
{'>>'}
{reply.author.displayName ? ( reply.author.displayName.length > 20 ? ( {reply.author.displayName.slice(0, 20) + ' (...)'} ) : ( {reply.author.displayName} ) ) : ( Anonymous )}   handleAddressClick( {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'}, ) } > (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); handleShareClick(thread.subplebbitAddress, reply.cid); }} > Share post
  • {({ authorAddress }) => ( <> {authorAddress === account?.author.address || authorAddress === account?.signer.address ? ( <>
  • handleAuthorEditClick(reply)}>Edit post
  • handleAuthorDeleteClick(reply)}>Delete post
  • ) : null} {isModerator ? ( <> {authorAddress === account?.author.address || authorAddress === 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 »
  • ) : null}
  • { setIsClientRedirectMenuOpen(true); }} onMouseLeave={() => { setIsClientRedirectMenuOpen(false); }} > View on »
    • handleOptionClick(reply.cid)}> Plebbit
    • {/*
    • handleOptionClick(reply.cid)}> Seedit
    • */}
    • handleOptionClick(reply.cid)}> Plebones
, document.body, )}
{replyMediaInfo?.url ? (
Link:  {replyMediaInfo?.url.length > 30 ? replyMediaInfo?.url.slice(0, 30) + '(...)' : replyMediaInfo?.url} {replyMediaInfo?.type === 'iframe' ? null : ` (${replyMediaInfo?.type})`} {((isReplyThumbnailClicked[index] && (replyMediaInfo.type === 'iframe' || replyMediaInfo.type === 'video')) || (replyMediaInfo.type === 'iframe' && !replyMediaInfo.thumbnail)) && (  [ { handleThumbnailClick(index, 'reply'); }} > {isReplyThumbnailClicked[index] ? 'Close' : 'Embed'} ] )}
{replyMediaInfo?.type === 'iframe' && (
{isReplyThumbnailClicked[index] && replyMediaInfo.url ? ( ) : replyMediaInfo.thumbnail ? ( thumbnail { handleThumbnailClick(index, 'reply'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : null}
)} {replyMediaInfo?.type === 'webpage' && (
{reply.thumbnailUrl ? ( {replyMediaInfo.type} { handleImageClick(e); handleThumbnailClick(index, 'reply'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : null}
)} {replyMediaInfo?.type === 'image' && (
{replyMediaInfo.type} { handleImageClick(e); handleThumbnailClick(index, 'reply'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} />
)} {replyMediaInfo?.type === 'video' && (
{isReplyThumbnailClicked[index] ? (
)} {replyMediaInfo?.type === 'audio' && ( )}
) : 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 ?
:
}
{createPortal( { postMenuMobileRef.current = el; }} onClick={(event) => event.stopPropagation()} style={{ position: 'absolute', display: openMobileMenuCid === thread.cid ? 'block' : 'none', top: mobileMenuPosition.top + 20, left: mobileMenuPosition.left, }} >
  • { handleMobileOptionClick(thread.cid); handleShareClick(selectedAddress, thread.cid); }} > Share thread
  • {({ authorAddress }) => ( <> {authorAddress === account?.author.address || authorAddress === account?.signer.address ? ( <>
  • handleAuthorEditClick(thread)}>Edit post
  • handleAuthorDeleteClick(thread)}>Delete post
  • ) : null} {isModerator ? ( <> {authorAddress === account?.author.address || authorAddress === account?.signer.address ? null : (
  • { setModeratingCommentCid(thread.cid); setIsModerationOpen(true); handleMobileOptionClick(thread.cid); setDeletePost(true); }} > Delete post
  • )}
  • { setModeratingCommentCid(thread.cid); setIsModerationOpen(true); handleMobileOptionClick(thread.cid); }} > Mod tools
  • ) : null} )}
    {commentMediaInfo && (commentMediaInfo.type === 'image' || (commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail)) ? ( <>
  • handleMobileOptionClick(thread.cid)}>Search image on Google
  • handleMobileOptionClick(thread.cid)}>Search image on Yandex
  • handleMobileOptionClick(thread.cid)}>Search image on SauceNAO
  • ) : null}
, document.body, )} {thread.author.displayName ? ( thread.author.displayName.length > 20 ? ( {thread.author.displayName.slice(0, 20) + ' (...)'} ) : ( {thread.author.displayName} ) ) : ( Anonymous )}   handleAddressClick( {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'}, ) } > (u/ {{({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'}}
{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?.type === 'iframe' && (
{isMobileThreadThumbnailClicked[index] && commentMediaInfo.url ? (
) : commentMediaInfo.thumbnail ? ( thumbnail { handleThumbnailClick(index, 'mobileThread'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : ( { handleThumbnailClick(index, 'mobileThread'); }} > Embed )} {commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'iframe' ? ( isMobileThreadThumbnailClicked[index] ? (
{ handleThumbnailClick(index, 'mobileThread'); }} > Close
) : (
webpage
) ) : (
webpage
)}
)} {commentMediaInfo?.url ? ( commentMediaInfo.type === 'webpage' ? (
{thread.thumbnailUrl ? ( thumbnail { handleImageClick(e); handleThumbnailClick(index, 'mobileThread'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : null}
{commentMediaInfo?.type}
) : commentMediaInfo.type === 'image' ? (
{commentMediaInfo.type} { handleImageClick(e); handleThumbnailClick(index, 'mobileThread'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} />
{commentMediaInfo?.type}
) : commentMediaInfo.type === 'video' ? ( {isMobileThreadThumbnailClicked[index] ? ( ) : 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) => { if (editedComments[reply.cid]) { reply = editedComments[reply.cid]; if (reply.removed) { reply.content = '[removed]'; reply.link = undefined; } } const replyMediaInfo = getCommentMediaInfo(reply); const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); return (
{createPortal( { postMenuMobileRef.current = el; }} onClick={(event) => event.stopPropagation()} style={{ position: 'absolute', display: openMobileMenuCid === reply.cid ? 'block' : 'none', top: mobileMenuPosition.top + 20, left: mobileMenuPosition.left, }} >
  • { handleMobileOptionClick(reply.cid); handleShareClick(selectedAddress, reply.cid); }} > Share post
  • {({ authorAddress }) => ( <> {authorAddress === account?.author.address || authorAddress === account?.signer.address ? ( <>
  • handleAuthorEditClick(reply)}>Edit post
  • handleAuthorDeleteClick(reply)}>Delete post
  • ) : null} {isModerator ? ( <> {authorAddress === account?.author.address || authorAddress === account?.signer.address ? null : (
  • { setModeratingCommentCid(reply.cid); setIsModerationOpen(true); handleMobileOptionClick(reply.cid); setDeletePost(true); }} > Delete post
  • )}
  • { setModeratingCommentCid(reply.cid); setIsModerationOpen(true); handleMobileOptionClick(reply.cid); }} > Mod tools
  • ) : null} )}
    {replyMediaInfo && (replyMediaInfo.type === 'image' || (replyMediaInfo.type === 'webpage' && replyMediaInfo.thumbnail)) ? ( <>
  • handleMobileOptionClick(reply.cid)}>Search image on Google
  • handleMobileOptionClick(reply.cid)}>Search image on Yandex
  • handleMobileOptionClick(reply.cid)}>Search image on SauceNAO
  • ) : null}
, document.body, )} {reply.author.displayName ? ( reply.author.displayName.length > 20 ? ( {reply.author.displayName.slice(0, 20) + ' (...)'} ) : ( {reply.author.displayName} ) ) : ( Anonymous )}   handleAddressClick( {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'}, ) } > (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 === 'iframe' ? (
{isMobileReplyThumbnailClicked[index] && replyMediaInfo.url ? (
) : replyMediaInfo.thumbnail ? ( thumbnail { handleThumbnailClick(index, 'mobileReply'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : ( { handleThumbnailClick(index, 'mobileReply'); }} > Embed )} {replyMediaInfo?.type === 'video' || replyMediaInfo?.type === 'iframe' ? ( isMobileReplyThumbnailClicked[index] ? (
{ handleThumbnailClick(index, 'mobileReply'); }} > Close
) : (
webpage
) ) : (
webpage
)}
) : replyMediaInfo.type === 'webpage' ? (
{reply.thumbnailUrl ? ( thumbnail { handleImageClick(e); handleThumbnailClick(index, 'mobileReply'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} /> ) : null}
{replyMediaInfo.type}
) : replyMediaInfo.type === 'image' ? (
{replyMediaInfo.type} { handleImageClick(e); handleThumbnailClick(index, 'mobileReply'); }} style={{ cursor: 'pointer' }} onError={(e) => (e.target.src = fallbackImgUrl)} />
{replyMediaInfo.type}
) : replyMediaInfo.type === 'video' ? ( {isMobileReplyThumbnailClicked[index] ? ( ) : 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}
); })}
); }} ref={virtuosoRef} restoreStateFrom={lastVirtuosoState} initialScrollTop={lastVirtuosoState?.scrollTop} endReached={tryLoadMore} useWindowScroll={true} components={{ Footer: () => { return isFeedCached ? null : ; }, }} /> )}
{outOfViewCid && outOfViewPosition && createPortal(
, document.body, )}
); }; export default Subscriptions;