import React, { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Helmet } from 'react-helmet-async'; import { Link, useNavigate} from 'react-router-dom'; import { confirmAlert } from 'react-confirm-alert'; import { Tooltip } from 'react-tooltip'; import { Virtuoso } from 'react-virtuoso'; import { useAccount, useFeed, usePublishCommentEdit, useSubplebbit, useSubplebbits } from '@plebbit/plebbit-react-hooks'; import { debounce } from 'lodash'; import { Container, NavBar, Header, Break, PostMenu, BoardForm } from '../styled/views/Board.styled'; import { Threads, PostMenuCatalog } from '../styled/views/Catalog.styled'; import { TopBar, Footer } from '../styled/views/Thread.styled'; import { AlertModal } from '../styled/modals/AlertModal.styled'; import CatalogLoader from '../CatalogLoader'; import EditModal from '../modals/EditModal'; import ImageBanner from '../ImageBanner'; import VerifiedAuthor from '../VerifiedAuthor'; import CreateBoardModal from '../modals/CreateBoardModal'; import ModerationModal from '../modals/ModerationModal'; import OfflineIndicator from '../OfflineIndicator'; import SettingsModal from '../modals/SettingsModal'; import countLinks from '../../utils/countLinks'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import handleShareClick from '../../utils/handleShareClick'; import handleStyleChange from '../../utils/handleStyleChange'; import useError from '../../hooks/useError'; import useFeedRows from '../../hooks/useFeedRows'; import useFeedStateString from '../../hooks/useFeedStateString'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; import useSuccess from '../../hooks/useSuccess'; import useWindowWidth from '../../hooks/useWindowWidth'; import packageJson from '../../../package.json'; const {version} = packageJson; let lastVirtuosoStates = {}; 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 [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 account = useAccount(); const subplebbit = useSubplebbit({subplebbitAddress: thread.subplebbitAddress}); const threadMenuRefs = useRef({}); const postMenuRef = useRef(null); const postMenuCatalogRef = useRef(null); 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 handleOutsideClick = useCallback((e) => { if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) { setOpenMenuCid(null); } }, [openMenuCid]); const handleOptionClick = () => { setOpenMenuCid(null); }; useEffect(() => { if (openMenuCid !== null) { document.addEventListener('click', handleOutsideClick); } else { document.removeEventListener('click', handleOutsideClick); } return () => { document.removeEventListener('click', handleOutsideClick); }; }, [openMenuCid, handleOutsideClick]); 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'); } 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 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(setNewErrorMessage('Could not load challenges')); }; }); }; const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({ commentCid: commentCid, content: editedComment || undefined, subplebbitAddress: selectedAddress, onChallenge, onChallengeVerification, onError: (error) => { setNewErrorMessage(error.message); console.log(error); }, }); const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); 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]); 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 { displayWidth = '150px'; displayHeight = '150px'; } return (
{setIsHoveringOnThread(thread.cid)}} onMouseLeave={() => {setIsHoveringOnThread('')}}> {commentMediaInfo?.url ? ( setSelectedThread(thread.cid)}> {commentMediaInfo?.type === "webpage" ? ( thread.thumbnailUrl ? ( {commentMediaInfo.type} { e.target.src = fallbackImgUrl e.target.onerror = null; }} /> ) : null ) : 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 SubscriptionsCatalog = () => { const { defaultSubplebbits, isModerationOpen, setIsModerationOpen, isSettingsOpen, setIsSettingsOpen, originalCommentContent, selectedAddress, setSelectedAddress, selectedStyle, setSelectedTitle, } = useGeneralStore(state => state); const virtuosoRef = useRef(); const account = useAccount(); const navigate = useNavigate(); 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 { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: account?.subscriptions, sortType: 'active'}); const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); const {subplebbits} = useSubplebbits({subplebbitAddresses: account?.subscriptions, sortType: 'active'}); const stateString = useFeedStateString(subplebbits); 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]); // 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[`${selectedAddress}-catalog`] = snapshot; } }); }; window.addEventListener('scroll', setLastVirtuosoState); return () => window.removeEventListener('scroll', setLastVirtuosoState); }, [selectedAddress]); const lastVirtuosoState = lastVirtuosoStates["subscriptionsCatalog"]; return ( <> Subscriptions - 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
 
 
<>
<>
Subscriptions
{account?.subscriptions.length < 1 ? (
You haven't subscribed to any board yet.
) : (
You have subscribed to {account?.subscriptions.length} board{account?.subscriptions.length > 1 ? "s" : null}.
)}

Style:  
[ Return ]
Return
{subplebbits.state === "succeeded" ? ( null ) : (
{stateString}
)}
{account.subscriptions.length > 0 ? ( } useWindowScroll={true} components={{ Footer: () => hasMore ? : null}} endReached={loadMore} ref={virtuosoRef} restoreStateFrom={lastVirtuosoState} initialScrollTop={lastVirtuosoState?.scrollTop} /> ) : ( account.subscriptions.length !== 0 ? : null)}
); } export default SubscriptionsCatalog;