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, 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, AuthorDeleteAlert } from '../styled/views/Thread.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 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 SubscriptionsCatalog = () => {
const {
setCaptchaResponse,
setChallengesArray,
defaultSubplebbits,
editedComment,
setIsAuthorDelete,
setIsAuthorEdit,
setIsCaptchaOpen,
isModerationOpen, setIsModerationOpen,
isSettingsOpen, setIsSettingsOpen,
setModeratingCommentCid,
setResolveCaptchaPromise,
selectedAddress, setSelectedAddress,
selectedStyle,
setSelectedThread,
setSelectedTitle,
} = useGeneralStore(state => state);
const threadMenuRefs = useRef({});
const postMenuRef = useRef(null);
const postMenuCatalogRef = useRef(null);
const virtuosoRef = useRef();
const account = useAccount();
const navigate = useNavigate();
const [, setNewErrorMessage] = useError();
const [, setNewSuccessMessage] = useSuccess();
const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [originalCommentContent, setOriginalCommentContent] = useState(null);
const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false);
const [deletePost, setDeletePost] = useState(false);
const [isImageSearchOpen, setIsImageSearchOpen] = useState(false);
const [commentCid, setCommentCid] = useState(null);
const [moderatorPermissions, setModeratorPermissions] = useState({});
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(feed, columnCount);
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]);
// 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 onChallengeVerification = (challengeVerification) => {
if (challengeVerification.challengeSuccess === true) {
setNewSuccessMessage('Challenge Success');
}
else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
const onChallenge = async (challenges, comment) => {
let challengeAnswers = [];
try {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
}
};
const getChallengeAnswersFromUser = async (challenges) => {
setChallengesArray(challenges);
return new Promise((resolve, reject) => {
const imageString = challenges?.challenges[0].challenge;
const imageSource = `data:image/png;base64,${imageString}`;
const challengeImg = new Image();
challengeImg.src = imageSource;
challengeImg.onload = () => {
setIsCaptchaOpen(true);
const handleKeyDown = async (event) => {
if (event.key === 'Enter') {
const currentCaptchaResponse = useGeneralStore.getState().captchaResponse;
resolve(currentCaptchaResponse);
setIsCaptchaOpen(false);
document.removeEventListener('keydown', handleKeyDown);
event.preventDefault();
}
};
setCaptchaResponse('');
document.addEventListener('keydown', handleKeyDown);
setResolveCaptchaPromise(resolve);
};
challengeImg.onerror = () => {
reject(setNewErrorMessage('Could not load challenges'));
};
});
};
const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
commentCid: commentCid,
content: editedComment || undefined,
subplebbitAddress: selectedAddress,
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error.message); console.log(error);
},
});
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]);
// 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}`);
};
const CatalogPost = ({ post }) => {
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 isModerator = moderatorPermissions[thread.subplebbitAddress];
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?