import React, { useState, useEffect, useRef, Fragment } from 'react'; import { Helmet } from 'react-helmet-async'; import InfiniteScroll from 'react-infinite-scroller'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { useFeed, usePublishComment } from '@plebbit/plebbit-react-hooks'; import { debounce } from 'lodash'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable } from '../styled/Board.styled'; import { Threads } from '../styled/Catalog.styled'; import { TopBar, Footer } from '../styled/Thread.styled'; import CatalogLoader from '../CatalogLoader'; import ImageBanner from '../ImageBanner'; import SettingsModal from '../SettingsModal'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import handleStyleChange from '../../utils/handleStyleChange'; import useClickForm from '../../hooks/useClickForm'; import useError from '../../hooks/useError'; const Catalog = () => { const { captchaResponse, setCaptchaResponse, setChallengesArray, defaultSubplebbits, setIsCaptchaOpen, isSettingsOpen, setIsSettingsOpen, setPendingComment, setPendingCommentIndex, setResolveCaptchaPromise, selectedAddress, setSelectedAddress, selectedStyle, setSelectedThread, selectedTitle, setSelectedTitle, showPostForm, showPostFormLink, } = useGeneralStore(state => state); const nameRef = useRef(); const subjectRef = useRef(); const commentRef = useRef(); const linkRef = useRef(); const navigate = useNavigate(); const [prevScrollPos, setPrevScrollPos] = useState(0); const [visible, setVisible] = useState(true); const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'new'}); const { subplebbitAddress } = useParams(); const [errorMessage, setErrorMessage] = useState(null); useError(errorMessage, [errorMessage]); // temporary title from JSON, gets subplebbitAddress from URL useEffect(() => { setSelectedAddress(subplebbitAddress); const selectedSubplebbit = defaultSubplebbits.find((subplebbit) => subplebbit.address === subplebbitAddress); if (selectedSubplebbit) { setSelectedTitle(selectedSubplebbit.title); } }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]); // 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 {loadMore()} catch (e) {await new Promise(resolve => setTimeout(resolve, 1000))} }; const onChallengeVerification = (challengeVerification) => { if (challengeVerification.challengeSuccess === true) { navigate(`/p/${selectedAddress}/c/${challengeVerification.publication?.cid}`); console.log('challenge success'); } else if (challengeVerification.challengeSuccess === false) { setErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors}); } }; const onChallenge = async (challenges, comment) => { setPendingComment(comment); let challengeAnswers = []; try { challengeAnswers = await getChallengeAnswersFromUser(challenges) } catch (error) { setErrorMessage(error); } if (challengeAnswers) { await comment.publishChallengeAnswers(challengeAnswers) } }; useEffect(() => { setPublishCommentOptions((prevPublishCommentOptions) => ({ ...prevPublishCommentOptions, subplebbitAddress: selectedAddress, })); }, [selectedAddress]); const [publishCommentOptions, setPublishCommentOptions] = useState({ subplebbitAddress: selectedAddress, onChallenge, onChallengeVerification, onError: (error) => { setErrorMessage(error); }, }); const { publishComment, index } = usePublishComment(publishCommentOptions); useEffect(() => { if (index !== undefined) { setPendingCommentIndex(index); navigate(`/profile/c/${index}`); } }, [index]); const resetFields = () => { nameRef.current.value = ''; subjectRef.current.value = ''; commentRef.current.value = ''; linkRef.current.value = ''; }; const handleSubmit = async (event) => { event.preventDefault(); setPublishCommentOptions((prevPublishCommentOptions) => ({ ...prevPublishCommentOptions, author: { displayName: nameRef.current.value || undefined, }, title: subjectRef.current.value || undefined, content: commentRef.current.value || undefined, link: linkRef.current.value || undefined, })); }; useEffect(() => { if (publishCommentOptions.content) { (async () => { await publishComment(); resetFields(); })(); } }, [publishCommentOptions]); 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(); } }; setCaptchaResponse(''); document.addEventListener('keydown', handleKeyDown); setResolveCaptchaPromise(resolve); }; challengeImg.onerror = () => { reject(setErrorMessage('Could not load challenges')); }; }); }; // mobile navbar board select functionality const handleSelectChange = (event) => { const selected = event.target.value; const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title; setSelectedTitle(selectedTitle); setSelectedAddress(selected); navigate(`/p/${selected}`); }; return ( <> {((selectedTitle ? selectedTitle : selectedAddress) + " - Catalog - plebchan")} setIsSettingsOpen(false)} /> <> {defaultSubplebbits.map(subplebbit => ( [ { setSelectedTitle(subplebbit.title); setSelectedAddress(subplebbit.address); }} >{subplebbit.title ? subplebbit.title : subplebbit.address} ] ))} [ setIsSettingsOpen(true)}>Settings ] [ handleStyleChange({target: {value: "Yotsuba"}} )}>Home ] Board {defaultSubplebbits.map(subplebbit => ( {subplebbit.title ? subplebbit.title : subplebbit.address} ))} setIsSettingsOpen(true)}>Settings handleStyleChange({target: {value: "Yotsuba"}} )}>Home > <> <> {selectedTitle} p/{selectedAddress} > > [ event.target.style.cursor='pointer'}>Start a New Thread ] event.target.style.cursor='pointer'}>Start a New Thread Name Subject Comment Embed File alert("- Embedding media is optional, posts can be text-only. \n- A CAPTCHA challenge will appear after posting. \n- The CAPTCHA is case-sensitive.") } data-tip="Help">? Style: Yotsuba Yotsuba B Futaba Burichan Tomorrow Photon [ Return ] Return {feed.length > 0 ? (null) : (Fetching IPFS...)} {feed.length < 1 ? ( ) : ( {feed.map((thread) => { const commentMediaInfo = getCommentMediaInfo(thread); const fallbackImgUrl = "/assets/filedeleted-res.gif"; return ( setSelectedThread(thread.cid)}> {commentMediaInfo?.url ? ( {commentMediaInfo?.type === "webpage" ? ( { e.target.src = fallbackImgUrl e.target.onerror = null;}} /> ) : null} {commentMediaInfo?.type === "image" ? ( { e.target.src = fallbackImgUrl e.target.onerror = null;}} /> ) : null} {commentMediaInfo?.type === "video" ? ( e.target.src = fallbackImgUrl} /> ) : null} {commentMediaInfo?.type === "audio" ? ( e.target.src = fallbackImgUrl} /> ) : null} ) : null} {/* */} R: {thread.replyCount} {thread.title ? `${thread.title}` : null} {thread.content ? `: ${thread.content}` : null} )})} )} > ); } export default Catalog;