From 51b50c1cfad831db24fa562a6975783462d8d217 Mon Sep 17 00:00:00 2001 From: "plebeius.eth" Date: Thu, 21 Sep 2023 14:38:12 +0200 Subject: [PATCH] chore(post menu): update plebones link to eth limo --- src/components/views/All.jsx | 5964 +++++++------ src/components/views/AllCatalog.jsx | 1892 ++-- src/components/views/Board.jsx | 7874 +++++++++-------- src/components/views/Catalog.jsx | 3226 +++---- src/components/views/Description.jsx | 1342 +-- src/components/views/Rules.jsx | 1225 +-- src/components/views/Subscriptions.jsx | 5976 +++++++------ src/components/views/SubscriptionsCatalog.jsx | 1902 ++-- src/components/views/Thread.jsx | 5746 ++++++------ 9 files changed, 18846 insertions(+), 16301 deletions(-) diff --git a/src/components/views/All.jsx b/src/components/views/All.jsx index 13ef8e2b..27a7b875 100755 --- a/src/components/views/All.jsx +++ b/src/components/views/All.jsx @@ -6,7 +6,7 @@ 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 { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils'; import { debounce } from 'lodash'; import { Container, NavBar, Header, Break, TopBar, BoardForm, PostMenu, PostMenuMobile } from '../styled/views/Board.styled'; import { Footer } from '../styled/views/Thread.styled'; @@ -45,2761 +45,3255 @@ import useSuccess from '../../hooks/useSuccess'; import useAnonModeStore from '../../hooks/stores/useAnonModeStore'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; import packageJson from '../../../package.json'; -const {version} = packageJson; +const { version } = packageJson; let lastVirtuosoStates = {}; const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : ''; - const All = () => { - 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['all']; - - useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode); - - const addresses = defaultSubplebbits.map(subplebbit => subplebbit.address); - const { feed, loadMore } = useFeed({subplebbitAddresses: addresses, sortType: 'active'}); - const {subplebbits} = useSubplebbits({subplebbitAddresses: addresses, sortType: 'active'}); - const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); - - const stateString = useFeedStateString(subplebbits); - - - useEffect(() => { - if (feed) { - setFeedCacheState('all', 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')); - }; - }); - }; - - - useEffect(() => { - if (anonymousMode) { - setExecuteAnonMode(true); - } - }, [anonymousMode, selectedThreadCidRef]); - - - const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({ - commentCid: commentCid, - content: editedComment || undefined, - subplebbitAddress: selectedAddress, - onChallenge, - onChallengeVerification, - onError: (error) => { - setNewErrorMessage(error.message); console.log(error); - }, - }); - - - 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['all'] = snapshot; - } - }); - }; - window.addEventListener('scroll', setLastVirtuosoState); - - return () => window.removeEventListener('scroll', setLastVirtuosoState); - }, []); - - const lastVirtuosoState = lastVirtuosoStates['all']; - - - return ( - <> - - p/All - 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 -
-
-
-
 
-
 
- -
-
- <> -
- -
- <> -
p/All
-
Default boards, currently curated by devs.
- - -
- - -
- - Style: -   - - -
- [ - Catalog - ] -
- {subplebbits.state === "succeeded" ? ( - null - ) : ( -
- - {stateString} - - -
- )} -
- - Catalog - -
-
- - -
- {feed ? ( - { - 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 » -
      -
    • handleOptionClick(thread.cid)}> - Google -
    • -
    • handleOptionClick(thread.cid)}> - Yandex -
    • -
    • handleOptionClick(thread.cid)}> - SauceNAO -
    • -
    -
  • - ) : null - } -
  • {setIsClientRedirectMenuOpen(true)}} - onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}> - View on » -
      -
    • handleOptionClick(thread.cid)}> - Plebbit -
    • - {/*
    • handleOptionClick(thread.cid)}> + 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['all']; + + useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode); + + const addresses = defaultSubplebbits.map((subplebbit) => subplebbit.address); + const { feed, loadMore } = useFeed({ subplebbitAddresses: addresses, sortType: 'active' }); + const { subplebbits } = useSubplebbits({ subplebbitAddresses: addresses, sortType: 'active' }); + const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); + + const stateString = useFeedStateString(subplebbits); + + useEffect(() => { + if (feed) { + setFeedCacheState('all', 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')); + }; + }); + }; + + useEffect(() => { + if (anonymousMode) { + setExecuteAnonMode(true); + } + }, [anonymousMode, selectedThreadCidRef]); + + const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({ + commentCid: commentCid, + content: editedComment || undefined, + subplebbitAddress: selectedAddress, + onChallenge, + onChallengeVerification, + onError: (error) => { + setNewErrorMessage(error.message); + console.log(error); + }, + }); + + 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['all'] = snapshot; + } + }); + }; + window.addEventListener('scroll', setLastVirtuosoState); + + return () => window.removeEventListener('scroll', setLastVirtuosoState); + }, []); + + const lastVirtuosoState = lastVirtuosoStates['all']; + + return ( + <> + + p/All - 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 + +
      +
      +
      +
       
      +
       
      + +
      +
      + <> +
      + +
      + <> +
      p/All
      +
      Default boards, currently curated by devs.
      + + +
      + + +
      + + Style:   + + +
      + [Catalog] +
      + {subplebbits.state === 'succeeded' ? null : ( +
      + {stateString} + +
      + )} +
      + + Catalog + +
      +
      + + +
      + {feed ? ( + { + 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 - )} -
    • 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 » -
      -
    • handleOptionClick(reply.cid)}> - Google -
    • -
    • handleOptionClick(reply.cid)}> - Yandex -
    • -
    • handleOptionClick(reply.cid)}> - SauceNAO -
    • -
    -
  • - ) : null - } -
  • {setIsClientRedirectMenuOpen(true)}} - onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}> - View on » -
      -
    • handleOptionClick(reply.cid)}> - Plebbit -
    • - {/*
    • handleOptionClick(reply.cid)}> + 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/{reply.shortCid} + +   +
+ ))} +
+ + {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 (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 (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/{reply.shortCid} + +   +
+ ))} +
+
+ {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); + 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; + } - 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 (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); - 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; + 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 - }); - } - }); - }} - 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 (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 (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); - }} - 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)} -
-
- - - - Style: -   - - - - <> - - [ - 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 - ] - [ - handleStyleChange({target: {value: "Yotsuba"}} - )}>Home - ] - - - -
- plebchan v{version}{commitRef}. GPL-2.0 -
-
- About -  •  - App -  •  - Twitter -  •  - Telegram -
-
-
- - ); -} + 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; + } -export default All; \ No newline at end of file + 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, + )} + +
+ + + + Style:   + + + + <> + + [ + 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 + + ] [ + handleStyleChange({ target: { value: 'Yotsuba' } })}> + Home + + ] + + + +
+ plebchan v{version} + {commitRef}. GPL-2.0 +
+
+ + About + +  •  + + App + +  •  + + Twitter + +  •  + + Telegram + +
+
+ + + ); +}; + +export default All; diff --git a/src/components/views/AllCatalog.jsx b/src/components/views/AllCatalog.jsx index 5bc17307..13798222 100755 --- a/src/components/views/AllCatalog.jsx +++ b/src/components/views/AllCatalog.jsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Helmet } from 'react-helmet-async'; -import { Link, useNavigate} from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { confirmAlert } from 'react-confirm-alert'; import { Tooltip } from 'react-tooltip'; import { Virtuoso } from 'react-virtuoso'; @@ -31,973 +31,1063 @@ import useSuccess from '../../hooks/useSuccess'; import useWindowWidth from '../../hooks/useWindowWidth'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; import packageJson from '../../../package.json'; -const {version} = packageJson; +const { version } = packageJson; let lastVirtuosoStates = {}; const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : ''; +const CatalogPost = ({ post }) => { + const { + editedComment, + setDeletePost, + setIsAuthorDelete, + captchaResponse, + setCaptchaResponse, + setIsModerationOpen, + setChallengesArray, + setIsAuthorEdit, + setIsCaptchaOpen, + setIsEditModalOpen, + setModeratingCommentCid, + setOriginalCommentContent, + setPendingComment, + setResolveCaptchaPromise, + selectedAddress, + setSelectedAddress, + selectedStyle, + setSelectedThread, + } = useGeneralStore((state) => state); -const CatalogPost = ({post}) => { - const { - editedComment, - setDeletePost, - setIsAuthorDelete, - captchaResponse, setCaptchaResponse, - setIsModerationOpen, - setChallengesArray, - setIsAuthorEdit, - setIsCaptchaOpen, - setIsEditModalOpen, - setModeratingCommentCid, - setOriginalCommentContent, - setPendingComment, - setResolveCaptchaPromise, - selectedAddress, setSelectedAddress, - selectedStyle, - setSelectedThread, - } = useGeneralStore(state => state); + const thread = post; + const commentMediaInfo = getCommentMediaInfo(thread); + const linkCount = countLinks(thread); + const fallbackImgUrl = 'assets/filedeleted-res.gif'; + const [isHoveringOnThread, setIsHoveringOnThread] = useState(false); + const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 }); + const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0 }); + const [openMenuCid, setOpenMenuCid] = useState(null); + const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false); + const [isImageSearchOpen, setIsImageSearchOpen] = useState(false); + const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false); + const [commentCid, setCommentCid] = useState(null); + const [isModerator, setIsModerator] = useState(false); - const thread = post; - const commentMediaInfo = getCommentMediaInfo(thread); - const linkCount = countLinks(thread); - const fallbackImgUrl = "assets/filedeleted-res.gif"; - const [isHoveringOnThread, setIsHoveringOnThread] = useState(false); - const [menuPosition, setMenuPosition] = useState({top: 0, left: 0}); - const [popupPosition, setPopupPosition] = useState({top: 0, left: 0}); - const [openMenuCid, setOpenMenuCid] = useState(null); - const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false); - const [isImageSearchOpen, setIsImageSearchOpen] = useState(false); - const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false); - const [commentCid, setCommentCid] = useState(null); - const [isModerator, setIsModerator] = useState(false); + const [, setNewErrorMessage] = useError(); + const [, setNewSuccessMessage] = useSuccess(); - const [, setNewErrorMessage] = useError(); - const [, setNewSuccessMessage] = useSuccess(); + const account = useAccount(); + const subplebbit = useSubplebbit({ subplebbitAddress: thread.subplebbitAddress }); + const threadMenuRefs = useRef({}); + const threadRefs = useRef({}); + const postMenuRef = useRef(null); + const postMenuCatalogRef = useRef(null); + const popupRef = useRef(null); + const textRef = useRef(null); + const imageRef = useRef(null); + const [spaceOnRight, setSpaceOnRight] = useState(null); + const [isCalculationDone, setIsCalculationDone] = useState(false); + const [hoverTimeoutId, setHoverTimeoutId] = useState(null); + const [isHoveringForMenu, setIsHoveringForMenu] = useState(false); + const textRect = textRef.current?.getBoundingClientRect(); + const imageRect = imageRef.current?.getBoundingClientRect(); + const popupRect = popupRef.current?.getBoundingClientRect(); + const isMediaShowed = + thread.link && + commentMediaInfo && + (commentMediaInfo.type === 'image' || + commentMediaInfo.type === 'video' || + (commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail) || + (commentMediaInfo.type === 'iframe' && commentMediaInfo.thumbnail)) + ? true + : false; - const account = useAccount(); - const subplebbit = useSubplebbit({subplebbitAddress: thread.subplebbitAddress}); - const threadMenuRefs = useRef({}); - const threadRefs = useRef({}); - const postMenuRef = useRef(null); - const postMenuCatalogRef = useRef(null); - const popupRef = useRef(null); - const textRef = useRef(null); - const imageRef = useRef(null); - const [spaceOnRight, setSpaceOnRight] = useState(null); - const [isCalculationDone,setIsCalculationDone] = useState(false); - const [hoverTimeoutId, setHoverTimeoutId] = useState(null); - const [isHoveringForMenu, setIsHoveringForMenu] = useState(false); - const textRect = textRef.current?.getBoundingClientRect(); - const imageRect = imageRef.current?.getBoundingClientRect(); - const popupRect = popupRef.current?.getBoundingClientRect(); - const isMediaShowed = (thread.link && commentMediaInfo && ( - commentMediaInfo.type === 'image' || - commentMediaInfo.type === 'video' || - (commentMediaInfo.type === 'webpage' && - commentMediaInfo.thumbnail) || - (commentMediaInfo.type === 'iframe' && - commentMediaInfo.thumbnail))) ? true : false; + const { left: textLeft, right: textRight, width: textWidth } = textRef.current?.getBoundingClientRect() || {}; + const { left: imageLeft, right: imageRight, width: imageWidth } = imageRef.current?.getBoundingClientRect() || {}; + useLayoutEffect(() => { + const executeLayoutEffectLogic = () => { + let ref; - const { left: textLeft, right: textRight, width: textWidth } = textRef.current?.getBoundingClientRect() || {}; - const { left: imageLeft, right: imageRight, width: imageWidth } = imageRef.current?.getBoundingClientRect() || {}; - - useLayoutEffect(() => { - const executeLayoutEffectLogic = () => { - let ref; - - if (isMediaShowed) { - ref = imageRef.current; - } else { - ref = textRef.current; - } - - if (ref && popupRef.current) { - const rect = ref.getBoundingClientRect(); - const viewportWidth = document.documentElement.clientWidth; - const spaceRight = viewportWidth - (rect.left + rect.width); - const spaceLeft = rect.left; - - if (spaceRight < 200 && spaceLeft > spaceRight && spaceLeft < 500) { - popupRef.current.style.maxWidth = `calc( + if (isMediaShowed) { + ref = imageRef.current; + } else { + ref = textRef.current; + } + + if (ref && popupRef.current) { + const rect = ref.getBoundingClientRect(); + const viewportWidth = document.documentElement.clientWidth; + const spaceRight = viewportWidth - (rect.left + rect.width); + const spaceLeft = rect.left; + + if (spaceRight < 200 && spaceLeft > spaceRight && spaceLeft < 500) { + popupRef.current.style.maxWidth = `calc( 100vw - ${isMediaShowed ? imageWidth : textWidth}px - ${spaceRight + 40}px )`; - } else if (spaceRight < 200 && spaceLeft < spaceRight) { - popupRef.current.style.maxWidth = `calc(100vw - ${isMediaShowed ? imageWidth : textWidth}px)`; - } else { - popupRef.current.style.maxWidth = `500px`; - } - - setSpaceOnRight(spaceRight); - setIsCalculationDone(true); - } - }; - - if (isHoveringOnThread) { - const timeoutId = setTimeout(executeLayoutEffectLogic, 250); - return () => { - clearTimeout(timeoutId); - setIsCalculationDone(false); - }; - } - - }, [isHoveringOnThread, isMediaShowed, textLeft, textRight, imageLeft, imageRight, textWidth, imageWidth]); - - - const handleMouseOnLeaveThread = () => { - if (hoverTimeoutId) { - clearTimeout(hoverTimeoutId); - setHoverTimeoutId(null); - setIsHoveringOnThread("") - } else if (isHoveringOnThread !== "") { - setIsHoveringOnThread(""); - setIsCalculationDone(false); - } - }; + } else if (spaceRight < 200 && spaceLeft < spaceRight) { + popupRef.current.style.maxWidth = `calc(100vw - ${isMediaShowed ? imageWidth : textWidth}px)`; + } else { + popupRef.current.style.maxWidth = `500px`; + } + setSpaceOnRight(spaceRight); + setIsCalculationDone(true); + } + }; - useEffect(() => { - if (subplebbit.roles !== undefined) { - const role = subplebbit.roles[account?.author.address]?.role; - - if (role === 'moderator' || role === 'admin' || role === 'owner') { - setIsModerator(true); - } else { - setIsModerator(false); - } - } - }, [account?.author.address, subplebbit.roles]); + if (isHoveringOnThread) { + const timeoutId = setTimeout(executeLayoutEffectLogic, 250); + return () => { + clearTimeout(timeoutId); + setIsCalculationDone(false); + }; + } + }, [isHoveringOnThread, isMediaShowed, textLeft, textRight, imageLeft, imageRight, textWidth, imageWidth]); + const handleMouseOnLeaveThread = () => { + if (hoverTimeoutId) { + clearTimeout(hoverTimeoutId); + setHoverTimeoutId(null); + setIsHoveringOnThread(''); + } else if (isHoveringOnThread !== '') { + setIsHoveringOnThread(''); + setIsCalculationDone(false); + } + }; - const handleOutsideClick = useCallback((e) => { - if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) { - setOpenMenuCid(null); - } - }, [openMenuCid]); + useEffect(() => { + if (subplebbit.roles !== undefined) { + const role = subplebbit.roles[account?.author.address]?.role; - const handleOptionClick = () => { - setOpenMenuCid(null); - }; + if (role === 'moderator' || role === 'admin' || role === 'owner') { + setIsModerator(true); + } else { + setIsModerator(false); + } + } + }, [account?.author.address, subplebbit.roles]); - useEffect(() => { - if (openMenuCid !== null) { - document.addEventListener('click', handleOutsideClick); - } else { - document.removeEventListener('click', handleOutsideClick); - } - - return () => { - document.removeEventListener('click', handleOutsideClick); - }; - }, [openMenuCid, handleOutsideClick]); + const handleOutsideClick = useCallback( + (e) => { + if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) { + setOpenMenuCid(null); + } + }, + [openMenuCid], + ); - const handleAuthorDeleteClick = (comment) => { - handleOptionClick(comment.cid); + const handleOptionClick = () => { + setOpenMenuCid(null); + }; - confirmAlert({ - customUI: ({ onClose }) => { - return ( - -
-

Are you sure you want to delete this post?

-
- - -
-
-
- ); - } - }); - }; + useEffect(() => { + if (openMenuCid !== null) { + document.addEventListener('click', handleOutsideClick); + } else { + document.removeEventListener('click', handleOutsideClick); + } + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, [openMenuCid, handleOutsideClick]); - const handleAuthorEditClick = (comment) => { - handleOptionClick(comment.cid); - setIsAuthorEdit(true); - setIsAuthorDelete(false); - setCommentCid(comment.cid); - setOriginalCommentContent(comment.content); - setIsEditModalOpen(true); - } + 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); + }; - 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 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) => { + setPendingComment(comment); + let challengeAnswers = []; + try { + challengeAnswers = await getChallengeAnswersFromUser(challenges); + } catch (error) { + setNewErrorMessage(error.message); + console.log(error); + } + if (challengeAnswers) { + await comment.publishChallengeAnswers(challengeAnswers); + } + }; - const onChallenge = async (challenges, comment) => { - setPendingComment(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; - 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 = captchaResponse; - resolve(currentCaptchaResponse); - setIsCaptchaOpen(false); - document.removeEventListener('keydown', handleKeyDown); - event.preventDefault(); - } - }; + challengeImg.onload = () => { + setIsCaptchaOpen(true); - setCaptchaResponse(''); - document.addEventListener('keydown', handleKeyDown); + const handleKeyDown = async (event) => { + if (event.key === 'Enter') { + const currentCaptchaResponse = captchaResponse; + resolve(currentCaptchaResponse); + setIsCaptchaOpen(false); + document.removeEventListener('keydown', handleKeyDown); + event.preventDefault(); + } + }; - setResolveCaptchaPromise(resolve); - }; - - challengeImg.onerror = () => { - reject(setNewErrorMessage('Could not load challenges')); - }; - }); - }; + setCaptchaResponse(''); + document.addEventListener('keydown', handleKeyDown); - const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({ - commentCid: commentCid, - content: editedComment || undefined, - subplebbitAddress: selectedAddress, - onChallenge, - onChallengeVerification, - onError: (error) => { - setNewErrorMessage(error.message); console.log(error); - }, - }); - - - const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); + setResolveCaptchaPromise(resolve); + }; + challengeImg.onerror = () => { + reject(setNewErrorMessage('Could not load challenges')); + }; + }); + }; - useEffect(() => { - setPublishCommentEditOptions((prevOptions) => ({ - ...prevOptions, - commentCid: commentCid, - content: editedComment || undefined, - })); - }, [commentCid, editedComment]); + const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({ + commentCid: commentCid, + content: editedComment || undefined, + subplebbitAddress: selectedAddress, + onChallenge, + onChallengeVerification, + onError: (error) => { + setNewErrorMessage(error.message); + console.log(error); + }, + }); + const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); - useEffect(() => { - if (editedComment !== '') { - setTriggerPublishCommentEdit(true); - } - }, [editedComment, setIsAuthorEdit]); + useEffect(() => { + setPublishCommentEditOptions((prevOptions) => ({ + ...prevOptions, + commentCid: commentCid, + content: editedComment || undefined, + })); + }, [commentCid, editedComment]); - - useEffect(() => { - let isActive = true; - if (publishCommentEditOptions && triggerPublishCommentEdit) { - (async () => { - await publishCommentEdit(); - if (isActive) { - setTriggerPublishCommentEdit(false); - } - })(); - } + useEffect(() => { + if (editedComment !== '') { + setTriggerPublishCommentEdit(true); + } + }, [editedComment, setIsAuthorEdit]); - return () => { - isActive = false; - }; - }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]); + useEffect(() => { + let isActive = true; + if (publishCommentEditOptions && triggerPublishCommentEdit) { + (async () => { + await publishCommentEdit(); + if (isActive) { + setTriggerPublishCommentEdit(false); + } + })(); + } + return () => { + isActive = false; + }; + }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]); - let displayWidth, displayHeight; - if (thread.linkWidth && thread.linkHeight) { - let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight)); - displayWidth = `${thread.linkWidth * scale}px`; - displayHeight = `${thread.linkHeight * scale}px`; - } else if (thread.link) { - displayWidth = '150px'; - displayHeight = '150px'; - } else { - displayWidth = '0px'; - displayHeight = '0px'; - } + let displayWidth, displayHeight; + if (thread.linkWidth && thread.linkHeight) { + let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight)); + displayWidth = `${thread.linkWidth * scale}px`; + displayHeight = `${thread.linkHeight * scale}px`; + } else if (thread.link) { + displayWidth = '150px'; + displayHeight = '150px'; + } else { + displayWidth = '0px'; + displayHeight = '0px'; + } - - return ( - <> - {isHoveringOnThread === thread.cid ? - createPortal( - -
handleMouseOnLeaveThread()} - style={{ - visibility: isCalculationDone ? 'visible' : 'hidden', - left: - isCalculationDone && spaceOnRight > 250 - ? isMediaShowed ? imageRect.right + 5 : textRect.right + 5 : - isCalculationDone && spaceOnRight < 250 - ? isMediaShowed ? imageRect.left - popupRect.width - 5 : textRect.left - popupRect.width - 5 - : 'auto', - top: popupPosition.top + 5, - }}> - - {thread.title ? `${thread.title} ` : "Posted "} - - by - - {thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "} - - - {thread.timestamp ? getFormattedTime(thread.timestamp) : null} - - {thread.replyCount > 0 ? -
- Last reply by - Anonymous - {getFormattedTime(thread.lastReplyTimestamp)} -
- : null} -
-
, document.body - ) - : null } -
{ - threadRefs.current[thread.cid] = el; - }} - onMouseLeave={() => {handleMouseOnLeaveThread()}}> - {commentMediaInfo?.url ? ( - setSelectedThread(thread.cid)} - onMouseOver={() => { - setIsHoveringOnThread(thread.cid); - setIsHoveringForMenu(thread.cid); - const rect = threadRefs.current[thread.cid].getBoundingClientRect(); - setPopupPosition({top: rect.top + window.scrollY, left: rect.left}); - }} - onMouseLeave={() => setIsHoveringForMenu(false)}> - {commentMediaInfo?.type === "webpage" ? ( - thread.thumbnailUrl ? ( - - {commentMediaInfo.type} { - e.target.src = fallbackImgUrl - e.target.onerror = null; - }} /> - - ) : null - ) : null} - {commentMediaInfo?.type === "iframe" && (thread.thumbnailUrl || commentMediaInfo.thumbnail) ? ( - - {commentMediaInfo.type} { e.target.src = fallbackImgUrl }} /> - - ) : null} - {commentMediaInfo?.type === "image" ? ( - - {commentMediaInfo.type} { - e.target.src = fallbackImgUrl - e.target.onerror = null;}} /> - - ) : null} - {commentMediaInfo?.type === "video" ? ( - - - ) : null} - {commentMediaInfo?.type === "audio" ? ( -
+ + ); }; - -const CatalogRow = ({row}) => { - const posts = [] - for (const post of row) { - posts.push() - } - return
{posts}
-} - +const CatalogRow = ({ row }) => { + const posts = []; + for (const post of row) { + posts.push(); + } + return
{posts}
; +}; const AllCatalog = () => { - const { - defaultSubplebbits, - isModerationOpen, setIsModerationOpen, - isSettingsOpen, setIsSettingsOpen, - originalCommentContent, - selectedAddress, setSelectedAddress, - selectedStyle, - setSelectedTitle, - } = useGeneralStore(state => state); + const { + defaultSubplebbits, + isModerationOpen, + setIsModerationOpen, + isSettingsOpen, + setIsSettingsOpen, + originalCommentContent, + selectedAddress, + setSelectedAddress, + selectedStyle, + setSelectedTitle, + } = useGeneralStore((state) => state); - const virtuosoRef = useRef(); + const virtuosoRef = useRef(); - const navigate = useNavigate(); + const navigate = useNavigate(); - const [prevScrollPos, setPrevScrollPos] = useState(0); - const [visible, setVisible] = useState(true); + const [prevScrollPos, setPrevScrollPos] = useState(0); + const [visible, setVisible] = useState(true); - const [isEditModalOpen, setIsEditModalOpen] = useState(false); - const [deletePost, setDeletePost] = useState(false); - const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [deletePost, setDeletePost] = useState(false); + const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false); - const addresses = defaultSubplebbits.map(subplebbit => subplebbit.address); - const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: addresses, sortType: 'active'}); - const {subplebbits} = useSubplebbits({subplebbitAddresses: addresses, sortType: 'active'}); - const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); + const addresses = defaultSubplebbits.map((subplebbit) => subplebbit.address); + const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses: addresses, sortType: 'active' }); + const { subplebbits } = useSubplebbits({ subplebbitAddresses: addresses, sortType: 'active' }); + const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); - const stateString = useFeedStateString(subplebbits); + const stateString = useFeedStateString(subplebbits); - const columnWidth = 180; - const windowWidth = useWindowWidth(); - const columnCount = Math.floor(windowWidth / columnWidth); - const rows = useFeedRows(selectedFeed, columnCount); + const columnWidth = 180; + const windowWidth = useWindowWidth(); + const columnCount = Math.floor(windowWidth / columnWidth); + const rows = useFeedRows(selectedFeed, columnCount); - // 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]); + // mobile navbar scroll effect + useEffect(() => { + const debouncedHandleScroll = debounce(() => { + const currentScrollPos = window.pageYOffset; + setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10); + setPrevScrollPos(currentScrollPos); + }, 50); - // mobile navbar board select functionality - const handleSelectChange = (event) => { - const selected = event.target.value; + window.addEventListener('scroll', debouncedHandleScroll); - if (selected === 'subscriptions') { - navigate(`/p/subscriptions`); - return; - } else if (selected === 'all') { - navigate(`/p/all`); - return; - } + return () => window.removeEventListener('scroll', debouncedHandleScroll); + }, [prevScrollPos, visible]); - const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title; - setSelectedTitle(selectedTitle); - setSelectedAddress(selected); - navigate(`/p/${selected}`); - }; + // mobile navbar board select functionality + const handleSelectChange = (event) => { + const selected = event.target.value; - // desktop navbar board select functionality - const handleClickTitle = (title, address) => { - setSelectedTitle(title); - setSelectedAddress(address); - setSelectedFeed(feed.filter(feed => feed.title === title)); - }; + 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["allCatalog"] = snapshot; - } - }); - }; - window.addEventListener('scroll', setLastVirtuosoState); + // desktop navbar board select functionality + const handleClickTitle = (title, address) => { + setSelectedTitle(title); + setSelectedAddress(address); + setSelectedFeed(feed.filter((feed) => feed.title === title)); + }; - return () => window.removeEventListener('scroll', setLastVirtuosoState); - }, [selectedAddress]); - + useEffect(() => { + const setLastVirtuosoState = () => { + virtuosoRef.current?.getState((snapshot) => { + if (snapshot?.scrollTop === 0 || snapshot?.ranges?.length) { + lastVirtuosoStates['allCatalog'] = snapshot; + } + }); + }; + window.addEventListener('scroll', setLastVirtuosoState); - const lastVirtuosoState = lastVirtuosoStates["allCatalog"]; + return () => window.removeEventListener('scroll', setLastVirtuosoState); + }, [selectedAddress]); + const lastVirtuosoState = lastVirtuosoStates['allCatalog']; - return ( - <> - - p/All - Catalog - plebchan - - - setIsCreateBoardOpen(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"} - { - setSelectedTitle(subplebbit.title); - setSelectedAddress(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 -
-
-
-
 
-
 
- -
-
- <> -
- -
-
p/All
-
Default boards, currently curated by devs.
- -
- - -
- - Style: -   - - -
- [ - Return - ] -
-
- - Return - -
- {subplebbits.state === "succeeded" ? ( - null - ) : ( -
- - {stateString} - - -
- )} -
-
- - - {feed.length > 1 ? ( - } - useWindowScroll={true} - components={{ Footer: () => hasMore ? : null}} - endReached={loadMore} - ref={virtuosoRef} - restoreStateFrom={lastVirtuosoState} - initialScrollTop={lastVirtuosoState?.scrollTop} - /> - ) : ()} - -
- - - - Style: -   - - - - <> - - [ - 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 - ] - [ - handleStyleChange({target: {value: "Yotsuba"}} - )}>Home - ] - - - -
- plebchan v{version}{commitRef}. GPL-2.0 -
-
- About -  •  - App -  •  - Twitter -  •  - Telegram -
-
-
- - ); -} + return ( + <> + + p/All - Catalog - plebchan + + + setIsCreateBoardOpen(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'} + { + setSelectedTitle(subplebbit.title); + setSelectedAddress(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 + +
+
+
+
 
+
 
+ +
+
+ <> +
+ +
+
p/All
+
Default boards, currently curated by devs.
+ +
+ + +
+ + Style:   + + +
+ [Return] +
+
+ + Return + +
+ {subplebbits.state === 'succeeded' ? null : ( +
+ {stateString} + +
+ )} +
+
+ + + {feed.length > 1 ? ( + } + useWindowScroll={true} + components={{ Footer: () => (hasMore ? : null) }} + endReached={loadMore} + ref={virtuosoRef} + restoreStateFrom={lastVirtuosoState} + initialScrollTop={lastVirtuosoState?.scrollTop} + /> + ) : ( + + )} + +
+ + + + Style:   + + + + <> + + [ + 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 + + ] [ + handleStyleChange({ target: { value: 'Yotsuba' } })}> + Home + + ] + + + +
+ plebchan v{version} + {commitRef}. GPL-2.0 +
+
+ + About + +  •  + + App + +  •  + + Twitter + +  •  + + Telegram + +
+
+
+ + ); +}; -export default AllCatalog; \ No newline at end of file +export default AllCatalog; diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index c6cde796..5cadb945 100755 --- a/src/components/views/Board.jsx +++ b/src/components/views/Board.jsx @@ -6,7 +6,7 @@ import { confirmAlert } from 'react-confirm-alert'; import { Tooltip } from 'react-tooltip'; import { Virtuoso } from 'react-virtuoso'; import { useAccount, useAccountComments, useFeed, usePublishComment, usePublishCommentEdit, useSubplebbit, useSubscribe } from '@plebbit/plebbit-react-hooks'; -import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils' +import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils'; import { debounce } from 'lodash'; import { Container, NavBar, Header, Break, PostFormLink, PostFormTable, PostForm, TopBar, BoardForm, PostMenu, PostMenuMobile } from '../styled/views/Board.styled'; import { Footer } from '../styled/views/Thread.styled'; @@ -49,3675 +49,4303 @@ import useSuccess from '../../hooks/useSuccess'; import useAnonModeStore from '../../hooks/stores/useAnonModeStore'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; import packageJson from '../../../package.json'; -const {version} = packageJson; +const { version } = packageJson; let lastVirtuosoStates = {}; const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : ''; - const Board = () => { - const { - setCaptchaResponse, - setChallengesArray, - defaultSubplebbits, - editedComment, - editedComments, - feedCacheStates, - setFeedCacheState, - setIsAuthorDelete, - setIsAuthorEdit, - setIsCaptchaOpen, - isModerationOpen, setIsModerationOpen, - isSettingsOpen, setIsSettingsOpen, - setModeratingCommentCid, - setPendingComment, - setPendingCommentIndex, - setReplyQuoteCid, - setResolveCaptchaPromise, - selectedAddress, setSelectedAddress, - setSelectedParentCid, - setSelectedShortCid, - selectedStyle, - setSelectedThread, - setSelectedText, - setSelectedTitle, - showPostForm, - showPostFormLink, - setTriggerInsertion, - } = useGeneralStore(state => state); - - const { anonymousMode } = useAnonModeStore(); - - // temporary hardcode - const plebtokenRules = [ - "This community is strictly SFW. Any NSFW language or content will be removed.", - Only post about the plebbit token ($PLEB). For general cryptocurrency discussion, go to p/business-and-finance.eth., - "FUD is allowed unless blatantly meaningless and spammy." - ] - - const account = useAccount(); - const navigate = useNavigate(); - const { subplebbitAddress } = useParams(); - - const [, setNewErrorMessage] = useError(); - const [, setNewSuccessMessage] = useSuccess(); - - const nameRef = useRef(); - const subjectRef = useRef(); - const commentRef = useRef(); - const linkRef = useRef(); - 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 { feed, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'active'}); - const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress}); - const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress}); - const stateString = useStateString(subplebbit); - - const [isAdminListOpen, setIsAdminListOpen] = useState(false); - 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 [triggerPublishComment, setTriggerPublishComment] = useState(false); - const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false); - const [selectedFeed, setSelectedFeed] = useState(feed); - const [deletePost, setDeletePost] = useState(false); - const [isImageSearchOpen, setIsImageSearchOpen] = useState(false); - const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false); - const [isModerator, setIsModerator] = useState(false); - const [commentCid, setCommentCid] = useState(null); - const [menuPosition, setMenuPosition] = useState({top: 0, left: 0}); - const [mobileMenuPosition, setMobileMenuPosition] = useState({top: 0, left: 0}); - 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 [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[selectedAddress]; - - useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode); - - - useEffect(() => { - if (feed) { - setFeedCacheState(selectedAddress, true); - } - }, [selectedAddress, setFeedCacheState, feed]); - - - useEffect(() => { - if (subplebbit?.roles !== undefined) { - const role = subplebbit?.roles[account?.author.address]?.role; - - if (role === 'moderator' || role === 'admin' || role === 'owner') { - setIsModerator(true); - } else { - setIsModerator(false); - } - } - }, [account?.author.address, subplebbit?.roles]); - - - 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; - } - }; - - - 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(() => { - if (subplebbit?.state === 'failed') { - let errorString = 'Failed fetching board "' + selectedAddress + '".'; - if (subplebbit.error) { - errorString += `: ${subplebbit.error.toString().slice(0, 300)}` - } - return errorString - } - }, [subplebbit?.state, subplebbit?.error, selectedAddress]) - - - useEffect(() => { - if (errorString) { - setNewErrorMessage(errorString); - } - }, [errorString, setNewErrorMessage]); - - - 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]); - - - useEffect(() => { - const selectedSubplebbit = defaultSubplebbits.find((subplebbit) => subplebbit.address === subplebbitAddress); - if (subplebbitAddress) { - setSelectedAddress(subplebbitAddress); - } else if (subplebbit?.address) { - setSelectedAddress(subplebbit.address) - } - if (selectedSubplebbit) { - setSelectedTitle(selectedSubplebbit.title); - } else if (subplebbit?.title) { - setSelectedTitle(subplebbit.title); - } - }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits, subplebbit?.address, subplebbit?.title]); - - // sets useFeed to address from URL - useEffect(() => { - setSelectedFeed(feed); - }, [feed]); - - // mobile navbar scroll effect - useEffect(() => { - const debouncedHandleScroll = debounce(() => { - const currentScrollPos = window.scrollY; - 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) { - if (challengeVerification.publication?.cid !== undefined) { - navigate(`/p/${subplebbitAddress}/c/${challengeVerification.publication?.cid}`); - console.log('challenge success'); - } else { - 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) => { - setPendingComment(comment); - let challengeAnswers = []; - - try { - challengeAnswers = await getChallengeAnswersFromUser(challenges) - } - catch (error) { - setNewErrorMessage(error.message); console.log(error); - } - if (challengeAnswers) { - await comment.publishChallengeAnswers(challengeAnswers) - } - }; - - - useEffect(() => { - setPublishCommentOptions((prevPublishCommentOptions) => ({ - ...prevPublishCommentOptions, - subplebbitAddress: selectedAddress, - })); - }, [selectedAddress]); - - - const [publishCommentOptions, setPublishCommentOptions] = useState({ - subplebbitAddress: selectedAddress, - onChallenge, - onChallengeVerification, - onError: (error) => { - setNewErrorMessage(error.message); console.log(error); - }, - }); - - - const { publishComment, index } = usePublishComment(publishCommentOptions); - - useEffect(() => { - if (index !== undefined) { - setPendingCommentIndex(index); - navigate(`/profile/c/${index}`); - } - }, [index, navigate, setPendingCommentIndex]); - - - const resetFields = useCallback(() => { - if (nameRef.current) { - nameRef.current.value = ''; - } - if (subjectRef.current) { - subjectRef.current.value = ''; - } - if (commentRef.current) { - commentRef.current.value = ''; - } - if (linkRef.current) { - linkRef.current.value = ''; - } - }, []); - - - const handleSubmit = async (event) => { - event.preventDefault(); - - if (subjectRef.current.value === "") { - setNewErrorMessage('Subject field is mandatory'); - return; - } - - setPublishCommentOptions((prevPublishCommentOptions) => ({ - ...prevPublishCommentOptions, - author: { - displayName: nameRef.current.value || undefined, - ...(anonymousMode ? {} : {address: account?.author.address}), - }, - title: subjectRef.current.value || undefined, - content: commentRef.current.value || undefined, - link: linkRef.current.value || undefined, - })); - - setTriggerPublishComment(true); - }; - - - const updateSigner = useCallback(async () => { - if (anonymousMode) { - setExecuteAnonMode(true); - - let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {}; - let signer; - - if (!storedSigners[selectedThreadCidRef]) { - signer = await account?.plebbit.createSigner(); - storedSigners[selectedThreadCidRef] = { privateKey: signer?.privateKey, address: signer?.address }; - localStorage.setItem('storedSigners', JSON.stringify(storedSigners)); - } else { - const signerPrivateKey = storedSigners[selectedThreadCidRef].privateKey; - - try { - signer = await account?.plebbit.createSigner({type: 'ed25519', privateKey: signerPrivateKey}); - } catch (error) { - console.log(error); - } - } - - setPublishCommentOptions(prevPublishCommentOptions => { - const newPublishCommentOptions = { - ...prevPublishCommentOptions, - signer, - author: { - ...prevPublishCommentOptions.author, - address: signer?.address, - }, - }; - - if (JSON.stringify(prevPublishCommentOptions) !== JSON.stringify(newPublishCommentOptions)) { - return newPublishCommentOptions; - } - - return prevPublishCommentOptions; - }); - } - }, [selectedThreadCidRef, anonymousMode, account]); - - useEffect(() => { - if (anonymousMode) { - updateSigner(); - } - }, [updateSigner, anonymousMode]); - - - useEffect(() => { - if (publishCommentOptions && triggerPublishComment) { - (async () => { - await publishComment(); - resetFields(); - })(); - setTriggerPublishComment(false); - setExecuteAnonMode(false); - } - }, [publishCommentOptions, triggerPublishComment, publishComment, resetFields]); - - - 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, - onChallenge, - onChallengeVerification, - onError: (error) => { - setNewErrorMessage(error.message); console.log(error); - }, - }); - - - 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)); - - if (subplebbitAddress === address) { - window.location.reload(); - } - }; - - // 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}`); - }; - - - const handleSubscribe = async () => { - try { - if (subscribed === false) { - await subscribe(selectedAddress); - } else if (subscribed === true) { - await unsubscribe(selectedAddress); - } - } catch (error) { - setNewErrorMessage(error.message); console.log(error); - } - }; - - - useEffect(() => { - const setLastVirtuosoState = () => { - virtuosoRef.current?.getState((snapshot) => { - if (snapshot?.scrollTop === 0 || snapshot?.ranges?.length) { - lastVirtuosoStates[selectedAddress] = snapshot; - } - }); - }; - window.addEventListener('scroll', setLastVirtuosoState); - - return () => window.removeEventListener('scroll', setLastVirtuosoState); - }, [selectedAddress]); - - - const lastVirtuosoState = lastVirtuosoStates[selectedAddress]; - - - return ( - <> - - {((subplebbit.title ? subplebbit.title : subplebbit.address) + " - plebchan")} - - - setIsAdminListOpen(false)} - roles={subplebbit?.roles} /> - setIsCreateBoardOpen(false)} /> - setIsEditModalOpen(false)} - originalCommentContent={originalCommentContent} /> - {setIsModerationOpen(false); setDeletePost(false)}} - deletePost={deletePost} /> - setIsReplyOpen(false)} /> - setIsSettingsOpen(false)} /> - - <> - - [ - 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 -   -   - { - 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 -   - {handleStyleChange({target: {value: "Yotsuba"}}); window.scrollTo(0, 0);}}>Home -
-
-
-
 
-
 
- -
-
- <> -
- -
-
{subplebbit.title ?? null}
-
p/{subplebbit.address} - -
- -
- - - -
- [ - Start a New Thread - ] -
-
- - Start a New Thread - -
-
- - - - Name - - {account && account?.author && account?.author.displayName ? ( - - ) : ( - - )} - - - - Subject - - - - - - - Comment - -