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?
-
- No
- {
- setIsAuthorDelete(true);
- setIsAuthorEdit(false);
- setPublishCommentEditOptions(prevOptions => ({
- ...prevOptions,
- commentCid: comment.cid,
- subplebbitAddress: comment.subplebbitAddress,
- deleted: true,
- }));
- setTriggerPublishCommentEdit(true);
- onClose();
- }}
- >
- Yes
-
-
-
-
- );
- }
- });
- };
-
- 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
-
-
- All
- Subscriptions
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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
-
-
-
-
-
- >
-
-
-
-
-
-
- Style:
-
-
- Yotsuba
- Yotsuba B
- Futaba
- Burichan
- Tomorrow
- Photon
-
-
-
- [
- 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 ? (
-
-
- {commentMediaInfo?.type === 'iframe' && (
-
-
- {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
-
- ) : commentMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {commentMediaInfo?.type === "webpage" ? (
-
-
- {thread.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- ) : null}
- {commentMediaInfo?.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- ) : null}
- {commentMediaInfo?.type === "video" ? (
-
-
- {isThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- ) : null}
- {commentMediaInfo?.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
-
- ) : 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?
+
+ No
+ {
+ setIsAuthorDelete(true);
+ setIsAuthorEdit(false);
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: comment.cid,
+ subplebbitAddress: comment.subplebbitAddress,
+ deleted: true,
+ }));
+ setTriggerPublishCommentEdit(true);
+ onClose();
+ }}
+ >
+ Yes
+
+
+
+
+ );
+ },
+ });
+ };
+
+ 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
+
+
+ All
+ Subscriptions
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ Style:
+
+ Yotsuba
+ Yotsuba B
+ Futaba
+ Burichan
+ Tomorrow
+ Photon
+
+
+
+ [ 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 ? (
+
+
+ {commentMediaInfo?.type === 'iframe' && (
+
+
+ {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
+
+ ) : commentMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {commentMediaInfo?.type === 'webpage' ? (
+
+
+ {thread.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'video' ? (
+
+
+ {isThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ ) : null}
+
+ ) : null}
+
+ {thread.title ? (
+ thread.title.length > 75 ? (
+
+
+ {thread.title.slice(0, 75) + ' (...)'}
+
+
+ ) : (
+
+ {thread.title}
+
+ )
+ ) : null}
+
+ {thread.author.displayName ? (
+ thread.author.displayName.length > 20 ? (
+
+
+ {thread.author.displayName.slice(0, 20) + ' (...)'}
+
+
+ ) : (
+
+ {thread.author.displayName}
+
+ )
+ ) : (
+
+ Anonymous
+
+ )}
+ (u/
+
+ handleAddressClick(
+ {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} ,
+ )
+ }
+ >
+ {{({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} }
+
+ )
+
+ {getDate(thread.timestamp)}
+
+
+
+ c/
+ {
+ if (e.button === 2) return;
+ e.preventDefault();
+ setSelectedThreadCid(thread.cid);
+ setSelectedAddress(thread.subplebbitAddress);
+ let text = document.getSelection().toString();
+ text = text ? `>${text}` : text;
+ setSelectedText(text);
+ if (isReplyOpen) {
+ setReplyQuoteCid(thread.shortCid);
+ setTriggerInsertion(Date.now());
+ } else {
+ setIsReplyOpen(true);
+ setSelectedShortCid(thread.shortCid);
+ setSelectedParentCid(thread.cid);
+ }
+ }}
+ title='Reply to this post'
+ >
+ {thread.shortCid}
+
+ p/
+
+ {thread.subplebbitAddress.includes('.eth') ? (
+ thread.subplebbitAddress
+ ) : (
+
+ {thread.subplebbitAddress.slice(0, 10) + '(...)'}
+
+ )}
+
+
+
+ [
+ setSelectedThread(thread.cid)}
+ className='reply-link'
+ >
+ Reply
+
+ ]
+
+
+
+ {
+ threadMenuRefs.current[thread.cid] = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === thread.cid}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current[thread.cid].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === thread.cid ? null : thread.cid));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {
+ handleOptionClick(thread.cid);
+ handleShareClick(thread.subplebbitAddress, thread.cid);
+ }}
+ >
+ Share thread
+
+
+ {({ authorAddress }) => (
+ <>
+ {authorAddress === account?.author.address || authorAddress === account?.signer.address ? (
+ <>
+ handleAuthorEditClick(thread)}>Edit post
+ handleAuthorDeleteClick(thread)}>Delete post
+ >
+ ) : null}
+ {isModerator ? (
+ <>
+ {authorAddress === account?.author.address || authorAddress === account?.signer.address ? null : (
+ {
+ setSelectedAddress(thread.subplebbitAddress);
+ setModeratingCommentCid(thread.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(thread.cid);
+ setDeletePost(true);
+ }}
+ >
+ Delete post
+
+ )}
+ {
+ setSelectedAddress(thread.subplebbitAddress);
+ setModeratingCommentCid(thread.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(thread.cid);
+ }}
+ >
+ Mod tools
+
+ >
+ ) : null}
+ >
+ )}
+
+ {commentMediaInfo &&
+ (commentMediaInfo.type === 'image' || (commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail)) ? (
+ {
+ setIsImageSearchOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsImageSearchOpen(false);
+ }}
+ >
+ Image search »
+
+
+ ) : null}
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick(thread.cid)}>
+
+ Plebbit
+
+
+ {/* handleOptionClick(thread.cid)}>
Seedit
*/}
- handleOptionClick(thread.cid)}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
- {thread.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}} className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- setOutOfViewCid(reply.cid);
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(thread.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {thread.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefs.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/{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 »
-
- 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
- )}
-
- {reply.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}} className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- setOutOfViewCid(reply.cid);
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(reply.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefs.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/{reply.shortCid}
-
-
- ))
- }
-
-
- {replyMediaInfo?.url ? (
-
-
- {replyMediaInfo?.type === 'iframe' && (
-
-
- {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
-
- ) : replyMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {replyMediaInfo?.type === "webpage" && (
-
-
- {reply.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {replyMediaInfo?.type === "image" && (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- )}
- {replyMediaInfo?.type === "video" && (
-
-
- {isReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- )}
- {replyMediaInfo?.type === "audio" && (
-
- e.target.src = fallbackImgUrl} />
-
- )}
-
- ) : 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 ? (
+
+
+ {replyMediaInfo?.type === 'iframe' && (
+
+
+ {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
+
+ ) : replyMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {replyMediaInfo?.type === 'webpage' && (
+
+
+ {reply.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {replyMediaInfo?.type === 'image' && (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ )}
+ {replyMediaInfo?.type === 'video' && (
+
+
+ {isReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ )}
+ {replyMediaInfo?.type === 'audio' && (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ )}
+
+ ) : 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 ? (
-
- ) : (
-
- )}
-
-
-
-
{
- threadMenuRefsMobile.current[thread.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current[thread.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === thread.cid ? null : thread.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {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 ? (
- {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 ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "video" ? (
-
- {isMobileThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
- {commentMediaInfo?.type === "video" || commentMediaInfo?.type === "iframe" ? (
- isMobileThreadThumbnailClicked[index] ? (
-
- {handleThumbnailClick(index, 'mobileThread')}}
- >Close
-
- ) : (
- video
- )) : video
}
-
- ) : commentMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
- ) : 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 (
-
-
-
-
{
- replyMenuRefsMobile.current[reply.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === reply.cid ? null : reply.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {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 ? (
- {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 ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "video" ? (
-
- {isMobileReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
- {replyMediaInfo?.type === "video" || replyMediaInfo?.type === "iframe" ? (
- isMobileReplyThumbnailClicked[index] ? (
-
- {handleThumbnailClick(index, 'mobileReply')}}
- >Close
-
- ) : (
- video
- )) : video
}
-
- ) : replyMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
- ) : 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)}
-
-
-
- >
- );
-}
+ 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 ?
:
}
+
+
+
+
{
+ threadMenuRefsMobile.current[thread.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current[thread.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === thread.cid ? null : thread.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {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 ? (
+ {
+ 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 ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'video' ? (
+
+ {isMobileThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+ {commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'iframe' ? (
+ isMobileThreadThumbnailClicked[index] ? (
+
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ >
+ Close
+
+
+ ) : (
+
+ video
+
+ )
+ ) : (
+
+ video
+
+ )}
+
+ ) : commentMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ {commentMediaInfo?.type}
+
+
+ ) : 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 (
+
+
+
+
{
+ replyMenuRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === reply.cid ? null : reply.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {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 ? (
+ {
+ 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 ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'video' ? (
+
+ {isMobileReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+ {replyMediaInfo?.type === 'video' || replyMediaInfo?.type === 'iframe' ? (
+ isMobileReplyThumbnailClicked[index] ? (
+
+ {
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ >
+ Close
+
+
+ ) : (
+
+ video
+
+ )
+ ) : (
+
+ video
+
+ )}
+
+ ) : replyMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ {replyMediaInfo.type}
+
+
+ ) : null
+ ) : null}
+
+ ) : null}
+ {reply.content ? (
+ reply.content?.length > 500 ? (
+
+
+ {}}
+ key={`mob-r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+ {' '}
+ (...)
+
+
+
+
+ Comment too long.
+ setSelectedThread(thread.cid)}
+ className='ttl-link'
+ >
+ Click here
+
+ to view.{' '}
+
+
+
+ ) : (
+
+ {}}
+ key={`mob-r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+
+
+ )
+ ) : null}
+ {reply.replyCount > 0 ? (
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefsMobile.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ onClick={(event) => handleQuoteClick(reply, reply.shortCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, reply.shortCid, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ className='quote-link'
+ >
+ c/{reply.shortCid}
+
+
+
+ ))}
+
+ ) : null}
+
+
+ );
+ })}
+
+
+ );
+ }}
+ ref={virtuosoRef}
+ restoreStateFrom={lastVirtuosoState}
+ initialScrollTop={lastVirtuosoState?.scrollTop}
+ endReached={tryLoadMore}
+ useWindowScroll={true}
+ components={{
+ Footer: () => {
+ return isFeedCached ? null : ;
+ },
+ }}
+ />
+ ) : (
+
+ )}
+
+ {outOfViewCid &&
+ outOfViewPosition &&
+ createPortal(
+ ,
+ document.body,
+ )}
+
+
+
+ >
+ );
+};
+
+export default 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?
-
- No
- {
- setIsAuthorDelete(true);
- setIsAuthorEdit(false);
- setPublishCommentEditOptions(prevOptions => ({
- ...prevOptions,
- commentCid: comment.cid,
- subplebbitAddress: comment.subplebbitAddress,
- deleted: true,
- }));
- setTriggerPublishCommentEdit(true);
- onClose();
- }}
- >
- Yes
-
-
-
-
- );
- }
- });
- };
+ 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?
+
+ No
+ {
+ setIsAuthorDelete(true);
+ setIsAuthorEdit(false);
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: comment.cid,
+ subplebbitAddress: comment.subplebbitAddress,
+ deleted: true,
+ }));
+ setTriggerPublishCommentEdit(true);
+ onClose();
+ }}
+ >
+ Yes
+
+
+
+
+ );
+ },
+ });
+ };
+ 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 ? (
-
- {
- e.target.src = fallbackImgUrl
- e.target.onerror = null;
- }} />
-
- ) : null
- ) : null}
- {commentMediaInfo?.type === "iframe" && (thread.thumbnailUrl || commentMediaInfo.thumbnail) ? (
-
- { e.target.src = fallbackImgUrl }} />
-
- ) : 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}
-
- {isMediaShowed ? (
-
- ) : (
- 0 ? "offline-icon-no-link-shifted" : "offline-icon-no-link"} ${isHoveringOnThread === thread.cid ? 'offline-icon-no-link-hovered' : ''}`}
- tooltipPlace="top" />
- )}
-
- {
- setIsHoveringForMenu(thread.cid);
- handleMouseOnLeaveThread();
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}
- style={{ all: "unset"}}>
-
- R:
{thread.replyCount}
- {linkCount > 0 ? (
- <>
- /
- L:
{linkCount}
- >
- ) : null}
-
{
- threadMenuRefs.current[thread.cid] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- id='post-menu-button-catalog'
- 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(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
- ) : (
- {
- 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)}>
+ 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 ? (
+
+ {
+ e.target.src = fallbackImgUrl;
+ e.target.onerror = null;
+ }}
+ />
+
+ ) : null
+ ) : null}
+ {commentMediaInfo?.type === 'iframe' && (thread.thumbnailUrl || commentMediaInfo.thumbnail) ? (
+
+ {
+ e.target.src = fallbackImgUrl;
+ }}
+ />
+
+ ) : 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}
+
+ {isMediaShowed ? (
+
+ ) : (
+ 0 ? 'offline-icon-no-link-shifted' : 'offline-icon-no-link'} ${
+ isHoveringOnThread === thread.cid ? 'offline-icon-no-link-hovered' : ''
+ }`}
+ tooltipPlace='top'
+ />
+ )}
+
+ {
+ setIsHoveringForMenu(thread.cid);
+ handleMouseOnLeaveThread();
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ style={{ all: 'unset' }}
+ >
+
+ R:
{thread.replyCount}
+ {linkCount > 0 ? (
+ <>
+ / L:
{linkCount}
+ >
+ ) : null}
+
{
+ threadMenuRefs.current[thread.cid] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ id='post-menu-button-catalog'
+ 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(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 : (
+ {
+ 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
- )}
-
- setSelectedThread(thread.cid)}>
-
- {
- 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)}>
- {thread.title ? `${thread.title}` : null}
- {thread.content ? `: ${thread.content}` : null}
-
-
-
-
- >
- );
+ handleOptionClick(thread.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ setSelectedThread(thread.cid)}
+ >
+
+ {
+ 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)}
+ >
+ {thread.title ? `${thread.title}` : null}
+ {thread.content ? `: ${thread.content}` : null}
+
+
+
+
+ >
+ );
};
-
-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
-
-
- All
- Subscriptions
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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
-
-
-
-
-
- >
-
-
-
-
-
-
- Style:
-
-
- Yotsuba
- Yotsuba B
- Futaba
- Burichan
- Tomorrow
- Photon
-
-
-
- [
- 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}
- />
- ) : ( )}
-
-
-
- >
- );
-}
+ 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
+
+
+ All
+ Subscriptions
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ Style:
+
+ Yotsuba
+ Yotsuba B
+ Futaba
+ Burichan
+ Tomorrow
+ Photon
+
+
+
+ [ 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}
+ />
+ ) : (
+
+ )}
+
+
+
+ >
+ );
+};
-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?
-
- No
- {
- setIsAuthorDelete(true);
- setIsAuthorEdit(false);
- setPublishCommentEditOptions(prevOptions => ({
- ...prevOptions,
- commentCid: comment.cid,
- subplebbitAddress: comment.subplebbitAddress,
- deleted: true,
- }));
- setTriggerPublishCommentEdit(true);
- onClose();
- }}
- >
- Yes
-
-
-
-
- );
- }
- });
- };
-
- 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
-
-
- All
- Subscriptions
- {!defaultSubplebbits.some(subplebbit => subplebbit.address === selectedAddress) && (
- {selectedAddress}
- )}
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- {
- 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
-
-
-
-
-
- >
-
-
-
-
-
-
- [
- Start a New Thread
- ]
-
-
-
- Start a New Thread
-
-
-
-
-
-
- Name
-
- {account && account?.author && account?.author.displayName ? (
-
- ) : (
-
- )}
-
-
-
- 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
-
-
-
- [
- {window.scrollTo(0, 0)}}>Catalog
- ]
- {subplebbit.roles && subplebbit?.roles[account?.author?.address]?.role === "admin" ? (
-
- ) : null}
-
- {subplebbit?.state === "succeeded" ? (
- <>
-
- [
-
- handleSubscribe()}>
- {subscribed ? "Unsubscribe" : "Subscribe"}
-
-
- ]
-
-
- handleSubscribe()}>
- {subscribed ? "Unsubscribe" : "Subscribe"}
-
-
- >
- ) : (
-
- {stateString}
-
- )}
-
-
- {window.scrollTo(0, 0)}}>Catalog
-
-
-
-
-
-
- {subplebbit?.state === "failed" ? (
- null
- ) : (
- subplebbit?.state === "succeeded" ? (
- <>
- {subplebbit.rules ?
- <>
-
-
-
-
-
-
- Rules
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
- {getDate(subplebbit.createdAt)}
-
-
-
-
-
- [
- Reply
- ]
-
- {
- threadMenuRefs.current["rules"] = el;
- }}
- className='post-menu-button'
- rotated={openMenuCid === "rules"}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current["rules"].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === "rules" ? null : "rules"));
- }}
- >
- ▶
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
- {/* {isModerator ? (
+ 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?
+
+ No
+ {
+ setIsAuthorDelete(true);
+ setIsAuthorEdit(false);
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: comment.cid,
+ subplebbitAddress: comment.subplebbitAddress,
+ deleted: true,
+ }));
+ setTriggerPublishCommentEdit(true);
+ onClose();
+ }}
+ >
+ Yes
+
+
+
+
+ );
+ },
+ });
+ };
+
+ 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
+
+
+ All
+ Subscriptions
+ {!defaultSubplebbits.some((subplebbit) => subplebbit.address === selectedAddress) && {selectedAddress} }
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+ {
+ 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
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ [
+
+ Start a New Thread
+
+ ]
+
+
+
+
+ Start a New Thread
+
+
+
+
+
+
+
+ Name
+
+ {account && account?.author && account?.author.displayName ? (
+
+ ) : (
+
+ )}
+
+
+
+ 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
+
+
+
+ [
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Catalog
+
+ ]{subplebbit.roles && subplebbit?.roles[account?.author?.address]?.role === 'admin' ? : null}
+
+ {subplebbit?.state === 'succeeded' ? (
+ <>
+
+ [
+
+ handleSubscribe()}>{subscribed ? 'Unsubscribe' : 'Subscribe'}
+
+ ]
+
+
+ handleSubscribe()}>
+ {subscribed ? 'Unsubscribe' : 'Subscribe'}
+
+
+ >
+ ) : (
+
+ {stateString}
+
+ )}
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Catalog
+
+
+
+
+
+
+
+ {subplebbit?.state === 'failed' ? null : subplebbit?.state === 'succeeded' ? (
+ <>
+ {subplebbit.rules ? (
+ <>
+
+
+
+
+
+
+ Rules
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+
+
+
+ [
+
+ Reply
+
+ ]
+
+ {
+ threadMenuRefs.current['rules'] = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === 'rules'}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current['rules'].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === 'rules' ? null : 'rules'));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {/* {isModerator ? (
<>
change rules
>
) : null} */}
- {setIsClientRedirectMenuOpen(true)}}
- onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}>
- View on »
-
- handleOptionClick("rules")}>
- Plebbit
-
- {/* handleOptionClick("rules")}>
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick('rules')}>
+
+ Plebbit
+
+
+ {/* handleOptionClick("rules")}>
Seedit
*/}
- handleOptionClick("rules")}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
-
-
- {subplebbit.rules.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
{
- threadMenuRefsMobile.current["rules"] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current["rules"].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === "rules" ? null : "rules"));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}>...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === "rules" ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , document.body
- )}
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
-
-
-
-
-
-
- Rules
-
-
-
{getDate(subplebbit.createdAt)}
-
-
-
-
- {subplebbit.rules.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))}
-
-
-
-
- View Thread
-
-
-
- >
- : subplebbit.address === "plebtoken.eth" ? (
- <>
-
-
-
-
-
-
- Rules
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
- {getDate(subplebbit.createdAt)}
-
-
-
-
-
- [
- Reply
- ]
-
- {
- threadMenuRefs.current["rules"] = el;
- }}
- className='post-menu-button'
- rotated={openMenuCid === "rules"}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current["rules"].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === "rules" ? null : "rules"));
- }}
- >
- ▶
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
- {/* {isModerator ? (
+ handleOptionClick('rules')}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+
+
+ {subplebbit.rules.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
{
+ threadMenuRefsMobile.current['rules'] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current['rules'].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === 'rules' ? null : 'rules'));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === 'rules' ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ document.body,
+ )}
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+
+
+
+
+
+
+ Rules
+
+
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+
+
+ {subplebbit.rules.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))}
+
+
+
+
+
+ View Thread
+
+
+
+
+ >
+ ) : subplebbit.address === 'plebtoken.eth' ? (
+ <>
+
+
+
+
+
+
+ Rules
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+
+
+
+ [
+
+ Reply
+
+ ]
+
+ {
+ threadMenuRefs.current['rules'] = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === 'rules'}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current['rules'].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === 'rules' ? null : 'rules'));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {/* {isModerator ? (
<>
change rules
>
) : null} */}
- {setIsClientRedirectMenuOpen(true)}}
- onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}>
- View on »
-
- handleOptionClick("rules")}>
- Plebbit
-
- {/* handleOptionClick("rules")}>
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick('rules')}>
+
+ Plebbit
+
+
+ {/* handleOptionClick("rules")}>
Seedit
*/}
- handleOptionClick("rules")}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
-
-
- {plebtokenRules.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
{
- threadMenuRefsMobile.current["rules"] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current["rules"].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === "rules" ? null : "rules"));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}>...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === "rules" ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , document.body
- )}
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
-
-
-
-
-
-
- Rules
-
-
-
{getDate(subplebbit.createdAt)}
-
-
-
-
- {plebtokenRules.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))}
-
-
-
-
- View Thread
-
-
-
- >
- ) : null}
- {subplebbit.description ?
- <>
-
-
-
-
-
- {subplebbit.suggested?.avatarUrl ? (
-
-
-
-
-
-
-
-
- ) : null}
-
- Welcome to {subplebbit.title || subplebbit.address}!
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
- {getDate(subplebbit.createdAt)}
-
-
-
-
-
- [
- Reply
- ]
-
- {
- threadMenuRefs.current[subplebbit.pubsubTopic] = el;
- }}
- className='post-menu-button'
- rotated={openMenuCid === subplebbit.pubsubTopic}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current[subplebbit.pubsubTopic].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === subplebbit.pubsubTopic ? null : subplebbit.pubsubTopic));
- }}
- >
- ▶
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
- {/* {isModerator ? (
+ handleOptionClick('rules')}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+
+
+ {plebtokenRules.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
{
+ threadMenuRefsMobile.current['rules'] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current['rules'].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === 'rules' ? null : 'rules'));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === 'rules' ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ document.body,
+ )}
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+
+
+
+
+
+
+ Rules
+
+
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+
+
+ {plebtokenRules.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))}
+
+
+
+
+
+ View Thread
+
+
+
+
+ >
+ ) : null}
+ {subplebbit.description ? (
+ <>
+
+
+
+
+
+ {subplebbit.suggested?.avatarUrl ? (
+
+
+
+
+
+
+
+
+ ) : null}
+
+ Welcome to {subplebbit.title || subplebbit.address}!
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+
+
+
+ [
+
+ Reply
+
+ ]
+
+ {
+ threadMenuRefs.current[subplebbit.pubsubTopic] = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === subplebbit.pubsubTopic}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current[subplebbit.pubsubTopic].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === subplebbit.pubsubTopic ? null : subplebbit.pubsubTopic));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {/* {isModerator ? (
<>
change description
>
) : null} */}
- {subplebbit.suggested?.avatarUrl ? (
- {setIsImageSearchOpen(true)}}
- onMouseLeave={() => {setIsImageSearchOpen(false)}}>
- Image search »
-
- handleOptionClick("description")}>
- Google
-
- handleOptionClick("description")}>
- Yandex
-
- handleOptionClick("description")}>
- SauceNAO
-
-
-
- ) : null}
- {setIsClientRedirectMenuOpen(true)}}
- onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}>
- View on »
-
- handleOptionClick("description")}>
- Plebbit
-
- {/* handleOptionClick("description")}>
+ {subplebbit.suggested?.avatarUrl ? (
+ {
+ setIsImageSearchOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsImageSearchOpen(false);
+ }}
+ >
+ Image search »
+
+
+ ) : null}
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick('description')}>
+
+ Plebbit
+
+
+ {/* handleOptionClick("description")}>
Seedit
*/}
- handleOptionClick("description")}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
-
-
-
-
-
-
-
-
- {subplebbit.rules ?
-
:
-
- }
-
-
-
-
{
- threadMenuRefsMobile.current["description"] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current["description"].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === "description" ? null : "description"));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === "description" ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , document.body
- )}
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
-
-
-
-
-
-
- Welcome to {subplebbit.title || subplebbit.address}
-
-
-
{getDate(subplebbit.createdAt)}
-
-
- {subplebbit.suggested?.avatarUrl ? (
-
-
-
-
- image
-
-
-
- ) : null}
-
-
-
-
-
- View Thread
-
-
-
- >
- : null}
-
{
- if (editedComments[thread.cid]) {
- thread = editedComments[thread.cid];
- if (thread.removed) {
- thread.content = "[removed]";
- thread.link = undefined;
- }
- };
- const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {};
- const commentMediaInfo = getCommentMediaInfo(thread);
- const fallbackImgUrl = "assets/filedeleted-res.gif";
- 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`;
+ handleOptionClick('description')}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+
+
+
+
+
+
+
+
+ {subplebbit.rules ?
:
}
+
+
+
+
{
+ threadMenuRefsMobile.current['description'] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current['description'].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === 'description' ? null : 'description'));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === 'description' ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ document.body,
+ )}
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+
+
+
+
+
+
+ Welcome to {subplebbit.title || subplebbit.address}
+
+
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+ {subplebbit.suggested?.avatarUrl ? (
+
+
+
+
+ image
+
+
+
+ ) : null}
+
+
+
+
+
+
+ View Thread
+
+
+
+
+ >
+ ) : null}
+
{
+ if (editedComments[thread.cid]) {
+ thread = editedComments[thread.cid];
+ if (thread.removed) {
+ thread.content = '[removed]';
+ thread.link = undefined;
+ }
+ }
+ const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {};
+ const commentMediaInfo = getCommentMediaInfo(thread);
+ const fallbackImgUrl = 'assets/filedeleted-res.gif';
+ 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 ? (
-
-
- {commentMediaInfo?.type === 'iframe' && (
-
-
- {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
-
- ) : commentMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {commentMediaInfo?.type === "webpage" ? (
-
-
- {thread.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- ) : null}
- {commentMediaInfo?.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- ) : null}
- {commentMediaInfo?.type === "video" ? (
-
-
- {isThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- ) : null}
- {commentMediaInfo?.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
-
- ) : 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);
- 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.pinned ? (
- <>
-
-
- >
- ) : null}
- {thread.locked ? (
- <>
-
-
- >
- ) : null}
-
- [
- 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(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);
- handleOptionClick(thread.cid);
- setDeletePost(true);
- }}>
- Delete post
-
- )}
- {
- 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)}>
+ 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 ? (
+
+
+ {commentMediaInfo?.type === 'iframe' && (
+
+
+ {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
+
+ ) : commentMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {commentMediaInfo?.type === 'webpage' ? (
+
+
+ {thread.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'video' ? (
+
+
+ {isThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ ) : 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);
+ 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.pinned ? (
+ <>
+
+
+ >
+ ) : null}
+ {thread.locked ? (
+ <>
+
+
+ >
+ ) : null}
+
+ [
+ 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(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);
+ handleOptionClick(thread.cid);
+ setDeletePost(true);
+ }}
+ >
+ Delete post
+
+ )}
+ {
+ setModeratingCommentCid(thread.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(thread.cid);
+ }}
+ >
+ Mod tools
+
+ >
+ ) : null}
+ >
+ )}
+
+ {commentMediaInfo &&
+ (commentMediaInfo.type === 'image' || (commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail)) ? (
+ {
+ setIsImageSearchOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsImageSearchOpen(false);
+ }}
+ >
+ Image search »
+
+
+ ) : null}
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick(thread.cid)}>
+
+ Plebbit
+
+
+ {/* handleOptionClick(thread.cid)}>
Seedit
*/}
- handleOptionClick(thread.cid)}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
- {thread.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}}
- className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(thread.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {thread.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ const rect = backlinkRefs.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
- });
- }
- setOutOfViewCid(reply.cid);
- });
- }}
- 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 ? (
- <>
- {thread.content ? null : }
-
-
- {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 storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
- let signerAddress;
- if (storedSigners[reply.parentCid]) {
- signerAddress = storedSigners[reply.parentCid].address;
- }
- 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}
-
- ) : (
- anonymousMode ? (
-
- {signerAddress?.slice(8, 20)}
-
- ) : (
-
- {account?.author?.shortAddress}
-
- )
- )
- }
- )
-
-
-
-
{getDate(reply.timestamp)}
-
-
- c/
- {reply.shortCid ? (
- {
- if (e.button === 2) return;
- e.preventDefault();
- setSelectedThreadCid(thread.cid);
- 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}
- ) : (
-
- )}
-
-
{
- 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(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);
- handleOptionClick(reply.cid);
- setDeletePost(true);
- }}>
- Delete post
-
- )}
- {
- 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,
+ });
+ }
+ setOutOfViewCid(reply.cid);
+ });
+ }}
+ 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 ? (
+ <>
+ {thread.content ? null : }
+
+
+ {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 storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
+ let signerAddress;
+ if (storedSigners[reply.parentCid]) {
+ signerAddress = storedSigners[reply.parentCid].address;
+ }
+ 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}
+ ) : anonymousMode ? (
+ {signerAddress?.slice(8, 20)}
+ ) : (
+ {account?.author?.shortAddress}
+ )}
+ )
+
+
+
+
+ {getDate(reply.timestamp)}
+
+
+
+ c/
+ {reply.shortCid ? (
+ {
+ if (e.button === 2) return;
+ e.preventDefault();
+ setSelectedThreadCid(thread.cid);
+ 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}
+
+ ) : (
+
+ )}
+
+
+
{
+ 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(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);
+ handleOptionClick(reply.cid);
+ setDeletePost(true);
+ }}
+ >
+ Delete post
+
+ )}
+ {
+ 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
- )}
-
- {reply.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}} className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- setOutOfViewCid(reply.cid);
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(reply.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefs.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/{reply.shortCid}
-
-
- ))
- }
-
-
- {replyMediaInfo?.url ? (
-
-
- {replyMediaInfo?.type === 'iframe' && (
-
-
- {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
-
- ) : replyMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {replyMediaInfo?.type === "webpage" && (
-
-
- {reply.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {replyMediaInfo?.type === "image" && (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- )}
- {replyMediaInfo?.type === "video" && (
-
-
- {isReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- )}
- {replyMediaInfo?.type === "audio" && (
-
- e.target.src = fallbackImgUrl} />
-
- )}
-
- ) : 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={() => handleQuoteClick(reply, shortParentCid, null)}
- 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 ? (
+
+
+ {replyMediaInfo?.type === 'iframe' && (
+
+
+ {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
+
+ ) : replyMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {replyMediaInfo?.type === 'webpage' && (
+
+
+ {reply.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {replyMediaInfo?.type === 'image' && (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ )}
+ {replyMediaInfo?.type === 'video' && (
+
+
+ {isReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ )}
+ {replyMediaInfo?.type === 'audio' && (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ )}
+
+ ) : null}
+ {reply.content ? (
+ reply.content?.length > 500 ? (
+
+
+ {}}
+ key={`r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefs.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefs.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
- if (distanceToTop < postOnHoverHeight / 2) {
- top = window.scrollY - 5;
- } else if (distanceToBottom < postOnHoverHeight / 2) {
- top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
- } else {
- top = rect.top + window.scrollY - postOnHoverHeight / 2;
- }
-
- if (distanceToRight < 200) {
- setOutOfViewPosition({
- top,
- right: window.innerWidth - rect.left - 10,
- maxWidth: rect.left - 5
- });
- } else {
- setOutOfViewPosition({
- top,
- left: rect.left + rect.width + 5,
- maxWidth: window.innerWidth - rect.left - rect.width - 5
- });
- }
- });
- }}
- onMouseLeave={() => {
- removeHighlight();
- setOutOfViewCid(null);
- }}>
- {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null}
-
- {
- postRefs.current[quoteShortParentCid] = ref;
- }}
- postQuoteOnClick={(quoteShortParentCid) => {
- handleQuoteClick(reply, quoteShortParentCid, null)
- }}
- postQuoteOnOver={(quoteShortParentCid) => {
- const quoteParentCid = cidTracker[quoteShortParentCid];
- if (outOfViewCid !== quoteParentCid) {
- handleQuoteHover(reply, quoteShortParentCid, () => {
- setOutOfViewCid(quoteParentCid);
-
- const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
-
- if (distanceToTop < postOnHoverHeight / 2) {
- top = window.scrollY - 5;
- } else if (distanceToBottom < postOnHoverHeight / 2) {
- top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
- } else {
- top = rect.top + window.scrollY - postOnHoverHeight / 2;
- }
-
- if (distanceToRight < 200) {
- setOutOfViewPosition({
- top,
- right: window.innerWidth - rect.left - 10,
- maxWidth: rect.left - 5
- });
- } else {
- setOutOfViewPosition({
- top,
- left: rect.left + rect.width + 5,
- maxWidth: window.innerWidth - rect.left - rect.width - 5
- });
- }
- })
- }}
- }
- postQuoteOnLeave={() => {
- removeHighlight();
- setOutOfViewCid(null);
- }}
- />
-
-
- )
- : null}
-
-
-
- )
- })}
-
-
- {index === 0 && (!subplebbit.rules && !subplebbit.description) ? (
-
- ) : (
-
- )}
-
-
-
-
{
- threadMenuRefsMobile.current[thread.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current[thread.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === thread.cid ? null : thread.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === thread.cid ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , 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.pinned ? (
-
- ) : null}
- {thread.locked ? (
-
- ) : null}
-
-
- {thread.title ? (
- thread.title.length > 30 ?
-
-
- {thread.title.slice(0, 30) + " (...)"}
-
-
- :
- {thread.title}
- )
- : null}
-
-
-
- {getDate(thread.timestamp)}
-
-
- c/
- {
- if (e.button === 2) return;
- e.preventDefault();
- setSelectedThreadCid(thread.cid);
- 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 ? (
- {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 ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "video" ? (
-
- {isMobileThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
- {commentMediaInfo?.type === "video" || commentMediaInfo?.type === "iframe" ? (
- isMobileThreadThumbnailClicked[index] ? (
-
- {handleThumbnailClick(index, 'mobileThread')}}
- >Close
-
- ) : (
- video
- )) : video
}
-
- ) : commentMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
- ) : 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); window.scrollTo(0, 0);}}
- className="button-mobile" >View Thread
-
-
- {displayedReplies?.map((reply, index) => {
- const replyMediaInfo = getCommentMediaInfo(reply);
- const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed);
- let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
- let signerAddress;
- if (storedSigners[selectedThreadCidRef]) {
- signerAddress = storedSigners[selectedThreadCidRef].address;
- }
- return (
-
-
-
-
{
- replyMenuRefsMobile.current[reply.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === reply.cid ? null : reply.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === reply.cid ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , 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}
-
- ) : (
- anonymousMode ? (
-
- {signerAddress?.slice(8, 20)}
-
- ) : (
-
- {account?.author?.shortAddress}
-
- )
- )
- }
- )
-
-
-
-
-
- {getDate(reply.timestamp)}
-
- c/
- {reply.shortCid ? (
- {
- if (e.button === 2) return;
- e.preventDefault();
- setSelectedThreadCid(thread.cid);
- 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 ? (
- {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 ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "video" ? (
-
- {isMobileReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
- {replyMediaInfo?.type === "video" || replyMediaInfo?.type === "iframe" ? (
- isMobileReplyThumbnailClicked[index] ? (
-
- {handleThumbnailClick(index, 'mobileReply')}}
- >Close
-
- ) : (
- video
- )) : video
}
-
- ) : replyMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
- ) : 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 (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 (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
- });
- }
- });
- }}
- 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;
+ 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);
- }}
- 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)}
-
-
-
- >
- );
-}
+ 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 Board;
\ 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,
+ });
+ }
+ });
+ }
+ }}
+ 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={() => handleQuoteClick(reply, shortParentCid, null)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefs.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+
+
+ )
+ ) : null}
+
+
+
+ );
+ })}
+
+
+ {index === 0 && !subplebbit.rules && !subplebbit.description ? (
+
+ ) : (
+
+ )}
+
+
+
+
{
+ threadMenuRefsMobile.current[thread.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current[thread.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === thread.cid ? null : thread.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === thread.cid ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ 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.pinned ? (
+
+ ) : null}
+ {thread.locked ? (
+
+ ) : null}
+
+
+ {thread.title ? (
+ thread.title.length > 30 ? (
+
+
+ {thread.title.slice(0, 30) + ' (...)'}
+
+
+ ) : (
+
+ {thread.title}
+
+ )
+ ) : null}
+
+
+
+ {getDate(thread.timestamp)}
+
+
+ c/
+ {
+ if (e.button === 2) return;
+ e.preventDefault();
+ setSelectedThreadCid(thread.cid);
+ 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 ? (
+ {
+ 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 ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'video' ? (
+
+ {isMobileThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+ {commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'iframe' ? (
+ isMobileThreadThumbnailClicked[index] ? (
+
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ >
+ Close
+
+
+ ) : (
+
+ video
+
+ )
+ ) : (
+
+ video
+
+ )}
+
+ ) : commentMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ {commentMediaInfo?.type}
+
+
+ ) : 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);
+ window.scrollTo(0, 0);
+ }}
+ className='button-mobile'
+ >
+ View Thread
+
+
+
+ {displayedReplies?.map((reply, index) => {
+ const replyMediaInfo = getCommentMediaInfo(reply);
+ const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed);
+ let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
+ let signerAddress;
+ if (storedSigners[selectedThreadCidRef]) {
+ signerAddress = storedSigners[selectedThreadCidRef].address;
+ }
+ return (
+
+
+
+
{
+ replyMenuRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === reply.cid ? null : reply.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === reply.cid ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ 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}
+
+ ) : anonymousMode ? (
+ {signerAddress?.slice(8, 20)}
+ ) : (
+ {account?.author?.shortAddress}
+ )}
+ )
+
+
+
+
+
+ {getDate(reply.timestamp)}
+
+ c/
+ {reply.shortCid ? (
+ {
+ if (e.button === 2) return;
+ e.preventDefault();
+ setSelectedThreadCid(thread.cid);
+ 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 ? (
+ {
+ 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 ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'video' ? (
+
+ {isMobileReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+ {replyMediaInfo?.type === 'video' || replyMediaInfo?.type === 'iframe' ? (
+ isMobileReplyThumbnailClicked[index] ? (
+
+ {
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ >
+ Close
+
+
+ ) : (
+
+ video
+
+ )
+ ) : (
+
+ video
+
+ )}
+
+ ) : replyMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ {replyMediaInfo.type}
+
+
+ ) : null
+ ) : null}
+
+ ) : null}
+ {reply.content ? (
+ reply.content?.length > 500 ? (
+
+
+ {}}
+ key={`mob-r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+ {' '}
+ (...)
+
+
+
+
+ Comment too long.
+ setSelectedThread(thread.cid)}
+ className='ttl-link'
+ >
+ Click here
+
+ to view.{' '}
+
+
+
+ ) : (
+
+ {}}
+ key={`mob-r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+
+
+ )
+ ) : null}
+ {reply.replyCount > 0 ? (
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefsMobile.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ onClick={(event) => handleQuoteClick(reply, reply.shortCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, reply.shortCid, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ className='quote-link'
+ >
+ c/{reply.shortCid}
+
+
+
+ ))}
+
+ ) : null}
+
+
+ );
+ })}
+
+
+ );
+ }}
+ ref={virtuosoRef}
+ restoreStateFrom={lastVirtuosoState}
+ initialScrollTop={lastVirtuosoState?.scrollTop}
+ endReached={tryLoadMore}
+ useWindowScroll={true}
+ components={{
+ Footer: () => {
+ return isFeedCached ? null :
;
+ },
+ }}
+ />
+ >
+ ) : (
+
+ )}
+
+ {outOfViewCid &&
+ outOfViewPosition &&
+ createPortal(
+ ,
+ document.body,
+ )}
+
+
+
+ >
+ );
+};
+
+export default Board;
diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx
index 47af8353..aefafec3 100755
--- a/src/components/views/Catalog.jsx
+++ b/src/components/views/Catalog.jsx
@@ -35,1626 +35,1812 @@ import useFeedRows from '../../hooks/useFeedRows';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
import useWindowWidth from '../../hooks/useWindowWidth';
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,
+ selectedAddress,
+ setIsModerationOpen,
+ setChallengesArray,
+ setIsAuthorEdit,
+ setIsCaptchaOpen,
+ setIsEditModalOpen,
+ isModerator,
+ setModeratingCommentCid,
+ setOriginalCommentContent,
+ setPendingComment,
+ setResolveCaptchaPromise,
+ selectedStyle,
+ setSelectedThread,
+ } = useGeneralStore((state) => state);
-const CatalogPost = ({post}) => {
- const {
- editedComment,
- setDeletePost,
- setIsAuthorDelete,
- captchaResponse, setCaptchaResponse,
- selectedAddress,
- setIsModerationOpen,
- setChallengesArray,
- setIsAuthorEdit,
- setIsCaptchaOpen,
- setIsEditModalOpen,
- isModerator,
- setModeratingCommentCid,
- setOriginalCommentContent,
- setPendingComment,
- setResolveCaptchaPromise,
- 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 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 threadMenuRefs = useRef({});
+ const threadRefs = useRef({});
+ const postMenuRef = useRef(null);
+ const popupRef = useRef(null);
+ const imageRef = useRef(null);
+ const postMenuCatalogRef = useRef(null);
+ const textRef = useRef(null);
+ const { subplebbitAddress } = useParams();
- const threadMenuRefs = useRef({});
- const threadRefs = useRef({});
- const postMenuRef = useRef(null);
- const popupRef = useRef(null);
- const imageRef = useRef(null);
- const postMenuCatalogRef = useRef(null);
- const textRef = useRef(null);
- const { subplebbitAddress } = useParams();
+ const [, setNewErrorMessage] = useError();
+ const [, setNewSuccessMessage] = useSuccess();
- const [, setNewErrorMessage] = useError();
- const [, setNewSuccessMessage] = useSuccess();
+ const subplebbit = useSubplebbit({ subplebbitAddress: selectedAddress });
- const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress});
+ const account = useAccount();
+ 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 [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() || {};
- 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(
+ 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(
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`;
+ }
-
- const handleOutsideClick = useCallback((e) => {
- if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) {
- setOpenMenuCid(null);
- }
- }, [openMenuCid]);
+ setSpaceOnRight(spaceRight);
+ setIsCalculationDone(true);
+ }
+ };
- const handleOptionClick = () => {
- setOpenMenuCid(null);
- };
+ 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);
+ }
+ };
- 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 handleOptionClick = () => {
+ setOpenMenuCid(null);
+ };
- const handleAuthorDeleteClick = (comment) => {
- handleOptionClick(comment.cid);
+ useEffect(() => {
+ if (openMenuCid !== null) {
+ document.addEventListener('click', handleOutsideClick);
+ } else {
+ document.removeEventListener('click', handleOutsideClick);
+ }
- confirmAlert({
- customUI: ({ onClose }) => {
- return (
-
-
-
Are you sure you want to delete this post?
-
- No
- {
- setIsAuthorDelete(true);
- setIsAuthorEdit(false);
- setPublishCommentEditOptions(prevOptions => ({
- ...prevOptions,
- commentCid: comment.cid,
- subplebbitAddress: comment.subplebbitAddress,
- deleted: true,
- }));
- setTriggerPublishCommentEdit(true);
- onClose();
- }}
- >
- Yes
-
-
-
-
- );
- }
- });
- };
+ return () => {
+ document.removeEventListener('click', handleOutsideClick);
+ };
+ }, [openMenuCid, handleOutsideClick]);
+ const handleAuthorDeleteClick = (comment) => {
+ handleOptionClick(comment.cid);
- const handleAuthorEditClick = (comment) => {
- handleOptionClick(comment.cid);
- setIsAuthorEdit(true);
- setIsAuthorDelete(false);
- setCommentCid(comment.cid);
- setOriginalCommentContent(comment.content);
- setIsEditModalOpen(true);
- }
+ confirmAlert({
+ customUI: ({ onClose }) => {
+ return (
+
+
+
Are you sure you want to delete this post?
+
+ No
+ {
+ setIsAuthorDelete(true);
+ setIsAuthorEdit(false);
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: comment.cid,
+ subplebbitAddress: comment.subplebbitAddress,
+ deleted: true,
+ }));
+ setTriggerPublishCommentEdit(true);
+ onClose();
+ }}
+ >
+ Yes
+
+
+
+
+ );
+ },
+ });
+ };
+ 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 = [];
- 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)
- }
- };
+ try {
+ challengeAnswers = await getChallengeAnswersFromUser(challenges);
+ } catch (error) {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ }
+ if (challengeAnswers) {
+ await comment.publishChallengeAnswers(challengeAnswers);
+ }
+ };
+ const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
+ commentCid: commentCid,
+ content: editedComment || undefined,
+ subplebbitAddress: selectedAddress || subplebbitAddress,
+ onChallenge,
+ onChallengeVerification,
+ onError: (error) => {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ },
+ });
- const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
- commentCid: commentCid,
- content: editedComment || undefined,
- subplebbitAddress: selectedAddress || subplebbitAddress,
- onChallenge,
- onChallengeVerification,
- onError: (error) => {
- setNewErrorMessage(error.message); console.log(error);
- },
- });
-
-
- const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
+ const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
+ useEffect(() => {
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: commentCid,
+ content: editedComment || undefined,
+ }));
+ }, [commentCid, editedComment]);
- useEffect(() => {
- setPublishCommentEditOptions((prevOptions) => ({
- ...prevOptions,
- commentCid: commentCid,
- content: editedComment || undefined,
- }));
- }, [commentCid, editedComment]);
+ useEffect(() => {
+ if (editedComment !== '') {
+ setTriggerPublishCommentEdit(true);
+ }
+ }, [editedComment]);
+ useEffect(() => {
+ if (publishCommentEditOptions && triggerPublishCommentEdit) {
+ (async () => {
+ await publishCommentEdit();
+ setTriggerPublishCommentEdit(false);
+ })();
+ }
+ }, [publishCommentEditOptions, triggerPublishCommentEdit, publishCommentEdit]);
- useEffect(() => {
- if (editedComment !== '') {
- setTriggerPublishCommentEdit(true);
- }
- }, [editedComment]);
+ const getChallengeAnswersFromUser = async (challenges) => {
+ setChallengesArray(challenges);
-
- useEffect(() => {
- if (publishCommentEditOptions && triggerPublishCommentEdit) {
- (async () => {
- await publishCommentEdit();
- setTriggerPublishCommentEdit(false);
- })();
- }
- }, [publishCommentEditOptions, triggerPublishCommentEdit, publishCommentEdit]);
+ 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);
-
- setResolveCaptchaPromise(resolve);
- };
-
- challengeImg.onerror = () => {
- reject(setNewErrorMessage('Could not load challenges'));
- };
- });
- };
-
+ const handleKeyDown = async (event) => {
+ if (event.key === 'Enter') {
+ const currentCaptchaResponse = captchaResponse;
+ resolve(currentCaptchaResponse);
+ setIsCaptchaOpen(false);
+ document.removeEventListener('keydown', handleKeyDown);
+ event.preventDefault();
+ }
+ };
- 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';
- }
+ setCaptchaResponse('');
+ document.addEventListener('keydown', handleKeyDown);
+ setResolveCaptchaPromise(resolve);
+ };
- if (post.type === 'rules') {
- return (
- <>
- {isHoveringOnThread === 'rules' ?
- createPortal(
-
- handleMouseOnLeaveThread()}
- style={{
- visibility: isCalculationDone ? 'visible' : 'hidden',
- left:
- isCalculationDone && spaceOnRight > 250 ? textRect.right + 5 :
- isCalculationDone && spaceOnRight < 250 ? textRect.left - popupRect.width - 5
- : 'auto',
- top: popupPosition.top + 5,
- }}>
-
- Rules
- by
- ## Board Admins
-
- {thread.timestamp ? getFormattedTime(thread.timestamp) : null}
-
-
- , document.body
- )
- : null }
- {
- threadRefs.current['rules'] = el;
- }}
- onMouseLeave={()=>{handleMouseOnLeaveThread()}}>
-
{
- setIsHoveringForMenu('rules');
- handleMouseOnLeaveThread();
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}>
-
- R:
0 / L:
0
-
-
-
-
-
{
- threadMenuRefs.current["rules"] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- id='post-menu-button-catalog'
- rotated={openMenuCid === "rules"}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current["rules"].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === "rules" ? null : "rules"));
- }}
- >
- ▶
-
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
-
-
- {/* {isModerator ? (
+ challengeImg.onerror = () => {
+ reject(setNewErrorMessage('Could not load challenges'));
+ };
+ });
+ };
+
+ 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';
+ }
+
+ if (post.type === 'rules') {
+ return (
+ <>
+ {isHoveringOnThread === 'rules'
+ ? createPortal(
+
+ handleMouseOnLeaveThread()}
+ style={{
+ visibility: isCalculationDone ? 'visible' : 'hidden',
+ left:
+ isCalculationDone && spaceOnRight > 250
+ ? textRect.right + 5
+ : isCalculationDone && spaceOnRight < 250
+ ? textRect.left - popupRect.width - 5
+ : 'auto',
+ top: popupPosition.top + 5,
+ }}
+ >
+ Rules by
+ ## Board Admins
+ {thread.timestamp ? getFormattedTime(thread.timestamp) : null}
+
+ ,
+ document.body,
+ )
+ : null}
+ {
+ threadRefs.current['rules'] = el;
+ }}
+ onMouseLeave={() => {
+ handleMouseOnLeaveThread();
+ }}
+ >
+
{
+ setIsHoveringForMenu('rules');
+ handleMouseOnLeaveThread();
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ >
+
+ R:
0 / L:
0
+
+
+
+
+
{
+ threadMenuRefs.current['rules'] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ id='post-menu-button-catalog'
+ rotated={openMenuCid === 'rules'}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current['rules'].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === 'rules' ? null : 'rules'));
+ }}
+ >
+ ▶
+
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+
+ {/* {isModerator ? (
<>
change rules
>
) : null} */}
-
-
- , document.body
- )}
-
-
-
-
- {
- setIsHoveringOnThread('rules');
- setIsHoveringForMenu('rules');
- const rect = threadRefs.current['rules'].getBoundingClientRect();
- setPopupPosition({top: rect.top + window.scrollY, left: rect.left});
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}>
- Rules
- {": " + subplebbit.rules?.map((rule, index) => `${index + 1}. ${rule}`).join(' ')}
-
-
-
-
-
- >
- )
- } else if (post.type === 'description') {
- return (
- <>
- {isHoveringOnThread === 'description' ?
- createPortal(
-
- handleMouseOnLeaveThread()}
- style={{
- visibility: isCalculationDone ? 'visible' : 'hidden',
- left:
- isCalculationDone && spaceOnRight > 250
- ? subplebbit.suggestedAvatarUrl ? imageRect.right + 5 : textRect.right + 5 :
- isCalculationDone && spaceOnRight < 250
- ? subplebbit.suggestedAvatarUrl ? imageRect.left - popupRect.width - 5 : textRect.left - popupRect.width - 5
- : 'auto',
- top: popupPosition.top + 5,
- }}>
-
- Welcome to {subplebbit.title || subplebbit.address}
- by
- ## Board Admins
-
- {thread.timestamp ? getFormattedTime(thread.timestamp) : null}
-
-
- , document.body
- )
- : null }
- {
- threadRefs.current['description'] = el;
- }}
- onMouseLeave={()=>{handleMouseOnLeaveThread()}}>
-
- {subplebbit.suggested?.avatarUrl ? (
-
{
- setIsHoveringOnThread('description');
- setIsHoveringForMenu('description');
- const rect = threadRefs.current['description'].getBoundingClientRect();
- setPopupPosition({top: rect.top + window.scrollY, left: rect.left});
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}
- src={subplebbit.suggested?.avatarUrl} alt="board avatar" />
- ) : null}
-
- {subplebbit.suggested?.avatarUrl ? (
-
-
-
-
- ) : null}
-
{
- setIsHoveringForMenu('description');
- handleMouseOnLeaveThread();
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}>
-
- R:
0 / L:
0
- {subplebbit.suggested?.avatarUrl ? null : (
-
-
-
-
- )}
-
{
- threadMenuRefs.current["description"] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- id='post-menu-button-catalog'
- rotated={openMenuCid === "description"}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current["description"].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === "description" ? null : "description"));
- }}
- >
- ▶
-
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
-
-
- {/* {isModerator ? (
+
+
+ ,
+ document.body,
+ )}
+
+
+
+
+ {
+ setIsHoveringOnThread('rules');
+ setIsHoveringForMenu('rules');
+ const rect = threadRefs.current['rules'].getBoundingClientRect();
+ setPopupPosition({ top: rect.top + window.scrollY, left: rect.left });
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ >
+ Rules
+ {': ' + subplebbit.rules?.map((rule, index) => `${index + 1}. ${rule}`).join(' ')}
+
+
+
+
+
+ >
+ );
+ } else if (post.type === 'description') {
+ return (
+ <>
+ {isHoveringOnThread === 'description'
+ ? createPortal(
+
+ handleMouseOnLeaveThread()}
+ style={{
+ visibility: isCalculationDone ? 'visible' : 'hidden',
+ left:
+ isCalculationDone && spaceOnRight > 250
+ ? subplebbit.suggestedAvatarUrl
+ ? imageRect.right + 5
+ : textRect.right + 5
+ : isCalculationDone && spaceOnRight < 250
+ ? subplebbit.suggestedAvatarUrl
+ ? imageRect.left - popupRect.width - 5
+ : textRect.left - popupRect.width - 5
+ : 'auto',
+ top: popupPosition.top + 5,
+ }}
+ >
+ Welcome to {subplebbit.title || subplebbit.address} by
+ ## Board Admins
+ {thread.timestamp ? getFormattedTime(thread.timestamp) : null}
+
+ ,
+ document.body,
+ )
+ : null}
+ {
+ threadRefs.current['description'] = el;
+ }}
+ onMouseLeave={() => {
+ handleMouseOnLeaveThread();
+ }}
+ >
+
+ {subplebbit.suggested?.avatarUrl ? (
+
{
+ setIsHoveringOnThread('description');
+ setIsHoveringForMenu('description');
+ const rect = threadRefs.current['description'].getBoundingClientRect();
+ setPopupPosition({ top: rect.top + window.scrollY, left: rect.left });
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ src={subplebbit.suggested?.avatarUrl}
+ alt='board avatar'
+ />
+ ) : null}
+
+ {subplebbit.suggested?.avatarUrl ? (
+
+
+
+
+ ) : null}
+
{
+ setIsHoveringForMenu('description');
+ handleMouseOnLeaveThread();
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ >
+
+ R:
0 / L:
0
+ {subplebbit.suggested?.avatarUrl ? null : (
+
+
+
+
+ )}
+
{
+ threadMenuRefs.current['description'] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ id='post-menu-button-catalog'
+ rotated={openMenuCid === 'description'}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current['description'].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === 'description' ? null : 'description'));
+ }}
+ >
+ ▶
+
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+
+ {/* {isModerator ? (
<>
change description
>
) : null} */}
- {subplebbit.suggested?.avatarUrl ? (
- {setIsImageSearchOpen(true)}}
- onMouseLeave={() => {setIsImageSearchOpen(false)}}>
- Image search »
-
- handleOptionClick("description")}>
- Google
-
- handleOptionClick("description")}>
- Yandex
-
- handleOptionClick("description")}>
- SauceNAO
-
-
-
- ) : null}
-
-
- , document.body
- )}
-
-
setSelectedThread("description")}>
-
-
- {
- setIsHoveringOnThread('description');
- setIsHoveringForMenu('description');
- const rect = threadRefs.current['description'].getBoundingClientRect();
- setPopupPosition({top: rect.top + window.scrollY, left: rect.left});
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}>
- Welcome to {subplebbit.title || subplebbit.address}!
- {": " + subplebbit.description}
-
-
-
-
-
- >
- )
- } else {
- 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 ? (
-
- {
- e.target.src = fallbackImgUrl
- e.target.onerror = null;
- }} />
-
- ) : null
- ) : null}
- {commentMediaInfo?.type === "iframe" && (thread.thumbnailUrl || commentMediaInfo.thumbnail) ? (
-
- { e.target.src = fallbackImgUrl }} />
-
- ) : 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}
- {isMediaShowed ? (
-
- {thread.pinned ? (
-
- ) : null}
- {thread.locked ? (
-
- ) : null}
-
- ) : null}
- {
- setIsHoveringForMenu(thread.cid);
- handleMouseOnLeaveThread();
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}
- style={{ all: "unset"}}>
-
- R:
{thread.replyCount}
- {linkCount > 0 ? (
- <>
- /
- L:
{linkCount}
- >
- ) : null}
- {isMediaShowed ? null : (
-
- {thread.pinned ? (
-
- ) : null}
- {thread.locked ? (
-
- ) : null}
-
- )}
-
{
- threadMenuRefs.current[thread.cid] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- id='post-menu-button-catalog'
- 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);
- }}>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);
- handleOptionClick(thread.cid);
- setDeletePost(true);
- }}>
- Delete post
-
- )}
- {
- setModeratingCommentCid(thread.cid)
- setIsModerationOpen(true);
- handleOptionClick(thread.cid);
- }}>
- Mod tools
-
- >
- ) : null}
- >
- )}
- {isMediaShowed ? (
- {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)}>
+ {subplebbit.suggested?.avatarUrl ? (
+ {
+ setIsImageSearchOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsImageSearchOpen(false);
+ }}
+ >
+ Image search »
+
+
+ ) : null}
+
+
+ ,
+ document.body,
+ )}
+
+ setSelectedThread('description')}>
+
+
+ {
+ setIsHoveringOnThread('description');
+ setIsHoveringForMenu('description');
+ const rect = threadRefs.current['description'].getBoundingClientRect();
+ setPopupPosition({ top: rect.top + window.scrollY, left: rect.left });
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ >
+ Welcome to {subplebbit.title || subplebbit.address}!
+ {': ' + subplebbit.description}
+
+
+
+
+
+ >
+ );
+ } else {
+ 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 ? (
+
+ {
+ e.target.src = fallbackImgUrl;
+ e.target.onerror = null;
+ }}
+ />
+
+ ) : null
+ ) : null}
+ {commentMediaInfo?.type === 'iframe' && (thread.thumbnailUrl || commentMediaInfo.thumbnail) ? (
+
+ {
+ e.target.src = fallbackImgUrl;
+ }}
+ />
+
+ ) : 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}
+ {isMediaShowed ? (
+
+ {thread.pinned ? (
+
+ ) : null}
+ {thread.locked ? (
+
+ ) : null}
+
+ ) : null}
+ {
+ setIsHoveringForMenu(thread.cid);
+ handleMouseOnLeaveThread();
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ style={{ all: 'unset' }}
+ >
+
+ R:
{thread.replyCount}
+ {linkCount > 0 ? (
+ <>
+ / L:
{linkCount}
+ >
+ ) : null}
+ {isMediaShowed ? null : (
+
+ {thread.pinned ? (
+
+ ) : null}
+ {thread.locked ? (
+
+ ) : null}
+
+ )}
+
{
+ threadMenuRefs.current[thread.cid] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ id='post-menu-button-catalog'
+ 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);
+ }}
+ >
+ 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);
+ handleOptionClick(thread.cid);
+ setDeletePost(true);
+ }}
+ >
+ Delete post
+
+ )}
+ {
+ setModeratingCommentCid(thread.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(thread.cid);
+ }}
+ >
+ Mod tools
+
+ >
+ ) : null}
+ >
+ )}
+
+ {isMediaShowed ? (
+ {
+ 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
- )}
-
- setSelectedThread(thread.cid)}>
-
-
- {
- 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)}>
- {thread.title ? `${thread.title}` : null}
- {thread.content ? `: ${thread.content}` : null}
-
-
-
-
-
- >
- )
- }
+ handleOptionClick(thread.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+
setSelectedThread(thread.cid)}>
+
+
+ {
+ 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)}
+ >
+ {thread.title ? `${thread.title}` : null}
+ {thread.content ? `: ${thread.content}` : null}
+
+
+
+
+
+ >
+ );
+ }
};
-
-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 Catalog = () => {
- const {
- captchaResponse, setCaptchaResponse,
- setChallengesArray,
- defaultSubplebbits,
- setIsCaptchaOpen,
- isModerationOpen, setIsModerationOpen,
- setIsModerator,
- isSettingsOpen, setIsSettingsOpen,
- originalCommentContent,
- setPendingComment,
- setPendingCommentIndex,
- setResolveCaptchaPromise,
- selectedAddress, setSelectedAddress,
- selectedStyle,
- selectedTitle, setSelectedTitle,
- showPostForm,
- showPostFormLink,
- } = useGeneralStore(state => state);
+ const {
+ captchaResponse,
+ setCaptchaResponse,
+ setChallengesArray,
+ defaultSubplebbits,
+ setIsCaptchaOpen,
+ isModerationOpen,
+ setIsModerationOpen,
+ setIsModerator,
+ isSettingsOpen,
+ setIsSettingsOpen,
+ originalCommentContent,
+ setPendingComment,
+ setPendingCommentIndex,
+ setResolveCaptchaPromise,
+ selectedAddress,
+ setSelectedAddress,
+ selectedStyle,
+ selectedTitle,
+ setSelectedTitle,
+ showPostForm,
+ showPostFormLink,
+ } = useGeneralStore((state) => state);
- const { anonymousMode } = useAnonModeStore();
-
- const nameRef = useRef();
- const subjectRef = useRef();
- const commentRef = useRef();
- const linkRef = useRef();
- const selectedThreadCidRef = useRef(null);
- const virtuosoRef = useRef();
+ const { anonymousMode } = useAnonModeStore();
- const navigate = useNavigate();
-
- const [, setNewErrorMessage] = useError();
- const [, setNewSuccessMessage] = useSuccess();
-
- const [triggerPublishComment, setTriggerPublishComment] = useState(false);
- const [prevScrollPos, setPrevScrollPos] = useState(0);
- const [visible, setVisible] = useState(true);
- const [isEditModalOpen, setIsEditModalOpen] = useState(false);
- const [deletePost, setDeletePost] = useState(false);
- const [executeAnonMode, setExecuteAnonMode] = useState(false);
- const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
+ const nameRef = useRef();
+ const subjectRef = useRef();
+ const commentRef = useRef();
+ const linkRef = useRef();
+ const selectedThreadCidRef = useRef(null);
+ const virtuosoRef = useRef();
- useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode);
+ const navigate = useNavigate();
- const account = useAccount();
- const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'active'});
- const { subplebbitAddress } = useParams();
- const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress});
- const stateString = useStateString(subplebbit);
- let feedWithDescriptionAndRules = [...feed];
+ const [, setNewErrorMessage] = useError();
+ const [, setNewSuccessMessage] = useSuccess();
-
- if (subplebbit.rules) {
- feedWithDescriptionAndRules.unshift({ type: 'rules', content: subplebbit.rules });
- }
-
- if (subplebbit.description) {
- feedWithDescriptionAndRules.unshift({ type: 'description', content: subplebbit.description });
- }
-
+ const [triggerPublishComment, setTriggerPublishComment] = useState(false);
+ const [prevScrollPos, setPrevScrollPos] = useState(0);
+ const [visible, setVisible] = useState(true);
+ const [isEditModalOpen, setIsEditModalOpen] = useState(false);
+ const [deletePost, setDeletePost] = useState(false);
+ const [executeAnonMode, setExecuteAnonMode] = useState(false);
+ const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
- const columnWidth = 180;
- const windowWidth = useWindowWidth();
- const columnCount = Math.floor(windowWidth / columnWidth);
- const rows = useFeedRows(feedWithDescriptionAndRules, columnCount);
+ useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode);
- 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, setIsModerator]);
+ const account = useAccount();
+ const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses: [`${selectedAddress}`], sortType: 'active' });
+ const { subplebbitAddress } = useParams();
+ const subplebbit = useSubplebbit({ subplebbitAddress: selectedAddress });
+ const stateString = useStateString(subplebbit);
+ let feedWithDescriptionAndRules = [...feed];
+ if (subplebbit.rules) {
+ feedWithDescriptionAndRules.unshift({ type: 'rules', content: subplebbit.rules });
+ }
- useEffect(() => {
- setSelectedAddress(subplebbitAddress);
- }, [subplebbitAddress, setSelectedAddress]);
+ if (subplebbit.description) {
+ feedWithDescriptionAndRules.unshift({ type: 'description', content: subplebbit.description });
+ }
+ const columnWidth = 180;
+ const windowWidth = useWindowWidth();
+ const columnCount = Math.floor(windowWidth / columnWidth);
+ const rows = useFeedRows(feedWithDescriptionAndRules, columnCount);
- 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 (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, setIsModerator]);
- useEffect(() => {
- if (errorString) {
- setNewErrorMessage(errorString);
- }
- }, [errorString, setNewErrorMessage]);
+ useEffect(() => {
+ setSelectedAddress(subplebbitAddress);
+ }, [subplebbitAddress, setSelectedAddress]);
-
- const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress});
+ 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]);
- 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]);
+ const { subscribed, subscribe, unsubscribe } = useSubscribe({ subplebbitAddress: selectedAddress });
- // 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]);
+ 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]);
+ // mobile navbar scroll effect
+ useEffect(() => {
+ const debouncedHandleScroll = debounce(() => {
+ const currentScrollPos = window.pageYOffset;
+ setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10);
+ setPrevScrollPos(currentScrollPos);
+ }, 50);
- 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);
- }
- };
+ window.addEventListener('scroll', debouncedHandleScroll);
+ return () => window.removeEventListener('scroll', debouncedHandleScroll);
+ }, [prevScrollPos, visible]);
- 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 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);
+ }
+ };
- useEffect(() => {
- setPublishCommentOptions((prevPublishCommentOptions) => ({
- ...prevPublishCommentOptions,
- subplebbitAddress: selectedAddress,
- }));
- }, [selectedAddress]);
-
+ const onChallenge = async (challenges, comment) => {
+ setPendingComment(comment);
+ let challengeAnswers = [];
- const [publishCommentOptions, setPublishCommentOptions] = useState({
- subplebbitAddress: selectedAddress,
- onChallenge,
- onChallengeVerification,
- onError: (error) => {
- setNewErrorMessage(error.message); console.log(error);
- },
- });
-
+ try {
+ challengeAnswers = await getChallengeAnswersFromUser(challenges);
+ } catch (error) {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ }
+ if (challengeAnswers) {
+ await comment.publishChallengeAnswers(challengeAnswers);
+ }
+ };
- const { publishComment, index } = usePublishComment(publishCommentOptions);
+ useEffect(() => {
+ setPublishCommentOptions((prevPublishCommentOptions) => ({
+ ...prevPublishCommentOptions,
+ subplebbitAddress: selectedAddress,
+ }));
+ }, [selectedAddress]);
- useEffect(() => {
- if (index !== undefined) {
- setPendingCommentIndex(index);
- navigate(`/profile/c/${index}`);
- }
- }, [index, navigate, setPendingCommentIndex]);
+ const [publishCommentOptions, setPublishCommentOptions] = useState({
+ subplebbitAddress: selectedAddress,
+ onChallenge,
+ onChallengeVerification,
+ onError: (error) => {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ },
+ });
-
- 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 { publishComment, index } = usePublishComment(publishCommentOptions);
+ useEffect(() => {
+ if (index !== undefined) {
+ setPendingCommentIndex(index);
+ navigate(`/profile/c/${index}`);
+ }
+ }, [index, navigate, setPendingCommentIndex]);
- const handleSubmit = async (event) => {
- event.preventDefault();
-
- if (subjectRef.current.value === "") {
- setNewErrorMessage('Subject field is mandatory');
- return;
- }
+ 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 = '';
+ }
+ }, []);
- 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,
- }));
+ const handleSubmit = async (event) => {
+ event.preventDefault();
- setTriggerPublishComment(true);
- };
+ 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,
+ }));
- 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(() => {
- updateSigner();
- }, [updateSigner]);
+ setTriggerPublishComment(true);
+ };
+ const updateSigner = useCallback(async () => {
+ if (anonymousMode) {
+ setExecuteAnonMode(true);
- 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 = captchaResponse;
- resolve(currentCaptchaResponse);
- setIsCaptchaOpen(false);
- document.removeEventListener('keydown', handleKeyDown);
- event.preventDefault();
- }
- };
+ let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
+ let signer;
- setCaptchaResponse('');
- document.addEventListener('keydown', handleKeyDown);
-
- setResolveCaptchaPromise(resolve);
- };
-
- challengeImg.onerror = () => {
- reject(setNewErrorMessage('Could not load challenges'));
- };
- });
- };
+ 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);
+ }
+ }
- // mobile navbar board select functionality
- const handleSelectChange = (event) => {
- const selected = event.target.value;
+ setPublishCommentOptions((prevPublishCommentOptions) => {
+ const newPublishCommentOptions = {
+ ...prevPublishCommentOptions,
+ signer,
+ author: {
+ ...prevPublishCommentOptions.author,
+ address: signer?.address,
+ },
+ };
- if (selected === 'subscriptions') {
- navigate(`/p/subscriptions`);
- return;
- } else if (selected === 'all') {
- navigate(`/p/all`);
- return;
- }
+ if (JSON.stringify(prevPublishCommentOptions) !== JSON.stringify(newPublishCommentOptions)) {
+ return newPublishCommentOptions;
+ }
- const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title;
- setSelectedTitle(selectedTitle);
- setSelectedAddress(selected);
- navigate(`/p/${selected}`);
- };
+ return prevPublishCommentOptions;
+ });
+ }
+ }, [selectedThreadCidRef, anonymousMode, account]);
- 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(() => {
+ updateSigner();
+ }, [updateSigner]);
+ useEffect(() => {
+ if (publishCommentOptions && triggerPublishComment) {
+ (async () => {
+ await publishComment();
+ resetFields();
+ })();
+ setTriggerPublishComment(false);
+ setExecuteAnonMode(false);
+ }
+ }, [publishCommentOptions, triggerPublishComment, publishComment, resetFields]);
- useEffect(() => {
- const setLastVirtuosoState = () => {
- virtuosoRef.current?.getState((snapshot) => {
- if (snapshot?.scrollTop === 0 || snapshot?.ranges?.length) {
- lastVirtuosoStates[`${selectedAddress}-catalog`] = snapshot;
- }
- });
- };
- window.addEventListener('scroll', setLastVirtuosoState);
+ const getChallengeAnswersFromUser = async (challenges) => {
+ setChallengesArray(challenges);
- return () => window.removeEventListener('scroll', setLastVirtuosoState);
- }, [selectedAddress]);
-
+ 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 lastVirtuosoState = lastVirtuosoStates[`${selectedAddress}-catalog`];
+ 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();
+ }
+ };
- return (
- <>
-
- {((selectedTitle ? selectedTitle : selectedAddress) + " - 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
-
-
- All
- Subscriptions
- {!defaultSubplebbits.some(subplebbit => subplebbit.address === selectedAddress) && (
- {selectedAddress}
- )}
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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
-
-
-
-
-
- >
-
-
-
-
-
-
- [
- event.target.style.cursor='pointer'}>Start a New Thread
- ]
-
-
-
- event.target.style.cursor='pointer'}>Start a New Thread
-
-
-
-
-
-
- Name
-
- {account && account?.author && account?.author.displayName ? (
-
- ) : (
-
- )}
-
-
-
- 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
-
-
-
- [
- {window.scrollTo(0, 0)}}>Return
- ]
- {subplebbit.roles && subplebbit?.roles[account?.author?.address]?.role === "admin" ? (
-
- ) : null}
-
- {subplebbit.state === "succeeded" ? (
- <>
-
- [
-
- handleSubscribe()}>
- {subscribed ? "Unsubscribe" : "Subscribe"}
-
-
- ]
-
-
- handleSubscribe()}>
- {subscribed ? "Unsubscribe" : "Subscribe"}
-
-
- >
- ) : (
-
- {stateString}
-
- )}
-
-
- {window.scrollTo(0, 0)}}>Return
-
-
-
-
-
-
- {subplebbit?.state !== "failed" && subplebbit.state === "succeeded" ? (
- }
- useWindowScroll={true}
- components={{ Footer: () => hasMore ? : null}}
- endReached={loadMore}
- ref={virtuosoRef}
- restoreStateFrom={lastVirtuosoState}
- initialScrollTop={lastVirtuosoState?.scrollTop}
- />
- ) : ( )}
-
-
-
- >
- );
-}
+ setCaptchaResponse('');
+ document.addEventListener('keydown', handleKeyDown);
-export default Catalog;
\ No newline at end of file
+ setResolveCaptchaPromise(resolve);
+ };
+
+ challengeImg.onerror = () => {
+ reject(setNewErrorMessage('Could not load challenges'));
+ };
+ });
+ };
+
+ // 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}-catalog`] = snapshot;
+ }
+ });
+ };
+ window.addEventListener('scroll', setLastVirtuosoState);
+
+ return () => window.removeEventListener('scroll', setLastVirtuosoState);
+ }, [selectedAddress]);
+
+ const lastVirtuosoState = lastVirtuosoStates[`${selectedAddress}-catalog`];
+
+ return (
+ <>
+
+ {(selectedTitle ? selectedTitle : selectedAddress) + ' - 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
+
+
+ All
+ Subscriptions
+ {!defaultSubplebbits.some((subplebbit) => subplebbit.address === selectedAddress) && {selectedAddress} }
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ [
+ (event.target.style.cursor = 'pointer')}>
+ Start a New Thread
+
+ ]
+
+
+
+ (event.target.style.cursor = 'pointer')}>
+ Start a New Thread
+
+
+
+
+
+
+
+ Name
+
+ {account && account?.author && account?.author.displayName ? (
+
+ ) : (
+
+ )}
+
+
+
+ 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
+
+
+
+ [
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Return
+
+ ]{subplebbit.roles && subplebbit?.roles[account?.author?.address]?.role === 'admin' ? : null}
+
+ {subplebbit.state === 'succeeded' ? (
+ <>
+
+ [
+
+ handleSubscribe()}>{subscribed ? 'Unsubscribe' : 'Subscribe'}
+
+ ]
+
+
+ handleSubscribe()}>
+ {subscribed ? 'Unsubscribe' : 'Subscribe'}
+
+
+ >
+ ) : (
+
+ {stateString}
+
+ )}
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Return
+
+
+
+
+
+
+
+ {subplebbit?.state !== 'failed' && subplebbit.state === 'succeeded' ? (
+ }
+ useWindowScroll={true}
+ components={{ Footer: () => (hasMore ? : null) }}
+ endReached={loadMore}
+ ref={virtuosoRef}
+ restoreStateFrom={lastVirtuosoState}
+ initialScrollTop={lastVirtuosoState?.scrollTop}
+ />
+ ) : (
+
+ )}
+
+
+
+ >
+ );
+};
+
+export default Catalog;
diff --git a/src/components/views/Description.jsx b/src/components/views/Description.jsx
index 02ff9a81..b9b49427 100644
--- a/src/components/views/Description.jsx
+++ b/src/components/views/Description.jsx
@@ -6,7 +6,7 @@ import { Tooltip } from 'react-tooltip';
import { useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { debounce } from 'lodash';
import { Container, NavBar, Header, Break, PostMenu, PostForm, PostMenuMobile } from '../styled/views/Board.styled';
-import { TopBar, BoardForm, Footer, ReplyFormLink} from '../styled/views/Thread.styled';
+import { TopBar, BoardForm, Footer, ReplyFormLink } from '../styled/views/Thread.styled';
import { PostMenuCatalog } from '../styled/views/Catalog.styled';
import BoardStats from '../BoardStats';
import ImageBanner from '../ImageBanner';
@@ -20,657 +20,757 @@ import handleImageClick from '../../utils/handleImageClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
import packageJson from '../../../package.json';
-const {version} = packageJson;
+const { version } = packageJson;
const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : '';
-
const Description = () => {
- const {
- defaultSubplebbits,
- isSettingsOpen, setIsSettingsOpen,
- selectedAddress, setSelectedAddress,
- selectedStyle,
- setSelectedTitle,
- } = useGeneralStore(state => state);
-
- const navigate = useNavigate();
+ const { defaultSubplebbits, isSettingsOpen, setIsSettingsOpen, selectedAddress, setSelectedAddress, selectedStyle, setSelectedTitle } = useGeneralStore(
+ (state) => state,
+ );
- const threadMenuRefs = useRef({});
- const threadMenuRefsMobile = useRef({});
- const postMenuMobileRef = useRef(null);
- const postMenuCatalogRef = useRef(null);
+ const navigate = useNavigate();
- const [isAdminListOpen, setIsAdminListOpen] = useState(false);
- const [prevScrollPos, setPrevScrollPos] = useState(0);
- const [visible, setVisible] = useState(true);
- 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 [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
- const [isImageSearchOpen, setIsImageSearchOpen] = useState(false);
- const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
+ const threadMenuRefs = useRef({});
+ const threadMenuRefsMobile = useRef({});
+ const postMenuMobileRef = useRef(null);
+ const postMenuCatalogRef = useRef(null);
- const { subplebbitAddress } = useParams();
- const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress});
-
+ const [isAdminListOpen, setIsAdminListOpen] = useState(false);
+ const [prevScrollPos, setPrevScrollPos] = useState(0);
+ const [visible, setVisible] = useState(true);
+ 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 [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
+ const [isImageSearchOpen, setIsImageSearchOpen] = useState(false);
+ const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
- const handleOptionClick = () => {
- setOpenMenuCid(null);
- };
+ const { subplebbitAddress } = useParams();
+ const subplebbit = useSubplebbit({ subplebbitAddress: selectedAddress });
- const handleMobileOptionClick = () => {
- setOpenMobileMenuCid(null);
- };
+ 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 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]);
-
+ 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 (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);
- }
+ useEffect(() => {
+ if (openMobileMenuCid !== null) {
+ document.addEventListener('click', handleMobileOutsideClick);
+ } else {
+ document.removeEventListener('click', handleMobileOutsideClick);
+ }
- return () => {
- document.removeEventListener('click', handleMobileOutsideClick);
- };
- }, [openMobileMenuCid, handleMobileOutsideClick]);
+ return () => {
+ document.removeEventListener('click', handleMobileOutsideClick);
+ };
+ }, [openMobileMenuCid, handleMobileOutsideClick]);
-
- useEffect(() => {
- window.scrollTo(0, 0);
- }, []);
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
+ 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]);
- 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]);
+ // mobile navbar scroll effect
+ useEffect(() => {
+ const debouncedHandleScroll = debounce(() => {
+ const currentScrollPos = window.scrollY;
+ setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10);
+ setPrevScrollPos(currentScrollPos);
+ }, 50);
- // 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]);
+ window.addEventListener('scroll', debouncedHandleScroll);
- // mobile navbar board select functionality
- const handleSelectChange = (event) => {
- const selected = event.target.value;
+ return () => window.removeEventListener('scroll', debouncedHandleScroll);
+ }, [prevScrollPos, visible]);
- if (selected === 'subscriptions') {
- navigate(`/p/subscriptions`);
- return;
- } else if (selected === 'all') {
- navigate(`/p/all`);
- return;
- }
+ // 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}`);
- };
+ 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}`);
+ };
- return (
- <>
-
-
- {"Board Description - " + (subplebbit.title || subplebbit.address) + " - plebchan"}
-
-
-
- setIsCreateBoardOpen(false)} />
- setIsAdminListOpen(false)}
- roles={subplebbit.roles} />
- setIsSettingsOpen(false)} />
-
- <>
-
- [
- 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
-
-
- All
- Subscriptions
- {!defaultSubplebbits.some(subplebbit => subplebbit.address === selectedAddress) && (
- {selectedAddress}
- )}
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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
-
-
-
-
-
- >
-
-
-
-
-
-
- {window.scrollTo(0, 0)}}>
- Return
-
-
-
-
- {window.scrollTo(0, 0)}}>Catalog
-
-
-
-
- window.scrollTo(0, document.body.scrollHeight)} onMouseOver={(event) => event.target.style.cursor='pointer'}>Bottom
-
-
-
-
-
-
-
- Style:
-
-
- Yotsuba
- Yotsuba B
- Futaba
- Burichan
- Tomorrow
- Photon
-
-
-
- [
- Return
- ]
-
-
- [
- Catalog
- ]
-
-
- [
- window.scrollTo(0, document.body.scrollHeight)}
- onMouseOver={(event) => event.target.style.cursor='pointer'}
- onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}>Bottom
- ]
-
-
-
-
- <>
-
-
-
-
-
- {subplebbit.suggested?.avatarUrl ? (
-
-
-
-
-
-
-
-
- ) : null}
-
- Welcome to {subplebbit.title || subplebbit.address}
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
- {getDate(subplebbit.createdAt)}
-
-
-
-
-
- [
- {}} style={{textDecoration: "none"}} className="reply-link">Reply
- ]
-
- {
- threadMenuRefs.current[subplebbit.pubsubTopic] = el;
- }}
- className='post-menu-button'
- rotated={openMenuCid === subplebbit.pubsubTopic}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current[subplebbit.pubsubTopic].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === subplebbit.pubsubTopic ? null : subplebbit.pubsubTopic));
- }}
- >
- ▶
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
- {/* {isModerator ? (
+ return (
+ <>
+
+ {'Board Description - ' + (subplebbit.title || subplebbit.address) + ' - plebchan'}
+
+
+ setIsCreateBoardOpen(false)} />
+ setIsAdminListOpen(false)} roles={subplebbit.roles} />
+ setIsSettingsOpen(false)} />
+
+ <>
+
+ [
+ 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
+
+
+ All
+ Subscriptions
+ {!defaultSubplebbits.some((subplebbit) => subplebbit.address === selectedAddress) && {selectedAddress} }
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Return
+
+
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Catalog
+
+
+
+
+
+ window.scrollTo(0, document.body.scrollHeight)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ >
+ Bottom
+
+
+
+
+
+
+
+
+ Style:
+
+ Yotsuba
+ Yotsuba B
+ Futaba
+ Burichan
+ Tomorrow
+ Photon
+
+
+
+ [ Return]
+
+
+ [ Catalog]
+
+
+ [
+ window.scrollTo(0, document.body.scrollHeight)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}
+ >
+ Bottom
+
+ ]
+
+
+
+
+ <>
+
+
+
+
+
+ {subplebbit.suggested?.avatarUrl ? (
+
+
+
+
+
+
+
+
+ ) : null}
+
+ Welcome to {subplebbit.title || subplebbit.address}
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+
+
+ [
+ {}} style={{ textDecoration: 'none' }} className='reply-link'>
+ Reply
+
+ ]
+
+ {
+ threadMenuRefs.current[subplebbit.pubsubTopic] = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === subplebbit.pubsubTopic}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current[subplebbit.pubsubTopic].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === subplebbit.pubsubTopic ? null : subplebbit.pubsubTopic));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {/* {isModerator ? (
<>
change description
>
) : null} */}
- {subplebbit.suggested?.avatarUrl ? (
- {setIsImageSearchOpen(true)}}
- onMouseLeave={() => {setIsImageSearchOpen(false)}}>
- Image search »
-
- handleOptionClick("description")}>
- Google
-
- handleOptionClick("description")}>
- Yandex
-
- handleOptionClick("description")}>
- SauceNAO
-
-
-
- ) : null}
- {setIsClientRedirectMenuOpen(true)}}
- onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}>
- View on »
-
- handleOptionClick("description")}>
- Plebbit
-
- {/* handleOptionClick("description")}>
+ {subplebbit.suggested?.avatarUrl ? (
+ {
+ setIsImageSearchOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsImageSearchOpen(false);
+ }}
+ >
+ Image search »
+
+
+ ) : null}
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick('description')}>
+
+ Plebbit
+
+
+ {/* handleOptionClick("description")}>
Seedit
*/}
- handleOptionClick("description")}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{
- threadMenuRefsMobile.current["description"] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current["description"].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === "description" ? null : "description"));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === "description" ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , document.body
- )}
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
-
-
-
-
-
-
- Welcome to {subplebbit.title || subplebbit.address}
-
-
-
{getDate(subplebbit.createdAt)}
-
-
- {subplebbit.suggested?.avatarUrl ? (
-
-
-
-
- image
-
-
-
- ) : null}
-
-
-
-
-
- {}} className="button-mobile">View Thread
-
-
-
- >
-
-
-
- >
- );
-}
+ handleOptionClick('description')}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ threadMenuRefsMobile.current['description'] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current['description'].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === 'description' ? null : 'description'));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === 'description' ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ document.body,
+ )}
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+
+
+
+
+
+
+ Welcome to {subplebbit.title || subplebbit.address}
+
+
+
+
{getDate(subplebbit.createdAt)}
+
+
+ {subplebbit.suggested?.avatarUrl ? (
+
+
+
+
+ image
+
+
+
+ ) : null}
+
+
+
+
+
+ {}} className='button-mobile'>
+ View Thread
+
+
+
+
+ >
+
+
+
+ >
+ );
+};
-export default Description;
\ No newline at end of file
+export default Description;
diff --git a/src/components/views/Rules.jsx b/src/components/views/Rules.jsx
index 2d727924..af8ad026 100644
--- a/src/components/views/Rules.jsx
+++ b/src/components/views/Rules.jsx
@@ -6,7 +6,7 @@ import { Tooltip } from 'react-tooltip';
import { useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { debounce } from 'lodash';
import { Container, NavBar, Header, Break, PostMenu, PostForm, PostMenuMobile } from '../styled/views/Board.styled';
-import { TopBar, BoardForm, Footer, ReplyFormLink} from '../styled/views/Thread.styled';
+import { TopBar, BoardForm, Footer, ReplyFormLink } from '../styled/views/Thread.styled';
import { PostMenuCatalog } from '../styled/views/Catalog.styled';
import BoardStats from '../BoardStats';
import ImageBanner from '../ImageBanner';
@@ -18,604 +18,695 @@ import getDate from '../../utils/getDate';
import handleStyleChange from '../../utils/handleStyleChange';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
import packageJson from '../../../package.json';
-const {version} = packageJson;
+const { version } = packageJson;
const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : '';
-
const Rules = () => {
- const {
- defaultSubplebbits,
- isSettingsOpen, setIsSettingsOpen,
- selectedAddress, setSelectedAddress,
- selectedStyle,
- setSelectedTitle,
- } = useGeneralStore(state => state);
+ const { defaultSubplebbits, isSettingsOpen, setIsSettingsOpen, selectedAddress, setSelectedAddress, selectedStyle, setSelectedTitle } = useGeneralStore(
+ (state) => state,
+ );
- // 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 navigate = useNavigate();
+ // 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 threadMenuRefs = useRef({});
- const threadMenuRefsMobile = useRef({});
- const postMenuMobileRef = useRef(null);
- const postMenuCatalogRef = useRef(null);
+ const navigate = useNavigate();
- const [isAdminListOpen, setIsAdminListOpen] = useState(false);
- const [prevScrollPos, setPrevScrollPos] = useState(0);
- const [visible, setVisible] = useState(true);
- 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 [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
- const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
+ const threadMenuRefs = useRef({});
+ const threadMenuRefsMobile = useRef({});
+ const postMenuMobileRef = useRef(null);
+ const postMenuCatalogRef = useRef(null);
- const { subplebbitAddress } = useParams();
- const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress});
-
+ const [isAdminListOpen, setIsAdminListOpen] = useState(false);
+ const [prevScrollPos, setPrevScrollPos] = useState(0);
+ const [visible, setVisible] = useState(true);
+ 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 [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
+ const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
- const handleOptionClick = () => {
- setOpenMenuCid(null);
- };
+ const { subplebbitAddress } = useParams();
+ const subplebbit = useSubplebbit({ subplebbitAddress: selectedAddress });
- const handleMobileOptionClick = () => {
- setOpenMobileMenuCid(null);
- };
+ 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 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]);
-
+ 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 (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);
- }
+ useEffect(() => {
+ if (openMobileMenuCid !== null) {
+ document.addEventListener('click', handleMobileOutsideClick);
+ } else {
+ document.removeEventListener('click', handleMobileOutsideClick);
+ }
- return () => {
- document.removeEventListener('click', handleMobileOutsideClick);
- };
- }, [openMobileMenuCid, handleMobileOutsideClick]);
+ return () => {
+ document.removeEventListener('click', handleMobileOutsideClick);
+ };
+ }, [openMobileMenuCid, handleMobileOutsideClick]);
-
- useEffect(() => {
- window.scrollTo(0, 0);
- }, []);
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
+ 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]);
- 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]);
+ // mobile navbar scroll effect
+ useEffect(() => {
+ const debouncedHandleScroll = debounce(() => {
+ const currentScrollPos = window.scrollY;
+ setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10);
+ setPrevScrollPos(currentScrollPos);
+ }, 50);
- // 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]);
+ window.addEventListener('scroll', debouncedHandleScroll);
- // mobile navbar board select functionality
- const handleSelectChange = (event) => {
- const selected = event.target.value;
+ return () => window.removeEventListener('scroll', debouncedHandleScroll);
+ }, [prevScrollPos, visible]);
- if (selected === 'subscriptions') {
- navigate(`/p/subscriptions`);
- return;
- } else if (selected === 'all') {
- navigate(`/p/all`);
- return;
- }
+ // 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}`);
- };
+ 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}`);
+ };
- return (
- <>
-
-
- {"Board Rules - " + (subplebbit.title || subplebbit.address) + " - plebchan"}
-
-
-
- setIsCreateBoardOpen(false)} />
- setIsAdminListOpen(false)}
- roles={subplebbit.roles} />
- setIsSettingsOpen(false)} />
-
- <>
-
- [
- 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
-
-
- All
- Subscriptions
- {!defaultSubplebbits.some(subplebbit => subplebbit.address === selectedAddress) && (
- {selectedAddress}
- )}
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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
-
-
-
-
-
- >
-
-
-
-
-
-
- {window.scrollTo(0, 0)}}>
- Return
-
-
-
-
- {window.scrollTo(0, 0)}}>Catalog
-
-
-
-
- window.scrollTo(0, document.body.scrollHeight)} onMouseOver={(event) => event.target.style.cursor='pointer'}>Bottom
-
-
-
-
-
-
-
- Style:
-
-
- Yotsuba
- Yotsuba B
- Futaba
- Burichan
- Tomorrow
- Photon
-
-
-
- [
- Return
- ]
-
-
- [
- Catalog
- ]
-
-
- [
- window.scrollTo(0, document.body.scrollHeight)}
- onMouseOver={(event) => event.target.style.cursor='pointer'}
- onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}>Bottom
- ]
-
-
-
-
- <>
-
-
-
-
-
-
- Rules
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
- {getDate(subplebbit.createdAt)}
-
-
-
-
-
- [
- {}} style={{textDecoration: "none"}} className="reply-link">Reply
- ]
-
- {
- threadMenuRefs.current["rules"] = el;
- }}
- className='post-menu-button'
- rotated={openMenuCid === "rules"}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current["rules"].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === "rules" ? null : "rules"));
- }}
- >
- ▶
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
- {/* {isModerator ? (
+ return (
+ <>
+
+ {'Board Rules - ' + (subplebbit.title || subplebbit.address) + ' - plebchan'}
+
+
+ setIsCreateBoardOpen(false)} />
+ setIsAdminListOpen(false)} roles={subplebbit.roles} />
+ setIsSettingsOpen(false)} />
+
+ <>
+
+ [
+ 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
+
+
+ All
+ Subscriptions
+ {!defaultSubplebbits.some((subplebbit) => subplebbit.address === selectedAddress) && {selectedAddress} }
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Return
+
+
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Catalog
+
+
+
+
+
+ window.scrollTo(0, document.body.scrollHeight)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ >
+ Bottom
+
+
+
+
+
+
+
+
+ Style:
+
+ Yotsuba
+ Yotsuba B
+ Futaba
+ Burichan
+ Tomorrow
+ Photon
+
+
+
+ [ Return]
+
+
+ [ Catalog]
+
+
+ [
+ window.scrollTo(0, document.body.scrollHeight)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}
+ >
+ Bottom
+
+ ]
+
+
+
+
+ <>
+
+
+
+
+
+
+ Rules
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+ {getDate(subplebbit.createdAt)}
+
+
+
+
+
+ [
+ {}} style={{ textDecoration: 'none' }} className='reply-link'>
+ Reply
+
+ ]
+
+ {
+ threadMenuRefs.current['rules'] = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === 'rules'}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current['rules'].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === 'rules' ? null : 'rules'));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {/* {isModerator ? (
<>
change rules
>
) : null} */}
- {setIsClientRedirectMenuOpen(true)}}
- onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}>
- View on »
-
- handleOptionClick("rules")}>
- Plebbit
-
- {/* handleOptionClick("rules")}>
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick('rules')}>
+
+ Plebbit
+
+
+ {/* handleOptionClick("rules")}>
Seedit
*/}
- handleOptionClick("rules")}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
-
-
- {subplebbit.address === "plebtoken.eth" ? (
- plebtokenRules.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))
- ) : (
- subplebbit.rules?.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))
- )}
-
-
-
-
-
-
-
-
-
-
-
-
{
- threadMenuRefsMobile.current["rules"] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current["rules"].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === "rules" ? null : "rules"));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === "rules" ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , document.body
- )}
-
- {setIsAdminListOpen(!isAdminListOpen)}}
- >## Board Admins
-
-
-
-
-
-
-
- Rules
-
-
-
{getDate(subplebbit.createdAt)}
-
-
-
-
- {subplebbit.address === "plebtoken.eth" ? (
- plebtokenRules.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))
- ) : (
- subplebbit.rules?.map((rule, index) => (
-
- {index + 1}. {rule}
-
-
- ))
- )}
-
-
-
-
- {}} className="button-mobile">View Thread
-
-
-
- >
-
-
-
- >
- );
-}
+ handleOptionClick('rules')}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+
+
+ {subplebbit.address === 'plebtoken.eth'
+ ? plebtokenRules.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))
+ : subplebbit.rules?.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
{
+ threadMenuRefsMobile.current['rules'] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current['rules'].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === 'rules' ? null : 'rules'));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === 'rules' ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ document.body,
+ )}
+
+ {
+ setIsAdminListOpen(!isAdminListOpen);
+ }}
+ >
+ ## Board Admins
+
+
+
+
+
+
+
+
+
+ Rules
+
+
+
+
{getDate(subplebbit.createdAt)}
+
+
+
+
+ {subplebbit.address === 'plebtoken.eth'
+ ? plebtokenRules.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))
+ : subplebbit.rules?.map((rule, index) => (
+
+ {index + 1}. {rule}
+
+
+ ))}
+
+
+
+
+ {}} className='button-mobile'>
+ View Thread
+
+
+
+
+ >
+
+
+
+ >
+ );
+};
-export default Rules;
\ No newline at end of file
+export default Rules;
diff --git a/src/components/views/Subscriptions.jsx b/src/components/views/Subscriptions.jsx
index eaab82e5..d5880d33 100755
--- a/src/components/views/Subscriptions.jsx
+++ b/src/components/views/Subscriptions.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 useGeneralStore from '../../hooks/stores/useGeneralStore';
import { Container, NavBar, Header, Break, TopBar, BoardForm, PostMenu, PostMenuMobile } from '../styled/views/Board.styled';
@@ -45,2765 +45,3259 @@ import useFeedStateString from '../../hooks/useFeedStateString';
import useSuccess from '../../hooks/useSuccess';
import useAnonModeStore from '../../hooks/stores/useAnonModeStore';
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 Subscriptions = () => {
- const {
- defaultSubplebbits,
- editedComment,
- editedComments,
- feedCacheStates,
- setFeedCacheState,
- isSettingsOpen, setIsSettingsOpen,
- setModeratingCommentCid,
- selectedAddress,
- setCaptchaResponse,
- setChallengesArray,
- setIsAuthorDelete,
- setIsAuthorEdit,
- setIsCaptchaOpen,
- isModerationOpen, setIsModerationOpen,
- setReplyQuoteCid,
- setResolveCaptchaPromise,
- setSelectedAddress,
- setSelectedParentCid,
- setSelectedShortCid,
- selectedStyle,
- setSelectedThread,
- setSelectedText,
- setSelectedTitle,
- setTriggerInsertion,
- } = useGeneralStore(state => state);
-
- const { anonymousMode } = useAnonModeStore();
-
- const account = useAccount();
- const navigate = useNavigate();
- const [, setNewErrorMessage] = useError();
- const [, setNewSuccessMessage] = useSuccess();
-
- const threadMenuRefs = useRef({});
- const threadMenuRefsMobile = useRef({});
- const replyMenuRefs = useRef({});
- const replyMenuRefsMobile = useRef({});
- const postMenuCatalogRef = useRef(null);
- const postMenuMobileRef = useRef(null);
- const backlinkRefs = useRef({});
- const quoteRefs = useRef({});
- const postRefs = useRef({});
- const backlinkRefsMobile = useRef({});
- const quoteRefsMobile = useRef({});
- const postOnHoverRef = useRef(null);
- const selectedThreadCidRef = useRef(null);
- const virtuosoRef = useRef();
-
- const [isReplyOpen, setIsReplyOpen] = useState(false);
- const [isEditModalOpen, setIsEditModalOpen] = useState(false);
- const [originalCommentContent, setOriginalCommentContent] = useState(null);
- const [prevScrollPos, setPrevScrollPos] = useState(0);
- const [visible, setVisible] = useState(true);
- const [isImageSearchOpen, setIsImageSearchOpen] = useState(false);
- const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
- const [commentCid, setCommentCid] = useState(null);
- const [menuPosition, setMenuPosition] = useState({top: 0, left: 0});
- const [mobileMenuPosition, setMobileMenuPosition] = useState({top: 0, left: 0});
- const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false);
- const [openMenuCid, setOpenMenuCid] = useState(null);
- const [openMobileMenuCid, setOpenMobileMenuCid] = useState(null);
- const [outOfViewCid, setOutOfViewCid] = useState(null);
- const [outOfViewPosition, setOutOfViewPosition] = useState({top: 0, left: 0});
- const [postOnHoverHeight, setPostOnHoverHeight] = useState(0);
- const [deletePost, setDeletePost] = useState(false);
- const [moderatorPermissions, setModeratorPermissions] = useState({});
- const [executeAnonMode, setExecuteAnonMode] = useState(false);
- const [cidTracker, setCidTracker] = useState({});
- const [isThreadThumbnailClicked, setIsThreadThumbnailClicked] = useState({});
- const [isReplyThumbnailClicked, setIsReplyThumbnailClicked] = useState({});
- const [isMobileThreadThumbnailClicked, setIsMobileThreadThumbnailClicked] = useState({});
- const [isMobileReplyThumbnailClicked, setIsMobileReplyThumbnailClicked] = useState({});
- const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
-
- const setSelectedThreadCid = (cid) => { selectedThreadCidRef.current = cid };
- const isFeedCached = feedCacheStates['subscriptions'];
-
- useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode);
-
- const { feed, loadMore } = useFeed({subplebbitAddresses: account?.subscriptions, sortType: 'active'});
- const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp));
- const {subplebbits} = useSubplebbits({subplebbitAddresses: account?.subscriptions, sortType: 'active'});
-
- const stateString = useFeedStateString(subplebbits);
-
-
- useEffect(() => {
- if (feed) {
- setFeedCacheState('subscriptions', true);
- }
- }, [feed, setFeedCacheState]);
-
-
- const handleThumbnailClick = (index, type) => {
- switch(type) {
- case 'thread':
- setIsThreadThumbnailClicked(prevState => ({
- ...prevState,
- [index]: !prevState[index],
- }));
- break;
- case 'reply':
- setIsReplyThumbnailClicked(prevState => ({
- ...prevState,
- [index]: !prevState[index],
- }));
- break;
- case 'mobileThread':
- setIsMobileThreadThumbnailClicked(prevState => ({
- ...prevState,
- [index]: !prevState[index],
- }));
- break;
- case 'mobileReply':
- setIsMobileReplyThumbnailClicked(prevState => ({
- ...prevState,
- [index]: !prevState[index],
- }));
- break;
- default:
- break;
- }
- };
-
-
- useEffect(() => {
- let permissions = {};
-
- selectedFeed.forEach(thread => {
- const subplebbit = subplebbits.find(s => s && s.address === thread.subplebbitAddress);
-
- if (subplebbit && subplebbit.roles) {
- const role = subplebbit.roles[account?.author.address]?.role;
-
- if (role === 'moderator' || role === 'admin' || role === 'owner') {
- permissions[thread.subplebbitAddress] = true;
- } else {
- permissions[thread.subplebbitAddress] = false;
- }
- }
- });
-
- setModeratorPermissions(permissions);
- }, [account?.author.address, selectedFeed, subplebbits]);
-
-
- const handleOptionClick = () => {
- setOpenMenuCid(null);
- };
-
- const handleMobileOptionClick = () => {
- setOpenMobileMenuCid(null);
- };
-
-
- const handleOutsideClick = useCallback((e) => {
- if (openMenuCid !== null && !postMenuCatalogRef.current.contains(e.target)) {
- setOpenMenuCid(null);
- }
- }, [openMenuCid, postMenuCatalogRef]);
-
- const handleMobileOutsideClick = useCallback((e) => {
- if (openMobileMenuCid !== null && !postMenuMobileRef.current.contains(e.target)) {
- setOpenMobileMenuCid(null);
- }
- }, [openMobileMenuCid, postMenuMobileRef]);
-
-
- useEffect(() => {
- if (openMenuCid !== null) {
- document.addEventListener('click', handleOutsideClick);
- } else {
- document.removeEventListener('click', handleOutsideClick);
- }
-
- return () => {
- document.removeEventListener('click', handleOutsideClick);
- };
- }, [openMenuCid, handleOutsideClick]);
-
-
- useEffect(() => {
- if (openMobileMenuCid !== null) {
- document.addEventListener('click', handleMobileOutsideClick);
- } else {
- document.removeEventListener('click', handleMobileOutsideClick);
- }
-
- return () => {
- document.removeEventListener('click', handleMobileOutsideClick);
- };
- }, [openMobileMenuCid, handleMobileOutsideClick]);
-
-
- useEffect(() => {
- if (postOnHoverRef.current) {
- const rect = postOnHoverRef.current.getBoundingClientRect();
- setPostOnHoverHeight(rect.height);
- }
- }, [outOfViewCid]);
-
-
- const errorString = useMemo(() => {
- for (const subplebbit of subplebbits) {
- if (subplebbit?.updatingState !== 'failed') {
- return
- }
- }
- for (const subplebbit of subplebbits) {
- if (subplebbit?.error) {
- return `Failed fetching subplebbit: ${subplebbit?.error.toString().slice(0, 300)}`
- }
- }
- }, [subplebbits])
-
- useEffect(() => {
- if (errorString) {
- setNewErrorMessage(errorString);
- }
- }, [errorString, setNewErrorMessage]);
-
-
- useEffect(() => {
- setSelectedFeed(feed.sort((a, b) => b.timestamp - a.timestamp));
- }, [feed]);
-
-
- const flattenedRepliesByThread = useMemo(() => {
- return selectedFeed.reduce((acc, thread) => {
- const replies = flattenCommentsPages(thread.replies);
- acc[thread.cid] = replies;
- return acc;
- }, {});
- }, [selectedFeed]);
-
-
- const allParentCids = useMemo(() => {
- const allRepliesCids = Object.values(flattenedRepliesByThread).flatMap(replies => replies.map(reply => reply.cid));
- const allThreadCids = selectedFeed.map(thread => thread.cid);
- return new Set([...allThreadCids, ...allRepliesCids]);
- }, [flattenedRepliesByThread, selectedFeed]);
-
-
- const filter = useCallback((accountComment) => allParentCids.has(accountComment.parentCid), [allParentCids]);
-
-
- const { accountComments } = useAccountComments({ filter });
-
-
- const filteredRepliesByThread = useMemo(() => {
- const maxRepliesPerThread = 5;
-
- const accountRepliesNotYetInCommentReplies = selectedFeed.reduce((acc, thread) => {
- const replyCids = new Set(flattenedRepliesByThread[thread.cid].map(reply => reply.cid));
- acc[thread.cid] = accountComments.filter(accountReply => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid);
- return acc;
- }, {});
-
- return selectedFeed.reduce((acc, thread) => {
- const combinedReplies = [...flattenedRepliesByThread[thread.cid], ...accountRepliesNotYetInCommentReplies[thread.cid]].sort((a, b) => a.timestamp - b.timestamp);
- acc[thread.cid] = {
- displayedReplies: combinedReplies.slice(0, maxRepliesPerThread),
- omittedCount: Math.max(combinedReplies.length - maxRepliesPerThread, 0),
- };
- return acc;
- }, {});
- }, [flattenedRepliesByThread, accountComments, selectedFeed]);
-
-
- const pendingReplyCounts = useMemo(() => {
- return selectedFeed.reduce((acc, thread) => {
- const replyCids = new Set(flattenedRepliesByThread[thread.cid].map(reply => reply.cid));
- acc[thread.cid] = accountComments.filter(accountReply => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid).length;
- return acc;
- }, {});
- }, [flattenedRepliesByThread, accountComments, selectedFeed]);
-
- const allReplies = useMemo(() => {
- return selectedFeed.flatMap(thread => filteredRepliesByThread[thread.cid]?.displayedReplies || []);
- }, [selectedFeed, filteredRepliesByThread]);
-
- // let post.jsx access full cid of user-typed short cid
- useEffect(() => {
- const newCidTracker = {};
- allReplies.forEach((reply) => {
- newCidTracker[reply.shortCid] = reply.cid;
- });
- setCidTracker(newCidTracker);
- }, [allReplies]);
-
- // mobile navbar scroll effect
- useEffect(() => {
- const debouncedHandleScroll = debounce(() => {
- const currentScrollPos = window.pageYOffset;
- setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10);
- setPrevScrollPos(currentScrollPos);
- }, 50);
-
- window.addEventListener('scroll', debouncedHandleScroll);
-
- return () => window.removeEventListener('scroll', debouncedHandleScroll);
- }, [prevScrollPos, visible]);
-
-
- const tryLoadMore = async () => {
- try {
- await loadMore();
- } catch (e) {
- await new Promise(resolve => setTimeout(resolve, 1000));
- }
- };
-
-
- const onChallengeVerification = (challengeVerification) => {
- if (challengeVerification.challengeSuccess === true) {
- setNewSuccessMessage('Challenge Success'); console.log('challenge success', challengeVerification);
- }
- else if (challengeVerification.challengeSuccess === false) {
- setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
- console.log('challenge failed', challengeVerification);
- }
- };
-
-
- const onChallenge = async (challenges, comment) => {
- let challengeAnswers = [];
- try {
- challengeAnswers = await getChallengeAnswersFromUser(challenges)
- }
- catch (error) {
- setNewErrorMessage(error.message); console.log(error);
- }
- if (challengeAnswers) {
- await comment.publishChallengeAnswers(challengeAnswers)
- }
- };
-
-
- const getChallengeAnswersFromUser = async (challenges) => {
- setChallengesArray(challenges);
-
- return new Promise((resolve, reject) => {
- const imageString = challenges?.challenges[0].challenge;
- const imageSource = `data:image/png;base64,${imageString}`;
- const challengeImg = new Image();
- challengeImg.src = imageSource;
-
- challengeImg.onload = () => {
- setIsCaptchaOpen(true);
-
- const handleKeyDown = async (event) => {
- if (event.key === 'Enter') {
- const currentCaptchaResponse = useGeneralStore.getState().captchaResponse;
- resolve(currentCaptchaResponse);
- setIsCaptchaOpen(false);
- document.removeEventListener('keydown', handleKeyDown);
- event.preventDefault();
- }
- };
-
- setCaptchaResponse('');
- document.addEventListener('keydown', handleKeyDown);
-
- setResolveCaptchaPromise(resolve);
- };
-
- challengeImg.onerror = () => {
- reject(setNewErrorMessage('Could not load challenges'));
- };
- });
- };
-
-
- const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
- commentCid: commentCid,
- content: editedComment || undefined,
- subplebbitAddress: selectedAddress,
- onChallenge,
- onChallengeVerification,
- onError: (error) => {
- setNewErrorMessage(error.message); console.log(error);
- },
- });
-
-
- useEffect(() => {
- if (anonymousMode) {
- setExecuteAnonMode(true);
- }
- }, [anonymousMode, selectedThreadCidRef]);
-
-
- const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
-
-
- const handleAuthorDeleteClick = (comment) => {
- handleOptionClick(comment.cid);
-
- confirmAlert({
- customUI: ({ onClose }) => {
- return (
-
-
-
Are you sure you want to delete this post?
-
- No
- {
- setIsAuthorDelete(true);
- setIsAuthorEdit(false);
- setPublishCommentEditOptions(prevOptions => ({
- ...prevOptions,
- commentCid: comment.cid,
- subplebbitAddress: comment.subplebbitAddress,
- deleted: true,
- }));
- setTriggerPublishCommentEdit(true);
- onClose();
- }}
- >
- Yes
-
-
-
-
- );
- }
- });
- };
-
-
- const handleAuthorEditClick = (comment) => {
- handleOptionClick(comment.cid);
- setIsAuthorEdit(true);
- setIsAuthorDelete(false);
- setCommentCid(comment.cid);
- setOriginalCommentContent(comment.content);
- setIsEditModalOpen(true);
- };
-
-
- useEffect(() => {
- setPublishCommentEditOptions((prevOptions) => ({
- ...prevOptions,
- commentCid: commentCid,
- content: editedComment || undefined,
- }));
- }, [commentCid, editedComment]);
-
-
- useEffect(() => {
- if (editedComment !== '') {
- setTriggerPublishCommentEdit(true);
- }
- }, [editedComment, setIsAuthorEdit]);
-
-
- useEffect(() => {
- let isActive = true;
- if (publishCommentEditOptions && triggerPublishCommentEdit) {
- (async () => {
- await publishCommentEdit();
- if (isActive) {
- setTriggerPublishCommentEdit(false);
- }
- })();
- }
-
- return () => {
- isActive = false;
- };
- }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]);
-
- // desktop navbar board select functionality
- const handleClickTitle = (title, address) => {
- setSelectedTitle(title);
- setSelectedAddress(address);
- setSelectedFeed(feed.filter(feed => feed.title === title));
- };
-
- // mobile navbar board select functionality
- const handleSelectChange = (event) => {
- const selected = event.target.value;
-
- if (selected === 'subscriptions') {
- navigate(`/p/subscriptions`);
- return;
- } else if (selected === 'all') {
- navigate(`/p/all`);
- return;
- }
-
- const selectedTitle = defaultSubplebbits?.find((subplebbit) => subplebbit.address === selected).title;
- setSelectedTitle(selectedTitle);
- setSelectedAddress(selected);
- navigate(`/p/${selected}`);
- };
-
-
- useEffect(() => {
- const setLastVirtuosoState = () => {
- virtuosoRef.current?.getState((snapshot) => {
- if (snapshot?.scrollTop === 0 || snapshot?.ranges?.length) {
- lastVirtuosoStates['subscriptions'] = snapshot;
- }
- });
- };
- window.addEventListener('scroll', setLastVirtuosoState);
-
- return () => window.removeEventListener('scroll', setLastVirtuosoState);
- }, []);
-
- const lastVirtuosoState = lastVirtuosoStates['subscriptions'];
-
-
- return (
- <>
-
- Subscriptions - plebchan
-
-
- setIsCreateBoardOpen(false)} />
- setIsReplyOpen(false)} />
- setIsSettingsOpen(false)} />
- {setIsModerationOpen(false); setDeletePost(false)}}
- deletePost={deletePost} />
- setIsEditModalOpen(false)}
- originalCommentContent={originalCommentContent} />
-
- <>
-
- [
- window.scrollTo(0, 0)}>All
- /
- window.scrollTo(0, 0)}>Subscriptions
- ] [
- {defaultSubplebbits.map((subplebbit, index) => (
-
- {index === 0 ? null : "\u00a0"}
- handleClickTitle(subplebbit.title, subplebbit.address)}
- >{subplebbit.title ? subplebbit.title : subplebbit.address}
- {index !== defaultSubplebbits.length - 1 ? " /" : null}
-
- ))}
- ]
-
-
- [
- {
- window.electron && window.electron.isElectron ? (
- setIsCreateBoardOpen(true)
- ) : (
- alert(
- 'You can create a board with the desktop version of plebchan:\nhttps://github.com/plebbit/plebchan/releases/latest\n\nIf you are comfortable with the command line, use plebbit-cli:\nhttps://github.com/plebbit/plebbit-cli\n\n'
- )
- )
- }
- }>Create Board
- ]
- [
- setIsSettingsOpen(true)}>Settings
- ]
- [
- Home
- ]
-
-
-
-
- Board
-
-
- All
- Subscriptions
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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:
-
-
- Yotsuba
- Yotsuba B
- Futaba
- Burichan
- Tomorrow
- Photon
-
-
-
- [
- Catalog
- ]
-
- {subplebbits.state === "succeeded" ? (
- null
- ) : (
-
- {stateString}
-
- )}
-
-
- Catalog
-
-
-
-
-
-
- { !feed ? (
- null
- ) : (
-
{
- if (editedComments[thread.cid]) {
- thread = editedComments[thread.cid];
- if (thread.removed) {
- thread.content = "[removed]";
- thread.link = undefined;
- }
- };
- const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {};
- const commentMediaInfo = getCommentMediaInfo(thread);
- const fallbackImgUrl = "assets/filedeleted-res.gif";
- const isModerator = moderatorPermissions[thread.subplebbitAddress];
- let displayWidth, displayHeight, displayWidthMobile, displayHeightMobile;
- if (thread.linkWidth && thread.linkHeight) {
- let scale = Math.min(1, 250 / Math.max(thread.linkWidth, thread.linkHeight));
- displayWidth = `${thread.linkWidth * scale}px`;
- displayHeight = `${thread.linkHeight * scale}px`;
-
- scale = Math.min(1, 100 / Math.max(thread.linkWidth, thread.linkHeight));
- displayWidthMobile = `${thread.linkWidth * scale}px`;
- displayHeightMobile = `${thread.linkHeight * scale}px`;
- } else {
- displayWidth = '250px';
- displayHeight = '250px';
- displayWidthMobile = '100px';
- displayHeightMobile = '100px';
- }
- return (
-
-
-
-
-
-
- {commentMediaInfo?.url ? (
-
-
- {commentMediaInfo?.type === 'iframe' && (
-
-
- {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
-
- ) : commentMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {commentMediaInfo?.type === "webpage" ? (
-
-
- {thread.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- ) : null}
- {commentMediaInfo?.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- ) : null}
- {commentMediaInfo?.type === "video" ? (
-
-
- {isThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- ) : null}
- {commentMediaInfo?.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
-
- ) : 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['subscriptions'];
+
+ useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode);
+
+ const { feed, loadMore } = useFeed({ subplebbitAddresses: account?.subscriptions, sortType: 'active' });
+ const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp));
+ const { subplebbits } = useSubplebbits({ subplebbitAddresses: account?.subscriptions, sortType: 'active' });
+
+ const stateString = useFeedStateString(subplebbits);
+
+ useEffect(() => {
+ if (feed) {
+ setFeedCacheState('subscriptions', true);
+ }
+ }, [feed, setFeedCacheState]);
+
+ const handleThumbnailClick = (index, type) => {
+ switch (type) {
+ case 'thread':
+ setIsThreadThumbnailClicked((prevState) => ({
+ ...prevState,
+ [index]: !prevState[index],
+ }));
+ break;
+ case 'reply':
+ setIsReplyThumbnailClicked((prevState) => ({
+ ...prevState,
+ [index]: !prevState[index],
+ }));
+ break;
+ case 'mobileThread':
+ setIsMobileThreadThumbnailClicked((prevState) => ({
+ ...prevState,
+ [index]: !prevState[index],
+ }));
+ break;
+ case 'mobileReply':
+ setIsMobileReplyThumbnailClicked((prevState) => ({
+ ...prevState,
+ [index]: !prevState[index],
+ }));
+ break;
+ default:
+ break;
+ }
+ };
+
+ useEffect(() => {
+ let permissions = {};
+
+ selectedFeed.forEach((thread) => {
+ const subplebbit = subplebbits.find((s) => s && s.address === thread.subplebbitAddress);
+
+ if (subplebbit && subplebbit.roles) {
+ const role = subplebbit.roles[account?.author.address]?.role;
+
+ if (role === 'moderator' || role === 'admin' || role === 'owner') {
+ permissions[thread.subplebbitAddress] = true;
+ } else {
+ permissions[thread.subplebbitAddress] = false;
+ }
+ }
+ });
+
+ setModeratorPermissions(permissions);
+ }, [account?.author.address, selectedFeed, subplebbits]);
+
+ const handleOptionClick = () => {
+ setOpenMenuCid(null);
+ };
+
+ const handleMobileOptionClick = () => {
+ setOpenMobileMenuCid(null);
+ };
+
+ const handleOutsideClick = useCallback(
+ (e) => {
+ if (openMenuCid !== null && !postMenuCatalogRef.current.contains(e.target)) {
+ setOpenMenuCid(null);
+ }
+ },
+ [openMenuCid, postMenuCatalogRef],
+ );
+
+ const handleMobileOutsideClick = useCallback(
+ (e) => {
+ if (openMobileMenuCid !== null && !postMenuMobileRef.current.contains(e.target)) {
+ setOpenMobileMenuCid(null);
+ }
+ },
+ [openMobileMenuCid, postMenuMobileRef],
+ );
+
+ useEffect(() => {
+ if (openMenuCid !== null) {
+ document.addEventListener('click', handleOutsideClick);
+ } else {
+ document.removeEventListener('click', handleOutsideClick);
+ }
+
+ return () => {
+ document.removeEventListener('click', handleOutsideClick);
+ };
+ }, [openMenuCid, handleOutsideClick]);
+
+ useEffect(() => {
+ if (openMobileMenuCid !== null) {
+ document.addEventListener('click', handleMobileOutsideClick);
+ } else {
+ document.removeEventListener('click', handleMobileOutsideClick);
+ }
+
+ return () => {
+ document.removeEventListener('click', handleMobileOutsideClick);
+ };
+ }, [openMobileMenuCid, handleMobileOutsideClick]);
+
+ useEffect(() => {
+ if (postOnHoverRef.current) {
+ const rect = postOnHoverRef.current.getBoundingClientRect();
+ setPostOnHoverHeight(rect.height);
+ }
+ }, [outOfViewCid]);
+
+ const errorString = useMemo(() => {
+ for (const subplebbit of subplebbits) {
+ if (subplebbit?.updatingState !== 'failed') {
+ return;
+ }
+ }
+ for (const subplebbit of subplebbits) {
+ if (subplebbit?.error) {
+ return `Failed fetching subplebbit: ${subplebbit?.error.toString().slice(0, 300)}`;
+ }
+ }
+ }, [subplebbits]);
+
+ useEffect(() => {
+ if (errorString) {
+ setNewErrorMessage(errorString);
+ }
+ }, [errorString, setNewErrorMessage]);
+
+ useEffect(() => {
+ setSelectedFeed(feed.sort((a, b) => b.timestamp - a.timestamp));
+ }, [feed]);
+
+ const flattenedRepliesByThread = useMemo(() => {
+ return selectedFeed.reduce((acc, thread) => {
+ const replies = flattenCommentsPages(thread.replies);
+ acc[thread.cid] = replies;
+ return acc;
+ }, {});
+ }, [selectedFeed]);
+
+ const allParentCids = useMemo(() => {
+ const allRepliesCids = Object.values(flattenedRepliesByThread).flatMap((replies) => replies.map((reply) => reply.cid));
+ const allThreadCids = selectedFeed.map((thread) => thread.cid);
+ return new Set([...allThreadCids, ...allRepliesCids]);
+ }, [flattenedRepliesByThread, selectedFeed]);
+
+ const filter = useCallback((accountComment) => allParentCids.has(accountComment.parentCid), [allParentCids]);
+
+ const { accountComments } = useAccountComments({ filter });
+
+ const filteredRepliesByThread = useMemo(() => {
+ const maxRepliesPerThread = 5;
+
+ const accountRepliesNotYetInCommentReplies = selectedFeed.reduce((acc, thread) => {
+ const replyCids = new Set(flattenedRepliesByThread[thread.cid].map((reply) => reply.cid));
+ acc[thread.cid] = accountComments.filter((accountReply) => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid);
+ return acc;
+ }, {});
+
+ return selectedFeed.reduce((acc, thread) => {
+ const combinedReplies = [...flattenedRepliesByThread[thread.cid], ...accountRepliesNotYetInCommentReplies[thread.cid]].sort((a, b) => a.timestamp - b.timestamp);
+ acc[thread.cid] = {
+ displayedReplies: combinedReplies.slice(0, maxRepliesPerThread),
+ omittedCount: Math.max(combinedReplies.length - maxRepliesPerThread, 0),
+ };
+ return acc;
+ }, {});
+ }, [flattenedRepliesByThread, accountComments, selectedFeed]);
+
+ const pendingReplyCounts = useMemo(() => {
+ return selectedFeed.reduce((acc, thread) => {
+ const replyCids = new Set(flattenedRepliesByThread[thread.cid].map((reply) => reply.cid));
+ acc[thread.cid] = accountComments.filter((accountReply) => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid).length;
+ return acc;
+ }, {});
+ }, [flattenedRepliesByThread, accountComments, selectedFeed]);
+
+ const allReplies = useMemo(() => {
+ return selectedFeed.flatMap((thread) => filteredRepliesByThread[thread.cid]?.displayedReplies || []);
+ }, [selectedFeed, filteredRepliesByThread]);
+
+ // let post.jsx access full cid of user-typed short cid
+ useEffect(() => {
+ const newCidTracker = {};
+ allReplies.forEach((reply) => {
+ newCidTracker[reply.shortCid] = reply.cid;
+ });
+ setCidTracker(newCidTracker);
+ }, [allReplies]);
+
+ // mobile navbar scroll effect
+ useEffect(() => {
+ const debouncedHandleScroll = debounce(() => {
+ const currentScrollPos = window.pageYOffset;
+ setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10);
+ setPrevScrollPos(currentScrollPos);
+ }, 50);
+
+ window.addEventListener('scroll', debouncedHandleScroll);
+
+ return () => window.removeEventListener('scroll', debouncedHandleScroll);
+ }, [prevScrollPos, visible]);
+
+ const tryLoadMore = async () => {
+ try {
+ await loadMore();
+ } catch (e) {
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+ };
+
+ const onChallengeVerification = (challengeVerification) => {
+ if (challengeVerification.challengeSuccess === true) {
+ setNewSuccessMessage('Challenge Success');
+ console.log('challenge success', challengeVerification);
+ } else if (challengeVerification.challengeSuccess === false) {
+ setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
+ console.log('challenge failed', challengeVerification);
+ }
+ };
+
+ const onChallenge = async (challenges, comment) => {
+ let challengeAnswers = [];
+ try {
+ challengeAnswers = await getChallengeAnswersFromUser(challenges);
+ } catch (error) {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ }
+ if (challengeAnswers) {
+ await comment.publishChallengeAnswers(challengeAnswers);
+ }
+ };
+
+ const getChallengeAnswersFromUser = async (challenges) => {
+ setChallengesArray(challenges);
+
+ return new Promise((resolve, reject) => {
+ const imageString = challenges?.challenges[0].challenge;
+ const imageSource = `data:image/png;base64,${imageString}`;
+ const challengeImg = new Image();
+ challengeImg.src = imageSource;
+
+ challengeImg.onload = () => {
+ setIsCaptchaOpen(true);
+
+ const handleKeyDown = async (event) => {
+ if (event.key === 'Enter') {
+ const currentCaptchaResponse = useGeneralStore.getState().captchaResponse;
+ resolve(currentCaptchaResponse);
+ setIsCaptchaOpen(false);
+ document.removeEventListener('keydown', handleKeyDown);
+ event.preventDefault();
+ }
+ };
+
+ setCaptchaResponse('');
+ document.addEventListener('keydown', handleKeyDown);
+
+ setResolveCaptchaPromise(resolve);
+ };
+
+ challengeImg.onerror = () => {
+ reject(setNewErrorMessage('Could not load challenges'));
+ };
+ });
+ };
+
+ const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
+ commentCid: commentCid,
+ content: editedComment || undefined,
+ subplebbitAddress: selectedAddress,
+ onChallenge,
+ onChallengeVerification,
+ onError: (error) => {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ },
+ });
+
+ useEffect(() => {
+ if (anonymousMode) {
+ setExecuteAnonMode(true);
+ }
+ }, [anonymousMode, selectedThreadCidRef]);
+
+ const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
+
+ const handleAuthorDeleteClick = (comment) => {
+ handleOptionClick(comment.cid);
+
+ confirmAlert({
+ customUI: ({ onClose }) => {
+ return (
+
+
+
Are you sure you want to delete this post?
+
+ No
+ {
+ setIsAuthorDelete(true);
+ setIsAuthorEdit(false);
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: comment.cid,
+ subplebbitAddress: comment.subplebbitAddress,
+ deleted: true,
+ }));
+ setTriggerPublishCommentEdit(true);
+ onClose();
+ }}
+ >
+ Yes
+
+
+
+
+ );
+ },
+ });
+ };
+
+ const handleAuthorEditClick = (comment) => {
+ handleOptionClick(comment.cid);
+ setIsAuthorEdit(true);
+ setIsAuthorDelete(false);
+ setCommentCid(comment.cid);
+ setOriginalCommentContent(comment.content);
+ setIsEditModalOpen(true);
+ };
+
+ useEffect(() => {
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: commentCid,
+ content: editedComment || undefined,
+ }));
+ }, [commentCid, editedComment]);
+
+ useEffect(() => {
+ if (editedComment !== '') {
+ setTriggerPublishCommentEdit(true);
+ }
+ }, [editedComment, setIsAuthorEdit]);
+
+ useEffect(() => {
+ let isActive = true;
+ if (publishCommentEditOptions && triggerPublishCommentEdit) {
+ (async () => {
+ await publishCommentEdit();
+ if (isActive) {
+ setTriggerPublishCommentEdit(false);
+ }
+ })();
+ }
+
+ return () => {
+ isActive = false;
+ };
+ }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]);
+
+ // desktop navbar board select functionality
+ const handleClickTitle = (title, address) => {
+ setSelectedTitle(title);
+ setSelectedAddress(address);
+ setSelectedFeed(feed.filter((feed) => feed.title === title));
+ };
+
+ // mobile navbar board select functionality
+ const handleSelectChange = (event) => {
+ const selected = event.target.value;
+
+ if (selected === 'subscriptions') {
+ navigate(`/p/subscriptions`);
+ return;
+ } else if (selected === 'all') {
+ navigate(`/p/all`);
+ return;
+ }
+
+ const selectedTitle = defaultSubplebbits?.find((subplebbit) => subplebbit.address === selected).title;
+ setSelectedTitle(selectedTitle);
+ setSelectedAddress(selected);
+ navigate(`/p/${selected}`);
+ };
+
+ useEffect(() => {
+ const setLastVirtuosoState = () => {
+ virtuosoRef.current?.getState((snapshot) => {
+ if (snapshot?.scrollTop === 0 || snapshot?.ranges?.length) {
+ lastVirtuosoStates['subscriptions'] = snapshot;
+ }
+ });
+ };
+ window.addEventListener('scroll', setLastVirtuosoState);
+
+ return () => window.removeEventListener('scroll', setLastVirtuosoState);
+ }, []);
+
+ const lastVirtuosoState = lastVirtuosoStates['subscriptions'];
+
+ return (
+ <>
+
+ Subscriptions - plebchan
+
+
+ setIsCreateBoardOpen(false)} />
+ setIsReplyOpen(false)} />
+ setIsSettingsOpen(false)} />
+ {
+ setIsModerationOpen(false);
+ setDeletePost(false);
+ }}
+ deletePost={deletePost}
+ />
+ setIsEditModalOpen(false)} originalCommentContent={originalCommentContent} />
+
+ <>
+
+ [
+ window.scrollTo(0, 0)}>
+ All
+
+ /
+ window.scrollTo(0, 0)}>
+ Subscriptions
+
+ ] [
+ {defaultSubplebbits.map((subplebbit, index) => (
+
+ {index === 0 ? null : '\u00a0'}
+ handleClickTitle(subplebbit.title, subplebbit.address)}>
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ {index !== defaultSubplebbits.length - 1 ? ' /' : null}
+
+ ))}
+ ]
+
+
+ [
+ {
+ window.electron && window.electron.isElectron
+ ? setIsCreateBoardOpen(true)
+ : alert(
+ 'You can create a board with the desktop version of plebchan:\nhttps://github.com/plebbit/plebchan/releases/latest\n\nIf you are comfortable with the command line, use plebbit-cli:\nhttps://github.com/plebbit/plebbit-cli\n\n',
+ );
+ }}
+ >
+ Create Board
+
+ ] [
+ setIsSettingsOpen(true)}>
+ Settings
+
+ ] [ Home]
+
+
+
+
+ Board
+
+
+ All
+ Subscriptions
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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:
+
+ Yotsuba
+ Yotsuba B
+ Futaba
+ Burichan
+ Tomorrow
+ Photon
+
+
+
+ [ Catalog]
+
+ {subplebbits.state === 'succeeded' ? null : (
+
+ {stateString}
+
+ )}
+
+
+ Catalog
+
+
+
+
+
+
+ {!feed ? null : (
+
{
+ if (editedComments[thread.cid]) {
+ thread = editedComments[thread.cid];
+ if (thread.removed) {
+ thread.content = '[removed]';
+ thread.link = undefined;
+ }
+ }
+ const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {};
+ const commentMediaInfo = getCommentMediaInfo(thread);
+ const fallbackImgUrl = 'assets/filedeleted-res.gif';
+ const isModerator = moderatorPermissions[thread.subplebbitAddress];
+ let displayWidth, displayHeight, displayWidthMobile, displayHeightMobile;
+ if (thread.linkWidth && thread.linkHeight) {
+ let scale = Math.min(1, 250 / Math.max(thread.linkWidth, thread.linkHeight));
+ displayWidth = `${thread.linkWidth * scale}px`;
+ displayHeight = `${thread.linkHeight * scale}px`;
+
+ scale = Math.min(1, 100 / Math.max(thread.linkWidth, thread.linkHeight));
+ displayWidthMobile = `${thread.linkWidth * scale}px`;
+ displayHeightMobile = `${thread.linkHeight * scale}px`;
+ } else {
+ displayWidth = '250px';
+ displayHeight = '250px';
+ displayWidthMobile = '100px';
+ displayHeightMobile = '100px';
+ }
+ return (
+
+
+
+
+
+
+ {commentMediaInfo?.url ? (
+
+
+ {commentMediaInfo?.type === 'iframe' && (
+
+
+ {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
+
+ ) : commentMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {commentMediaInfo?.type === 'webpage' ? (
+
+
+ {thread.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'video' ? (
+
+
+ {isThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ ) : null}
+
+ ) : null}
+
+ {thread.title ? (
+ thread.title.length > 75 ? (
+
+
+ {thread.title.slice(0, 75) + ' (...)'}
+
+
+ ) : (
+
+ {thread.title}
+
+ )
+ ) : null}
+
+ {thread.author.displayName ? (
+ thread.author.displayName.length > 20 ? (
+
+
+ {thread.author.displayName.slice(0, 20) + ' (...)'}
+
+
+ ) : (
+
+ {thread.author.displayName}
+
+ )
+ ) : (
+
+ Anonymous
+
+ )}
+ (u/
+
+ handleAddressClick(
+ {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} ,
+ )
+ }
+ >
+ {{({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} }
+
+ )
+
+ {getDate(thread.timestamp)}
+
+
+
+ c/
+ {
+ if (e.button === 2) return;
+ e.preventDefault();
+ setSelectedThreadCid(thread.cid);
+ setSelectedAddress(thread.subplebbitAddress);
+ let text = document.getSelection().toString();
+ text = text ? `>${text}` : text;
+ setSelectedText(text);
+ if (isReplyOpen) {
+ setReplyQuoteCid(thread.shortCid);
+ setTriggerInsertion(Date.now());
+ } else {
+ setIsReplyOpen(true);
+ setSelectedShortCid(thread.shortCid);
+ setSelectedParentCid(thread.cid);
+ }
+ }}
+ title='Reply to this post'
+ >
+ {thread.shortCid}
+
+ p/
+
+ {thread.subplebbitAddress.includes('.eth') ? (
+ thread.subplebbitAddress
+ ) : (
+
+ {thread.subplebbitAddress.slice(0, 10) + '(...)'}
+
+ )}
+
+
+
+ [
+ setSelectedThread(thread.cid)}
+ className='reply-link'
+ >
+ Reply
+
+ ]
+
+
+
+ {
+ threadMenuRefs.current[thread.cid] = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === thread.cid}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current[thread.cid].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === thread.cid ? null : thread.cid));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {
+ handleOptionClick(thread.cid);
+ handleShareClick(thread.subplebbitAddress, thread.cid);
+ }}
+ >
+ Share thread
+
+
+ {({ authorAddress }) => (
+ <>
+ {authorAddress === account?.author.address || authorAddress === account?.signer.address ? (
+ <>
+ handleAuthorEditClick(thread)}>Edit post
+ handleAuthorDeleteClick(thread)}>Delete post
+ >
+ ) : null}
+ {isModerator ? (
+ <>
+ {authorAddress === account?.author.address || authorAddress === account?.signer.address ? null : (
+ {
+ setSelectedAddress(thread.subplebbitAddress);
+ setModeratingCommentCid(thread.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(thread.cid);
+ setDeletePost(true);
+ }}
+ >
+ Delete post
+
+ )}
+ {
+ setSelectedAddress(thread.subplebbitAddress);
+ setModeratingCommentCid(thread.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(thread.cid);
+ }}
+ >
+ Mod tools
+
+ >
+ ) : null}
+ >
+ )}
+
+ {commentMediaInfo &&
+ (commentMediaInfo.type === 'image' || (commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail)) ? (
+ {
+ setIsImageSearchOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsImageSearchOpen(false);
+ }}
+ >
+ Image search »
+
+
+ ) : null}
+ {
+ setIsClientRedirectMenuOpen(true);
+ }}
+ onMouseLeave={() => {
+ setIsClientRedirectMenuOpen(false);
+ }}
+ >
+ View on »
+
+ handleOptionClick(thread.cid)}>
+
+ Plebbit
+
+
+ {/* handleOptionClick(thread.cid)}>
Seedit
*/}
- handleOptionClick(thread.cid)}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
- {thread.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}} className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- setOutOfViewCid(reply.cid);
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(thread.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {thread.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefs.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/{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 »
-
- 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
- )}
-
- {reply.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}} className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- setOutOfViewCid(reply.cid);
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(reply.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefs.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/{reply.shortCid}
-
-
- ))
- }
-
-
- {replyMediaInfo?.url ? (
-
-
- {replyMediaInfo?.type === 'iframe' && (
-
-
- {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
-
- ) : replyMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {replyMediaInfo?.type === "webpage" && (
-
-
- {reply.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {replyMediaInfo?.type === "image" && (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- )}
- {replyMediaInfo?.type === "video" && (
-
-
- {isReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- )}
- {replyMediaInfo?.type === "audio" && (
-
- e.target.src = fallbackImgUrl} />
-
- )}
-
- ) : 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 ? (
+
+
+ {replyMediaInfo?.type === 'iframe' && (
+
+
+ {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
+
+ ) : replyMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {replyMediaInfo?.type === 'webpage' && (
+
+
+ {reply.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {replyMediaInfo?.type === 'image' && (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ )}
+ {replyMediaInfo?.type === 'video' && (
+
+
+ {isReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ )}
+ {replyMediaInfo?.type === 'audio' && (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ )}
+
+ ) : null}
+ {reply.content ? (
+ reply.content?.length > 500 ? (
+
+
+ {}}
+ key={`r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefs.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefs.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
- if (distanceToTop < postOnHoverHeight / 2) {
- top = window.scrollY - 5;
- } else if (distanceToBottom < postOnHoverHeight / 2) {
- top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
- } else {
- top = rect.top + window.scrollY - postOnHoverHeight / 2;
- }
-
- if (distanceToRight < 200) {
- setOutOfViewPosition({
- top,
- right: window.innerWidth - rect.left - 10,
- maxWidth: rect.left - 5
- });
- } else {
- setOutOfViewPosition({
- top,
- left: rect.left + rect.width + 5,
- maxWidth: window.innerWidth - rect.left - rect.width - 5
- });
- }
- });
- }}
- onMouseLeave={() => {
- removeHighlight();
- setOutOfViewCid(null);
- }}>
- {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null}
-
- {
- postRefs.current[quoteShortParentCid] = ref;
- }}
- postQuoteOnClick={(quoteShortParentCid) => {
- handleQuoteClick(reply, quoteShortParentCid, null)
- }}
- postQuoteOnOver={(quoteShortParentCid) => {
- const quoteParentCid = cidTracker[quoteShortParentCid];
- if (outOfViewCid !== quoteParentCid) {
- handleQuoteHover(reply, quoteShortParentCid, () => {
- setOutOfViewCid(quoteParentCid);
-
- const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
-
- if (distanceToTop < postOnHoverHeight / 2) {
- top = window.scrollY - 5;
- } else if (distanceToBottom < postOnHoverHeight / 2) {
- top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
- } else {
- top = rect.top + window.scrollY - postOnHoverHeight / 2;
- }
-
- if (distanceToRight < 200) {
- setOutOfViewPosition({
- top,
- right: window.innerWidth - rect.left - 10,
- maxWidth: rect.left - 5
- });
- } else {
- setOutOfViewPosition({
- top,
- left: rect.left + rect.width + 5,
- maxWidth: window.innerWidth - rect.left - rect.width - 5
- });
- }
- })
- }}
- }
- postQuoteOnLeave={() => {
- removeHighlight();
- setOutOfViewCid(null);
- }}
- />
-
-
- )
- : null}
-
-
- )
- })}
-
-
- {index === 0 ? (
-
- ) : (
-
- )}
-
-
-
-
{
- threadMenuRefsMobile.current[thread.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current[thread.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === thread.cid ? null : thread.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {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 ? (
- {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 ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "video" ? (
-
- {isMobileThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
- {commentMediaInfo?.type === "video" || commentMediaInfo?.type === "iframe" ? (
- isMobileThreadThumbnailClicked[index] ? (
-
- {handleThumbnailClick(index, 'mobileThread')}}
- >Close
-
- ) : (
- video
- )) : video
}
-
- ) : commentMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
- ) : 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 (
-
-
-
-
{
- replyMenuRefsMobile.current[reply.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === reply.cid ? null : reply.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {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 ? (
- {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 ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "video" ? (
-
- {isMobileReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
- {replyMediaInfo?.type === "video" || replyMediaInfo?.type === "iframe" ? (
- isMobileReplyThumbnailClicked[index] ? (
-
- {handleThumbnailClick(index, 'mobileReply')}}
- >Close
-
- ) : (
- video
- )) : video
}
-
- ) : replyMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
- ) : 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 (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 (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
- });
- }
- });
- }}
- 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;
+ 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);
- }}
- 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)}
-
-
-
- >
- );
-}
+ 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 Subscriptions;
\ 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,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+ {' '}
+ (...)
+
+
+
+
+ Comment too long.
+ setSelectedThread(thread.cid)}
+ className='ttl-link'
+ >
+ Click here
+
+ to view.{' '}
+
+
+
+ ) : (
+
+ {}}
+ key={`r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefs.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefs.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+
+
+ )
+ ) : null}
+
+
+ );
+ })}
+
+
+ {index === 0 ?
:
}
+
+
+
+
{
+ threadMenuRefsMobile.current[thread.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current[thread.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === thread.cid ? null : thread.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {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 ? (
+ {
+ 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 ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'video' ? (
+
+ {isMobileThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+ {commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'iframe' ? (
+ isMobileThreadThumbnailClicked[index] ? (
+
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ >
+ Close
+
+
+ ) : (
+
+ video
+
+ )
+ ) : (
+
+ video
+
+ )}
+
+ ) : commentMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ {commentMediaInfo?.type}
+
+
+ ) : 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 (
+
+
+
+
{
+ replyMenuRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === reply.cid ? null : reply.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {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 ? (
+ {
+ 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 ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'video' ? (
+
+ {isMobileReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+ {replyMediaInfo?.type === 'video' || replyMediaInfo?.type === 'iframe' ? (
+ isMobileReplyThumbnailClicked[index] ? (
+
+ {
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ >
+ Close
+
+
+ ) : (
+
+ video
+
+ )
+ ) : (
+
+ video
+
+ )}
+
+ ) : replyMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ {replyMediaInfo.type}
+
+
+ ) : null
+ ) : null}
+
+ ) : null}
+ {reply.content ? (
+ reply.content?.length > 500 ? (
+
+
+ {}}
+ key={`mob-r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+ {' '}
+ (...)
+
+
+
+
+ Comment too long.
+ setSelectedThread(thread.cid)}
+ className='ttl-link'
+ >
+ Click here
+
+ to view.{' '}
+
+
+
+ ) : (
+
+ {}}
+ key={`mob-r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => handleQuoteClick(reply, shortParentCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, shortParentCid, () => {
+ setOutOfViewCid(reply.parentCid);
+ const rect = quoteRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ >
+ {`c/${shortParentCid}`}
+ {shortParentCid === thread.shortCid ? ' (OP)' : null}
+
+ {
+ postRefs.current[quoteShortParentCid] = ref;
+ }}
+ postQuoteOnClick={(quoteShortParentCid) => {
+ handleQuoteClick(reply, quoteShortParentCid, null);
+ }}
+ postQuoteOnOver={(quoteShortParentCid) => {
+ const quoteParentCid = cidTracker[quoteShortParentCid];
+ if (outOfViewCid !== quoteParentCid) {
+ handleQuoteHover(reply, quoteShortParentCid, () => {
+ setOutOfViewCid(quoteParentCid);
+
+ const rect = postRefs.current[quoteShortParentCid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+
+
+ )
+ ) : null}
+ {reply.replyCount > 0 ? (
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefsMobile.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ onClick={(event) => handleQuoteClick(reply, reply.shortCid, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, reply.shortCid, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefsMobile.current[reply.cid].getBoundingClientRect();
+ const distanceToRight = window.innerWidth - rect.right;
+ const distanceToTop = rect.top;
+ const distanceToBottom = window.innerHeight - rect.bottom;
+ let top;
+
+ if (distanceToTop < postOnHoverHeight / 2) {
+ top = window.scrollY - 5;
+ } else if (distanceToBottom < postOnHoverHeight / 2) {
+ top = window.scrollY - postOnHoverHeight + window.innerHeight - 10;
+ } else {
+ top = rect.top + window.scrollY - postOnHoverHeight / 2;
+ }
+
+ if (distanceToRight < 200) {
+ setOutOfViewPosition({
+ top,
+ right: window.innerWidth - rect.left - 10,
+ maxWidth: rect.left - 5,
+ });
+ } else {
+ setOutOfViewPosition({
+ top,
+ left: rect.left + rect.width + 5,
+ maxWidth: window.innerWidth - rect.left - rect.width - 5,
+ });
+ }
+ });
+ }}
+ onMouseLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ className='quote-link'
+ >
+ c/{reply.shortCid}
+
+
+
+ ))}
+
+ ) : null}
+
+
+ );
+ })}
+
+
+ );
+ }}
+ ref={virtuosoRef}
+ restoreStateFrom={lastVirtuosoState}
+ initialScrollTop={lastVirtuosoState?.scrollTop}
+ endReached={tryLoadMore}
+ useWindowScroll={true}
+ components={{
+ Footer: () => {
+ return isFeedCached ? null : ;
+ },
+ }}
+ />
+ )}
+
+ {outOfViewCid &&
+ outOfViewPosition &&
+ createPortal(
+ ,
+ document.body,
+ )}
+
+
+
+ >
+ );
+};
+
+export default Subscriptions;
diff --git a/src/components/views/SubscriptionsCatalog.jsx b/src/components/views/SubscriptionsCatalog.jsx
index 36709181..d159a1d8 100755
--- a/src/components/views/SubscriptionsCatalog.jsx
+++ b/src/components/views/SubscriptionsCatalog.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,980 +31,1066 @@ import useGeneralStore from '../../hooks/stores/useGeneralStore';
import useSuccess from '../../hooks/useSuccess';
import useWindowWidth from '../../hooks/useWindowWidth';
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`;
+ }
- 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]);
+ 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 handleOutsideClick = useCallback((e) => {
- if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) {
- setOpenMenuCid(null);
- }
- }, [openMenuCid]);
+ const handleMouseOnLeaveThread = () => {
+ if (hoverTimeoutId) {
+ clearTimeout(hoverTimeoutId);
+ setHoverTimeoutId(null);
+ setIsHoveringOnThread('');
+ } else if (isHoveringOnThread !== '') {
+ setIsHoveringOnThread('');
+ setIsCalculationDone(false);
+ }
+ };
- const handleOptionClick = () => {
- setOpenMenuCid(null);
- };
+ useEffect(() => {
+ if (subplebbit.roles !== undefined) {
+ const role = subplebbit.roles[account?.author.address]?.role;
- useEffect(() => {
- if (openMenuCid !== null) {
- document.addEventListener('click', handleOutsideClick);
- } else {
- document.removeEventListener('click', handleOutsideClick);
- }
-
- return () => {
- document.removeEventListener('click', handleOutsideClick);
- };
- }, [openMenuCid, handleOutsideClick]);
+ if (role === 'moderator' || role === 'admin' || role === 'owner') {
+ setIsModerator(true);
+ } else {
+ setIsModerator(false);
+ }
+ }
+ }, [account?.author.address, subplebbit.roles]);
- const handleAuthorDeleteClick = (comment) => {
- handleOptionClick(comment.cid);
+ const handleOutsideClick = useCallback(
+ (e) => {
+ if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) {
+ setOpenMenuCid(null);
+ }
+ },
+ [openMenuCid],
+ );
- confirmAlert({
- customUI: ({ onClose }) => {
- return (
-
-
-
Are you sure you want to delete this post?
-
- No
- {
- setIsAuthorDelete(true);
- setIsAuthorEdit(false);
- setPublishCommentEditOptions(prevOptions => ({
- ...prevOptions,
- commentCid: comment.cid,
- subplebbitAddress: comment.subplebbitAddress,
- deleted: true,
- }));
- setTriggerPublishCommentEdit(true);
- onClose();
- }}
- >
- Yes
-
-
-
-
- );
- }
- });
- };
+ const handleOptionClick = () => {
+ setOpenMenuCid(null);
+ };
+ useEffect(() => {
+ if (openMenuCid !== null) {
+ document.addEventListener('click', handleOutsideClick);
+ } else {
+ document.removeEventListener('click', handleOutsideClick);
+ }
- const handleAuthorEditClick = (comment) => {
- handleOptionClick(comment.cid);
- setIsAuthorEdit(true);
- setIsAuthorDelete(false);
- setCommentCid(comment.cid);
- setOriginalCommentContent(comment.content);
- setIsEditModalOpen(true);
- }
+ 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?
+
+ No
+ {
+ setIsAuthorDelete(true);
+ setIsAuthorEdit(false);
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: comment.cid,
+ subplebbitAddress: comment.subplebbitAddress,
+ deleted: true,
+ }));
+ setTriggerPublishCommentEdit(true);
+ onClose();
+ }}
+ >
+ Yes
+
+
+
+
+ );
+ },
+ });
+ };
- 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 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 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);
- 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();
- }
- };
+ 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;
- setCaptchaResponse('');
- document.addEventListener('keydown', handleKeyDown);
+ challengeImg.onload = () => {
+ setIsCaptchaOpen(true);
- setResolveCaptchaPromise(resolve);
- };
-
- challengeImg.onerror = () => {
- reject(setNewErrorMessage('Could not load challenges'));
- };
- });
- };
+ const handleKeyDown = async (event) => {
+ if (event.key === 'Enter') {
+ const currentCaptchaResponse = captchaResponse;
+ resolve(currentCaptchaResponse);
+ setIsCaptchaOpen(false);
+ document.removeEventListener('keydown', handleKeyDown);
+ event.preventDefault();
+ }
+ };
- 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);
+ setCaptchaResponse('');
+ document.addEventListener('keydown', handleKeyDown);
+ setResolveCaptchaPromise(resolve);
+ };
- useEffect(() => {
- setPublishCommentEditOptions((prevOptions) => ({
- ...prevOptions,
- commentCid: commentCid,
- content: editedComment || undefined,
- }));
- }, [commentCid, editedComment]);
+ challengeImg.onerror = () => {
+ reject(setNewErrorMessage('Could not load challenges'));
+ };
+ });
+ };
+ const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
+ commentCid: commentCid,
+ content: editedComment || undefined,
+ subplebbitAddress: selectedAddress,
+ onChallenge,
+ onChallengeVerification,
+ onError: (error) => {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ },
+ });
- useEffect(() => {
- if (editedComment !== '') {
- setTriggerPublishCommentEdit(true);
- }
- }, [editedComment, setIsAuthorEdit]);
+ const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
-
- useEffect(() => {
- let isActive = true;
- if (publishCommentEditOptions && triggerPublishCommentEdit) {
- (async () => {
- await publishCommentEdit();
- if (isActive) {
- setTriggerPublishCommentEdit(false);
- }
- })();
- }
+ useEffect(() => {
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: commentCid,
+ content: editedComment || undefined,
+ }));
+ }, [commentCid, editedComment]);
- return () => {
- isActive = false;
- };
- }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]);
+ useEffect(() => {
+ if (editedComment !== '') {
+ setTriggerPublishCommentEdit(true);
+ }
+ }, [editedComment, setIsAuthorEdit]);
+ useEffect(() => {
+ let isActive = true;
+ if (publishCommentEditOptions && triggerPublishCommentEdit) {
+ (async () => {
+ await publishCommentEdit();
+ if (isActive) {
+ setTriggerPublishCommentEdit(false);
+ }
+ })();
+ }
- 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 () => {
+ 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';
+ }
- 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 ? (
-
- {
- e.target.src = fallbackImgUrl
- e.target.onerror = null;
- }} />
-
- ) : null
- ) : null}
- {commentMediaInfo?.type === "iframe" && (thread.thumbnailUrl || commentMediaInfo.thumbnail) ? (
-
- { e.target.src = fallbackImgUrl }} />
-
- ) : 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}
-
- {isMediaShowed ? (
-
- ) : (
- 0?"offline-icon-no-link-shifted": "offline-icon-no-link"} ${isHoveringOnThread === thread.cid ? 'offline-icon-no-link-hovered' : ''}`}
- tooltipPlace="top" />
- )}
-
- {
- setIsHoveringForMenu(thread.cid);
- handleMouseOnLeaveThread();
- }}
- onMouseLeave={() => setIsHoveringForMenu(false)}
- style={{ all: "unset"}}>
-
- R:
{thread.replyCount}
- {linkCount > 0 ? (
- <>
- /
- L:
{linkCount}
- >
- ) : null}
-
{
- threadMenuRefs.current[thread.cid] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- id='post-menu-button-catalog'
- 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(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
- ) : (
- {
- 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)}>
+ 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 ? (
+
+ {
+ e.target.src = fallbackImgUrl;
+ e.target.onerror = null;
+ }}
+ />
+
+ ) : null
+ ) : null}
+ {commentMediaInfo?.type === 'iframe' && (thread.thumbnailUrl || commentMediaInfo.thumbnail) ? (
+
+ {
+ e.target.src = fallbackImgUrl;
+ }}
+ />
+
+ ) : 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}
+
+ {isMediaShowed ? (
+
+ ) : (
+ 0 ? 'offline-icon-no-link-shifted' : 'offline-icon-no-link'} ${
+ isHoveringOnThread === thread.cid ? 'offline-icon-no-link-hovered' : ''
+ }`}
+ tooltipPlace='top'
+ />
+ )}
+
+ {
+ setIsHoveringForMenu(thread.cid);
+ handleMouseOnLeaveThread();
+ }}
+ onMouseLeave={() => setIsHoveringForMenu(false)}
+ style={{ all: 'unset' }}
+ >
+
+ R:
{thread.replyCount}
+ {linkCount > 0 ? (
+ <>
+ / L:
{linkCount}
+ >
+ ) : null}
+
{
+ threadMenuRefs.current[thread.cid] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ id='post-menu-button-catalog'
+ 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(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 : (
+ {
+ 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
- )}
-
- setSelectedThread(thread.cid)}>
-
- {
- 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)}>
- {thread.title ? `${thread.title}` : null}
- {thread.content ? `: ${thread.content}` : null}
-
-
-
-
- >
- );
+ handleOptionClick(thread.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ setSelectedThread(thread.cid)}
+ >
+
+ {
+ 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)}
+ >
+ {thread.title ? `${thread.title}` : null}
+ {thread.content ? `: ${thread.content}` : null}
+
+
+
+
+ >
+ );
};
-
-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 SubscriptionsCatalog = () => {
- 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 account = useAccount();
- const navigate = useNavigate();
+ const account = useAccount();
+ 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 { 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 { 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 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);
- // 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]);
+ 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));
- };
+ // 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;
+ // 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;
- }
+ 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 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);
- 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]);
- return () => window.removeEventListener('scroll', setLastVirtuosoState);
- }, [selectedAddress]);
-
+ const lastVirtuosoState = lastVirtuosoStates['subscriptionsCatalog'];
- 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
+
+
+ All
+ Subscriptions
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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:
+
+ Yotsuba
+ Yotsuba B
+ Futaba
+ Burichan
+ Tomorrow
+ Photon
+
+
+
+ [ 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}
+
+
+
+ >
+ );
+};
-
- 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
-
-
- All
- Subscriptions
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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:
-
-
- Yotsuba
- Yotsuba B
- Futaba
- Burichan
- Tomorrow
- Photon
-
-
-
- [
- 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;
\ No newline at end of file
+export default SubscriptionsCatalog;
diff --git a/src/components/views/Thread.jsx b/src/components/views/Thread.jsx
index 4c805107..7c310ee0 100755
--- a/src/components/views/Thread.jsx
+++ b/src/components/views/Thread.jsx
@@ -5,7 +5,7 @@ import { Link, useNavigate, useParams } from 'react-router-dom';
import { confirmAlert } from 'react-confirm-alert';
import { Tooltip } from 'react-tooltip';
import { useAccount, useAccountComments, useComment, usePublishComment, usePublishCommentEdit, useSubplebbit } 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, PostForm, PostFormTable, PostMenu, PostMenuMobile } from '../styled/views/Board.styled';
import { ReplyFormLink, TopBar, BottomBar, BoardForm, Footer } from '../styled/views/Thread.styled';
@@ -46,2711 +46,3087 @@ 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;
const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : '';
-
const Thread = () => {
- const {
- captchaResponse, setCaptchaResponse,
- setChallengesArray,
- defaultSubplebbits,
- editedComment,
- editedComments,
- setIsAuthorDelete,
- setIsAuthorEdit,
- setIsCaptchaOpen,
- isModerationOpen, setIsModerationOpen,
- isSettingsOpen, setIsSettingsOpen,
- setModeratingCommentCid,
- setReplyQuoteCid,
- setResolveCaptchaPromise,
- setPendingComment,
- setPendingCommentIndex,
- setSelectedAddress,
- setSelectedParentCid,
- setSelectedShortCid,
- selectedStyle,
- selectedThread, setSelectedThread,
- setSelectedText,
- selectedTitle, setSelectedTitle,
- showPostForm,
- showPostFormLink,
- setTriggerInsertion,
- } = useGeneralStore(state => state);
-
- const { anonymousMode } = useAnonModeStore();
-
- const account = useAccount();
- const navigate = useNavigate();
- const handleClickForm = useClickForm();
-
- const [, setNewErrorMessage] = useError();
- const [, setNewSuccessMessage] = useSuccess();
-
- const nameRef = useRef();
- const commentRef = useRef();
- const linkRef = useRef();
- const threadMenuRefs = useRef({});
- const threadMenuRefsMobile = useRef({});
- const replyMenuRefs = useRef({});
- const replyMenuRefsMobile = useRef({});
- const postMenuRef = useRef(null);
- const postMenuMobileRef = useRef(null);
- const postMenuCatalogRef = useRef(null);
- const backlinkRefs = useRef({});
- const quoteRefs = useRef({});
- const postRefs = useRef({});
- const postOnHoverRef = useRef(null);
- const backlinkRefsMobile = useRef({});
- const quoteRefsMobile = useRef({});
-
- const [triggerPublishComment, setTriggerPublishComment] = useState(false);
- const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false);
- const [deletePost, setDeletePost] = 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 [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);
-
-
- useAnonMode(selectedThread, anonymousMode && executeAnonMode);
-
- const originalComment = useComment({commentCid: selectedThread});
- const [comment, setComment] = useState(originalComment);
- const { subplebbitAddress, threadCid } = useParams();
- const subplebbit = useSubplebbit({subplebbitAddress: originalComment.subplebbitAddress});
- const selectedAddress = subplebbit.address;
-
- const stateString = useStateString(comment);
-
- const commentMediaInfo = getCommentMediaInfo(comment);
- const fallbackImgUrl = "assets/filedeleted-res.gif";
-
-
- useEffect(() => {
- let updatedComment = { ...originalComment };
-
- if (editedComments[originalComment.cid]) {
- updatedComment = { ...originalComment, ...editedComments[originalComment.cid] };
-
- if (updatedComment.removed) {
- updatedComment.content = "[removed]";
- updatedComment.link = undefined;
- }
- }
-
- setComment(updatedComment);
- }, [originalComment, editedComments]);
-
-
- 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(() => {
- 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 handleOptionClick = () => {
- setOpenMenuCid(null);
- };
-
- const handleMobileOptionClick = () => {
- setOpenMobileMenuCid(null);
- };
-
- const handleOutsideClick = useCallback((e) => {
- if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) {
- setOpenMenuCid(null);
- }
- }, [openMenuCid, postMenuRef, 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]);
-
-
- useEffect(() => {
- window.scrollTo(0, 0);
- }, []);
-
-
- useEffect(() => {
- if ( comment.state === "failed" && selectedAddress === undefined) {
- navigate('/404');
- }
- }, [selectedAddress, navigate, comment.state]);
-
-
- const errorString = useMemo(() => {
- if (comment?.state === 'failed') {
- let errorString = 'Failed fetching thread.'
- if (comment.error) {
- errorString += `: ${comment.error.toString().slice(0, 300)}`
- }
- return errorString
- }
- }, [comment?.state, comment?.error])
-
-
- useEffect(() => {
- if (errorString) {
- setNewErrorMessage(errorString);
- }
- }, [errorString, setNewErrorMessage]);
-
-
- const flattenedReplies = useMemo(() =>
- flattenCommentsPages(comment.replies), [comment.replies]
- );
-
-
- const threadAndRepliesCids = useMemo(() => new Set([(selectedThread || 'n/a'), ...flattenedReplies.map(reply => reply.cid)]), [selectedThread, flattenedReplies]);
-
- const filter = useCallback((accountComment) => threadAndRepliesCids.has(accountComment.parentCid), [threadAndRepliesCids]);
-
-
- const { accountComments } = useAccountComments({filter});
-
-
- const accountRepliesNotYetInCommentReplies = useMemo(() => {
- const commentReplyCids = new Set(flattenedReplies.map(reply => reply.cid))
- return accountComments.filter(accountReply => {
- return !commentReplyCids.has(accountReply.cid) && accountReply.parentCid === selectedThread;
- });
- }, [flattenedReplies, accountComments, selectedThread]);
-
-
- const sortedReplies = useMemo(() => [
- ...accountRepliesNotYetInCommentReplies, ...flattenedReplies
- ].sort((a, b) => a.timestamp - b.timestamp
- ), [accountRepliesNotYetInCommentReplies, flattenedReplies]);
-
- // let post.jsx access full cid of user-typed short cid
- useEffect(() => {
- const newCidTracker = {};
- sortedReplies.forEach((reply) => {
- newCidTracker[reply.shortCid] = reply.cid;
- });
- setCidTracker(newCidTracker);
- }, [sortedReplies]);
-
-
- useEffect(() => {
- const selectedSubplebbit = defaultSubplebbits.find((subplebbit) => subplebbit.address === subplebbitAddress);
- if (threadCid) {
- setSelectedThread(threadCid);
- }
- if (subplebbitAddress) {
- setSelectedAddress(subplebbitAddress);
- } else if (subplebbit?.address) {
- setSelectedAddress(subplebbit.address)
- }
- if (selectedSubplebbit) {
- setSelectedTitle(selectedSubplebbit.title);
- } else if (subplebbit?.title) {
- setSelectedTitle(subplebbit.title);
- }
- }, [subplebbitAddress, threadCid, defaultSubplebbits, subplebbit, setSelectedThread, setSelectedAddress, setSelectedTitle]);
-
- // 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 onChallengeVerification = (challengeVerification) => {
- if (challengeVerification.challengeSuccess === true) {
- if (challengeVerification.publication?.cid !== undefined) {
- return;
- } 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: comment.subplebbitAddress,
- }));
- }, [comment.subplebbitAddress]);
-
-
- const [publishCommentOptions, setPublishCommentOptions] = useState({
- subplebbitAddress: comment.subplebbitAddress,
- onChallenge,
- onChallengeVerification,
- onError: (error) => {
- setNewErrorMessage(error.message); console.log(error);
- },
- });
-
-
- const { publishComment, index } = usePublishComment(publishCommentOptions);
-
-
- useEffect(() => {
- if (index !== undefined) {
- setPendingCommentIndex(index);
- }
- }, [index, setPendingCommentIndex]);
-
-
- const resetFields = useCallback(() => {
- if (nameRef.current) {
- nameRef.current.value = '';
- }
- if (commentRef.current) {
- commentRef.current.value = '';
- }
- if (linkRef.current) {
- linkRef.current.value = '';
- }
- }, []);
-
-
- const handleSubmit = async (event) => {
- event.preventDefault();
-
- if (
- commentRef.current.value === "" &&
- linkRef.current.value === ""
- ) {
- setNewErrorMessage("Please enter a comment or link.");
- return;
- }
-
-
- setPublishCommentOptions((prevPublishCommentOptions) => ({
- ...prevPublishCommentOptions,
- author: {
- displayName: nameRef.current.value || undefined,
- ...(anonymousMode ? {} : {address: account?.author.address}),
- },
- content: commentRef.current.value || undefined,
- link: linkRef.current.value || undefined,
- parentCid: selectedThread,
- }));
-
- setTriggerPublishComment(true);
- };
-
-
- const updateSigner = useCallback(async () => {
- if (anonymousMode) {
- setExecuteAnonMode(true);
-
- let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
- let signer;
-
- if (!storedSigners[selectedThread]) {
- signer = await account?.plebbit.createSigner();
- storedSigners[selectedThread] = { privateKey: signer?.privateKey, address: signer?.address };
- localStorage.setItem('storedSigners', JSON.stringify(storedSigners));
- } else {
- const signerPrivateKey = storedSigners[selectedThread].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;
- });
- }
- }, [selectedThread, 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 = 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: comment.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?
-
- No
- {
- setIsAuthorDelete(true);
- setIsAuthorEdit(false);
- setPublishCommentEditOptions(prevOptions => ({
- ...prevOptions,
- commentCid: comment.cid,
- subplebbitAddress: comment.subplebbitAddress,
- deleted: true,
- }));
- setTriggerPublishCommentEdit(true);
- onClose();
- }}
- >
- Yes
-
-
-
-
- );
- }
- });
- };
-
- 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]);
-
-
- useEffect(() => {
- let isActive = true;
- if (publishCommentEditOptions && triggerPublishCommentEdit) {
- (async () => {
- await publishCommentEdit();
- if (isActive) {
- setTriggerPublishCommentEdit(false);
- }
- })();
- }
-
- return () => {
- isActive = false;
- };
- }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]);
-
- // 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}`);
- };
-
-
- return (
- <>
-
-
- {(
- (comment.content?.slice(0, 40) ?? comment.title?.slice(0, 40) ?? "Thread") + " - "
- + (selectedTitle ? selectedTitle : selectedAddress)
- + " - 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"}
- {
- 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
-
-
- All
- Subscriptions
- {!defaultSubplebbits.some(subplebbit => subplebbit.address === selectedAddress) && (
- {selectedAddress}
- )}
- {defaultSubplebbits.map(subplebbit => (
- {subplebbit.title ? subplebbit.title : subplebbit.address}
- ))}
-
- 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
-
-
-
-
-
- >
-
-
-
-
-
-
- {window.scrollTo(0, 0)}}>
- Return
-
-
-
-
- {window.scrollTo(0, 0)}}>Catalog
-
-
-
-
- window.scrollTo(0, document.body.scrollHeight)} onMouseOver={(event) => event.target.style.cursor='pointer'}>Bottom
-
-
-
- [
- {handleClickForm(); setSelectedShortCid(comment.shortCid)}} onMouseOver={(event) => event.target.style.cursor='pointer'}>Post a Reply
- ]
-
-
-
- {handleClickForm(); setSelectedShortCid(comment.shortCid)}} onMouseOver={(event) => event.target.style.cursor='pointer'}>Post a Reply
-
-
-
-
-
-
- Name
-
- {account && account?.author && account?.author.displayName ? (
-
- ) : (
-
- )}
-
-
-
-
- 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
- ]
-
-
- [
- Catalog
- ]
-
-
- [
- window.scrollTo(0, document.body.scrollHeight)}
- onMouseOver={(event) => event.target.style.cursor='pointer'}
- onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}>Bottom
- ]
-
- {comment ? (
- comment.replyCount !== undefined ? (
- comment.replyCount > 0 ? (
- comment.replyCount === 1 ? (
- {comment.replyCount} reply
- ) : (
- {comment.replyCount} replies
- )
- ) : (
- No replies yet
- )
- ) : (
- {stateString}
- )
- ) : (
- {stateString}
- )}
-
-
-
-
- {comment !== undefined ? (
- comment.state === "fetching-ipfs" ? (
-
- ) : (
- <>
-
-
-
-
- {commentMediaInfo?.url ? (
-
-
- {commentMediaInfo?.type === 'iframe' && (
-
-
- {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
-
- ) : commentMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {commentMediaInfo?.type === "webpage" ? (
-
-
- {comment.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- ) : null}
- {commentMediaInfo?.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- ) : null}
- {commentMediaInfo?.type === "video" ? (
-
-
- {isThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'thread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- ) : null}
- {commentMediaInfo?.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
-
- ) : null}
-
- ) : null}
-
- {comment.title ? (
- comment.title.length > 75 ?
- <>
-
- {comment.title.slice(0, 75) + " (...)"}
-
- >
- :
- {comment.title}
- )
- : null}
-
- {comment?.author?.displayName
- ? comment?.author?.displayName.length > 20
- ? <>
-
- {comment?.author?.displayName.slice(0, 20) + " (...)"}
-
- >
- :
- {comment?.author?.displayName}
- :
- Anonymous }
-
-
- handleAddressClick({({ shortAuthorAddress }) => (shortAuthorAddress || "aPL4c3H0Ld3r")} )}>
- (u/
-
- {{({ shortAuthorAddress }) => (shortAuthorAddress || "aPL4c3H0Ld3r")} }
- )
-
-
-
- {getDate(comment?.timestamp)}
-
-
-
- c/
- {}} id="reply-button" style={{ cursor: 'pointer' }}
- onClick={() => {
- let text = document.getSelection().toString();
- text = text ? `>${text}` : text;
- setSelectedText(text);
- if (isReplyOpen) {
- setReplyQuoteCid(comment.shortCid);
- setTriggerInsertion(Date.now());
- } else {
- setIsReplyOpen(true);
- setSelectedShortCid(comment.shortCid);
- setSelectedParentCid(comment.cid);
- }}} title="Reply to this post">{comment.shortCid}
-
- {comment.pinned ? (
- <>
-
-
- >
- ) : null}
- {comment.locked ? (
- <>
-
-
- >
- ) : null}
- {
- threadMenuRefs.current[comment.cid] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- rotated={openMenuCid === comment.cid}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current[comment.cid].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === comment.cid ? null : comment.cid));
- }}
- >
- ▶
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
- {
- handleOptionClick(comment.cid);
- handleShareClick(selectedAddress, comment.cid);
- }}>Share thread
- {({ authorAddress }) => (
- <>
- {authorAddress === account?.author.address ||
- authorAddress === account?.signer.address ? (
- <>
- handleAuthorEditClick(comment)}>Edit post
- handleAuthorDeleteClick(comment)}>Delete post
- >
- ) : null}
- {isModerator ? (
- <>
- {authorAddress === account?.author.address ||
- authorAddress === account?.signer.address ? (
- null
- ) : (
- {
- setModeratingCommentCid(comment.cid)
- setIsModerationOpen(true);
- handleOptionClick(comment.cid);
- setDeletePost(true);
- }}>
- Delete post
-
- )}
- {
- setModeratingCommentCid(comment.cid)
- setIsModerationOpen(true);
- handleOptionClick(comment.cid);
- }}>
- Mod tools
-
- >
- ) : null}
- >
- )}
- {(commentMediaInfo && (
- commentMediaInfo.type === 'image' ||
- (commentMediaInfo.type === 'webpage' &&
- commentMediaInfo.thumbnail))) ? (
- {setIsImageSearchOpen(true)}}
- onMouseLeave={() => {setIsImageSearchOpen(false)}}>
- Image search »
-
- handleOptionClick(comment.cid)}>
- Google
-
- handleOptionClick(comment.cid)}>
- Yandex
-
- handleOptionClick(comment.cid)}>
- SauceNAO
-
-
-
- ) : null
- }
- {setIsClientRedirectMenuOpen(true)}}
- onMouseLeave={() => {setIsClientRedirectMenuOpen(false)}}>
- View on »
-
- handleOptionClick(comment.cid)}>
- Plebbit
-
- {/* handleOptionClick(comment.cid)}>
+ const {
+ captchaResponse,
+ setCaptchaResponse,
+ setChallengesArray,
+ defaultSubplebbits,
+ editedComment,
+ editedComments,
+ setIsAuthorDelete,
+ setIsAuthorEdit,
+ setIsCaptchaOpen,
+ isModerationOpen,
+ setIsModerationOpen,
+ isSettingsOpen,
+ setIsSettingsOpen,
+ setModeratingCommentCid,
+ setReplyQuoteCid,
+ setResolveCaptchaPromise,
+ setPendingComment,
+ setPendingCommentIndex,
+ setSelectedAddress,
+ setSelectedParentCid,
+ setSelectedShortCid,
+ selectedStyle,
+ selectedThread,
+ setSelectedThread,
+ setSelectedText,
+ selectedTitle,
+ setSelectedTitle,
+ showPostForm,
+ showPostFormLink,
+ setTriggerInsertion,
+ } = useGeneralStore((state) => state);
+
+ const { anonymousMode } = useAnonModeStore();
+
+ const account = useAccount();
+ const navigate = useNavigate();
+ const handleClickForm = useClickForm();
+
+ const [, setNewErrorMessage] = useError();
+ const [, setNewSuccessMessage] = useSuccess();
+
+ const nameRef = useRef();
+ const commentRef = useRef();
+ const linkRef = useRef();
+ const threadMenuRefs = useRef({});
+ const threadMenuRefsMobile = useRef({});
+ const replyMenuRefs = useRef({});
+ const replyMenuRefsMobile = useRef({});
+ const postMenuRef = useRef(null);
+ const postMenuMobileRef = useRef(null);
+ const postMenuCatalogRef = useRef(null);
+ const backlinkRefs = useRef({});
+ const quoteRefs = useRef({});
+ const postRefs = useRef({});
+ const postOnHoverRef = useRef(null);
+ const backlinkRefsMobile = useRef({});
+ const quoteRefsMobile = useRef({});
+
+ const [triggerPublishComment, setTriggerPublishComment] = useState(false);
+ const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false);
+ const [deletePost, setDeletePost] = 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 [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);
+
+ useAnonMode(selectedThread, anonymousMode && executeAnonMode);
+
+ const originalComment = useComment({ commentCid: selectedThread });
+ const [comment, setComment] = useState(originalComment);
+ const { subplebbitAddress, threadCid } = useParams();
+ const subplebbit = useSubplebbit({ subplebbitAddress: originalComment.subplebbitAddress });
+ const selectedAddress = subplebbit.address;
+
+ const stateString = useStateString(comment);
+
+ const commentMediaInfo = getCommentMediaInfo(comment);
+ const fallbackImgUrl = 'assets/filedeleted-res.gif';
+
+ useEffect(() => {
+ let updatedComment = { ...originalComment };
+
+ if (editedComments[originalComment.cid]) {
+ updatedComment = { ...originalComment, ...editedComments[originalComment.cid] };
+
+ if (updatedComment.removed) {
+ updatedComment.content = '[removed]';
+ updatedComment.link = undefined;
+ }
+ }
+
+ setComment(updatedComment);
+ }, [originalComment, editedComments]);
+
+ 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(() => {
+ 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 handleOptionClick = () => {
+ setOpenMenuCid(null);
+ };
+
+ const handleMobileOptionClick = () => {
+ setOpenMobileMenuCid(null);
+ };
+
+ const handleOutsideClick = useCallback(
+ (e) => {
+ if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) {
+ setOpenMenuCid(null);
+ }
+ },
+ [openMenuCid, postMenuRef, 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]);
+
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
+
+ useEffect(() => {
+ if (comment.state === 'failed' && selectedAddress === undefined) {
+ navigate('/404');
+ }
+ }, [selectedAddress, navigate, comment.state]);
+
+ const errorString = useMemo(() => {
+ if (comment?.state === 'failed') {
+ let errorString = 'Failed fetching thread.';
+ if (comment.error) {
+ errorString += `: ${comment.error.toString().slice(0, 300)}`;
+ }
+ return errorString;
+ }
+ }, [comment?.state, comment?.error]);
+
+ useEffect(() => {
+ if (errorString) {
+ setNewErrorMessage(errorString);
+ }
+ }, [errorString, setNewErrorMessage]);
+
+ const flattenedReplies = useMemo(() => flattenCommentsPages(comment.replies), [comment.replies]);
+
+ const threadAndRepliesCids = useMemo(() => new Set([selectedThread || 'n/a', ...flattenedReplies.map((reply) => reply.cid)]), [selectedThread, flattenedReplies]);
+
+ const filter = useCallback((accountComment) => threadAndRepliesCids.has(accountComment.parentCid), [threadAndRepliesCids]);
+
+ const { accountComments } = useAccountComments({ filter });
+
+ const accountRepliesNotYetInCommentReplies = useMemo(() => {
+ const commentReplyCids = new Set(flattenedReplies.map((reply) => reply.cid));
+ return accountComments.filter((accountReply) => {
+ return !commentReplyCids.has(accountReply.cid) && accountReply.parentCid === selectedThread;
+ });
+ }, [flattenedReplies, accountComments, selectedThread]);
+
+ const sortedReplies = useMemo(
+ () => [...accountRepliesNotYetInCommentReplies, ...flattenedReplies].sort((a, b) => a.timestamp - b.timestamp),
+ [accountRepliesNotYetInCommentReplies, flattenedReplies],
+ );
+
+ // let post.jsx access full cid of user-typed short cid
+ useEffect(() => {
+ const newCidTracker = {};
+ sortedReplies.forEach((reply) => {
+ newCidTracker[reply.shortCid] = reply.cid;
+ });
+ setCidTracker(newCidTracker);
+ }, [sortedReplies]);
+
+ useEffect(() => {
+ const selectedSubplebbit = defaultSubplebbits.find((subplebbit) => subplebbit.address === subplebbitAddress);
+ if (threadCid) {
+ setSelectedThread(threadCid);
+ }
+ if (subplebbitAddress) {
+ setSelectedAddress(subplebbitAddress);
+ } else if (subplebbit?.address) {
+ setSelectedAddress(subplebbit.address);
+ }
+ if (selectedSubplebbit) {
+ setSelectedTitle(selectedSubplebbit.title);
+ } else if (subplebbit?.title) {
+ setSelectedTitle(subplebbit.title);
+ }
+ }, [subplebbitAddress, threadCid, defaultSubplebbits, subplebbit, setSelectedThread, setSelectedAddress, setSelectedTitle]);
+
+ // 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 onChallengeVerification = (challengeVerification) => {
+ if (challengeVerification.challengeSuccess === true) {
+ if (challengeVerification.publication?.cid !== undefined) {
+ return;
+ } 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: comment.subplebbitAddress,
+ }));
+ }, [comment.subplebbitAddress]);
+
+ const [publishCommentOptions, setPublishCommentOptions] = useState({
+ subplebbitAddress: comment.subplebbitAddress,
+ onChallenge,
+ onChallengeVerification,
+ onError: (error) => {
+ setNewErrorMessage(error.message);
+ console.log(error);
+ },
+ });
+
+ const { publishComment, index } = usePublishComment(publishCommentOptions);
+
+ useEffect(() => {
+ if (index !== undefined) {
+ setPendingCommentIndex(index);
+ }
+ }, [index, setPendingCommentIndex]);
+
+ const resetFields = useCallback(() => {
+ if (nameRef.current) {
+ nameRef.current.value = '';
+ }
+ if (commentRef.current) {
+ commentRef.current.value = '';
+ }
+ if (linkRef.current) {
+ linkRef.current.value = '';
+ }
+ }, []);
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+
+ if (commentRef.current.value === '' && linkRef.current.value === '') {
+ setNewErrorMessage('Please enter a comment or link.');
+ return;
+ }
+
+ setPublishCommentOptions((prevPublishCommentOptions) => ({
+ ...prevPublishCommentOptions,
+ author: {
+ displayName: nameRef.current.value || undefined,
+ ...(anonymousMode ? {} : { address: account?.author.address }),
+ },
+ content: commentRef.current.value || undefined,
+ link: linkRef.current.value || undefined,
+ parentCid: selectedThread,
+ }));
+
+ setTriggerPublishComment(true);
+ };
+
+ const updateSigner = useCallback(async () => {
+ if (anonymousMode) {
+ setExecuteAnonMode(true);
+
+ let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
+ let signer;
+
+ if (!storedSigners[selectedThread]) {
+ signer = await account?.plebbit.createSigner();
+ storedSigners[selectedThread] = { privateKey: signer?.privateKey, address: signer?.address };
+ localStorage.setItem('storedSigners', JSON.stringify(storedSigners));
+ } else {
+ const signerPrivateKey = storedSigners[selectedThread].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;
+ });
+ }
+ }, [selectedThread, 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 = 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: comment.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?
+
+ No
+ {
+ setIsAuthorDelete(true);
+ setIsAuthorEdit(false);
+ setPublishCommentEditOptions((prevOptions) => ({
+ ...prevOptions,
+ commentCid: comment.cid,
+ subplebbitAddress: comment.subplebbitAddress,
+ deleted: true,
+ }));
+ setTriggerPublishCommentEdit(true);
+ onClose();
+ }}
+ >
+ Yes
+
+
+
+
+ );
+ },
+ });
+ };
+
+ 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]);
+
+ useEffect(() => {
+ let isActive = true;
+ if (publishCommentEditOptions && triggerPublishCommentEdit) {
+ (async () => {
+ await publishCommentEdit();
+ if (isActive) {
+ setTriggerPublishCommentEdit(false);
+ }
+ })();
+ }
+
+ return () => {
+ isActive = false;
+ };
+ }, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]);
+
+ // 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}`);
+ };
+
+ return (
+ <>
+
+
+ {(comment.content?.slice(0, 40) ?? comment.title?.slice(0, 40) ?? 'Thread') + ' - ' + (selectedTitle ? selectedTitle : selectedAddress) + ' - 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'}
+ {
+ 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
+
+
+ All
+ Subscriptions
+ {!defaultSubplebbits.some((subplebbit) => subplebbit.address === selectedAddress) && {selectedAddress} }
+ {defaultSubplebbits.map((subplebbit) => (
+
+ {subplebbit.title ? subplebbit.title : subplebbit.address}
+
+ ))}
+
+
+
+ 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
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Return
+
+
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ >
+ Catalog
+
+
+
+
+
+ window.scrollTo(0, document.body.scrollHeight)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ >
+ Bottom
+
+
+
+
+ [
+ {
+ handleClickForm();
+ setSelectedShortCid(comment.shortCid);
+ }}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ >
+ Post a Reply
+
+ ]
+
+
+
+ {
+ handleClickForm();
+ setSelectedShortCid(comment.shortCid);
+ }}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ >
+ Post a Reply
+
+
+
+
+
+
+
+ Name
+
+ {account && account?.author && account?.author.displayName ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ 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]
+
+
+ [ Catalog]
+
+
+ [
+ window.scrollTo(0, document.body.scrollHeight)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}
+ >
+ Bottom
+
+ ]
+
+ {comment ? (
+ comment.replyCount !== undefined ? (
+ comment.replyCount > 0 ? (
+ comment.replyCount === 1 ? (
+ {comment.replyCount} reply
+ ) : (
+ {comment.replyCount} replies
+ )
+ ) : (
+ No replies yet
+ )
+ ) : (
+ {stateString}
+ )
+ ) : (
+ {stateString}
+ )}
+
+
+
+
+ {comment !== undefined ? (
+ comment.state === 'fetching-ipfs' ? (
+
+ ) : (
+ <>
+
+
+
+
+ {commentMediaInfo?.url ? (
+
+
+ {commentMediaInfo?.type === 'iframe' && (
+
+
+ {isThreadThumbnailClicked[index] && commentMediaInfo.url ? (
+
+ ) : commentMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {commentMediaInfo?.type === 'webpage' ? (
+
+
+ {comment.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'video' ? (
+
+
+ {isThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'thread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ ) : null}
+ {commentMediaInfo?.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ ) : null}
+
+ ) : null}
+
+ {comment.title ? (
+ comment.title.length > 75 ? (
+ <>
+
+ {comment.title.slice(0, 75) + ' (...)'}
+
+ >
+ ) : (
+
+ {comment.title}
+
+ )
+ ) : null}
+
+ {comment?.author?.displayName ? (
+ comment?.author?.displayName.length > 20 ? (
+ <>
+
+ {comment?.author?.displayName.slice(0, 20) + ' (...)'}
+
+ >
+ ) : (
+
+ {comment?.author?.displayName}
+
+ )
+ ) : (
+
+ Anonymous
+
+ )}
+
+
+ handleAddressClick(
+ {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} ,
+ )
+ }
+ >
+ (u/
+
+ {{({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} }
+
+ )
+
+
+
+ {getDate(comment?.timestamp)}
+
+
+
+ c/
+ {}}
+ id='reply-button'
+ style={{ cursor: 'pointer' }}
+ onClick={() => {
+ let text = document.getSelection().toString();
+ text = text ? `>${text}` : text;
+ setSelectedText(text);
+ if (isReplyOpen) {
+ setReplyQuoteCid(comment.shortCid);
+ setTriggerInsertion(Date.now());
+ } else {
+ setIsReplyOpen(true);
+ setSelectedShortCid(comment.shortCid);
+ setSelectedParentCid(comment.cid);
+ }
+ }}
+ title='Reply to this post'
+ >
+ {comment.shortCid}
+
+
+
+ {comment.pinned ? (
+ <>
+
+
+ >
+ ) : null}
+ {comment.locked ? (
+ <>
+
+
+ >
+ ) : null}
+ {
+ threadMenuRefs.current[comment.cid] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ rotated={openMenuCid === comment.cid}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current[comment.cid].getBoundingClientRect();
+ setMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMenuCid((prevCid) => (prevCid === comment.cid ? null : comment.cid));
+ }}
+ >
+ ▶
+
+ {createPortal(
+ {
+ postMenuCatalogRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{ position: 'absolute', top: menuPosition.top + 7, left: menuPosition.left }}
+ >
+
+
+ {
+ handleOptionClick(comment.cid);
+ handleShareClick(selectedAddress, comment.cid);
+ }}
+ >
+ Share thread
+
+
+ {({ authorAddress }) => (
+ <>
+ {authorAddress === account?.author.address || authorAddress === account?.signer.address ? (
+ <>
+ handleAuthorEditClick(comment)}>Edit post
+ handleAuthorDeleteClick(comment)}>Delete post
+ >
+ ) : null}
+ {isModerator ? (
+ <>
+ {authorAddress === account?.author.address || authorAddress === account?.signer.address ? null : (
+ {
+ setModeratingCommentCid(comment.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(comment.cid);
+ setDeletePost(true);
+ }}
+ >
+ Delete post
+
+ )}
+ {
+ setModeratingCommentCid(comment.cid);
+ setIsModerationOpen(true);
+ handleOptionClick(comment.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(comment.cid)}>
+
+ Plebbit
+
+
+ {/* handleOptionClick(comment.cid)}>
Seedit
*/}
- handleOptionClick(comment.cid)}>
- Plebones
-
-
-
-
-
- , document.body
- )}
-
- {comment.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}} className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- setOutOfViewCid(reply.cid);
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(comment.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {comment.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefs.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/{reply.shortCid}
-
-
- ))
- }
-
-
-
-
-
-
-
-
-
-
- {comment.replyCount === undefined ?
: null}
- {sortedReplies.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, comment);
- let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
- let signerAddress;
- if (storedSigners[selectedThread]) {
- signerAddress = storedSigners[selectedThread].address;
- }
- 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 ?? reply.displayName}
- :
- Anonymous }
-
- handleAddressClick(reply.author?.shortAddress)}
- >
- (u/
- {reply.author?.shortAddress ?
- (
-
- {reply.author?.shortAddress}
-
- ) : (
- anonymousMode ? (
-
- {signerAddress?.slice(8, 20)}
-
- ) : (
-
- {account?.author?.shortAddress}
-
- )
- )
- }
- )
-
-
-
-
-
- {getDate(reply?.timestamp)}
-
-
- c/
- {reply.shortCid ? (
- {
- 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}
- ) : (
-
- )}
-
-
-
{
- replyMenuRefs.current[reply.cid] = el;
- postMenuRef.current = 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(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);
- handleOptionClick(reply.cid);
- setDeletePost(true);
- }}>
- Delete post
-
- )}
- {
- 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}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {comment.replyCount === undefined ?
: null}
+ {sortedReplies.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, comment);
+ let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
+ let signerAddress;
+ if (storedSigners[selectedThread]) {
+ signerAddress = storedSigners[selectedThread].address;
+ }
+ 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 ?? reply.displayName}
+
+ )
+ ) : (
+
+ Anonymous
+
+ )}
+
+ handleAddressClick(reply.author?.shortAddress)}
+ >
+ (u/
+ {reply.author?.shortAddress ? (
+ {reply.author?.shortAddress}
+ ) : anonymousMode ? (
+ {signerAddress?.slice(8, 20)}
+ ) : (
+ {account?.author?.shortAddress}
+ )}
+ )
+
+
+
+
+
+ {getDate(reply?.timestamp)}
+
+
+ c/
+ {reply.shortCid ? (
+ {
+ 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}
+
+ ) : (
+
+ )}
+
+
+
+
{
+ replyMenuRefs.current[reply.cid] = el;
+ postMenuRef.current = 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(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);
+ handleOptionClick(reply.cid);
+ setDeletePost(true);
+ }}
+ >
+ Delete post
+
+ )}
+ {
+ 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
- )}
-
- {reply.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefs.current[reply.cid] = el;
- }}>
-
{}} className="quote-link"
- onClick={(event) => handleQuoteClick(reply, null, event)}
- onMouseOver={(event) => {
- event.stopPropagation();
- handleQuoteHover(reply, null, () => {
- setOutOfViewCid(reply.cid);
- const rect = backlinkRefs.current[reply.cid].getBoundingClientRect();
- const distanceToRight = window.innerWidth - rect.right;
- const distanceToTop = rect.top;
- const distanceToBottom = window.innerHeight - rect.bottom;
- let top;
+
handleOptionClick(reply.cid)}>
+
+ Plebones
+
+
+
+
+
+
+ ,
+ document.body,
+ )}
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefs.current[reply.cid] = el;
+ }}
+ >
+ {}}
+ className='quote-link'
+ onClick={(event) => handleQuoteClick(reply, null, event)}
+ onMouseOver={(event) => {
+ event.stopPropagation();
+ handleQuoteHover(reply, null, () => {
+ setOutOfViewCid(reply.cid);
+ const rect = backlinkRefs.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/{reply.shortCid}
-
-
- ))
- }
-
-
- {replyMediaInfo?.url ? (
-
-
- {replyMediaInfo?.type === 'iframe' && (
-
-
- {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
-
- ) : replyMediaInfo.thumbnail ? (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- )}
- {replyMediaInfo?.type === "webpage" ? (
-
-
- {reply.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
-
-
- ) : null}
- {replyMediaInfo?.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
-
-
- ) : null}
- {replyMediaInfo?.type === "video" ? (
-
-
- {isReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl}
- />
- ) : (
- {handleThumbnailClick(index, 'reply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
-
- ) : null}
- {replyMediaInfo?.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
-
- ) : null}
-
- ) : null}
-
- {}} key={`r-pm-${index}`} className="quotelink"
- ref={el => {
- quoteRefs.current[reply.cid] = el;
- }}
- onClick={() => handleQuoteClick(reply, shortParentCid, null)}
- 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 === comment.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 (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 ? (
+
+
+ {replyMediaInfo?.type === 'iframe' && (
+
+
+ {isReplyThumbnailClicked[index] && replyMediaInfo.url ? (
+
+ ) : replyMediaInfo.thumbnail ? (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ )}
+ {replyMediaInfo?.type === 'webpage' ? (
+
+
+ {reply.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+
+ ) : null}
+ {replyMediaInfo?.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+
+ ) : null}
+ {replyMediaInfo?.type === 'video' ? (
+
+
+ {isReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'reply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+
+ ) : null}
+ {replyMediaInfo?.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)}
+ />
+
+ ) : null}
+
+ ) : null}
+
+ {}}
+ key={`r-pm-${index}`}
+ className='quotelink'
+ ref={(el) => {
+ quoteRefs.current[reply.cid] = el;
+ }}
+ onClick={() => handleQuoteClick(reply, shortParentCid, null)}
+ 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;
- 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 (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);
- }}
- />
-
-
-
-
-
- )
- })
- }
-
-
-
-
-
-
-
{
- threadMenuRefsMobile.current[comment.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefsMobile.current[comment.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === comment.cid ? null : comment.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === comment.cid ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , document.body
- )}
-
- {comment?.author?.displayName
- ? comment?.author?.displayName.length > 15
- ? <>
-
- {comment?.author?.displayName.slice(0, 15) + " (...)"}
-
- >
- :
- {comment?.author?.displayName}
- :
- Anonymous }
-
- handleAddressClick({({ shortAuthorAddress }) => (shortAuthorAddress || "aPL4c3H0Ld3r")} )}
- >
- (u/
-
- {{({ shortAuthorAddress }) => (shortAuthorAddress || "aPL4c3H0Ld3r")} }
-
- )
-
-
- {comment.pinned ? (
-
- ) : null}
- {comment.locked ? (
-
- ) : null}
-
-
- {comment.title ? (
- comment.title.length > 30 ?
- <>
-
- {comment.title.slice(0, 30) + " (...)"}
-
- >
- :
- {comment.title}
- ) : null}
-
-
-
- {getDate(comment?.timestamp)}
-
-
- c/
- {}} id="reply-button" key={`mob-no2-${comment.cid}`} title="Reply to this post"
- onClick={() => {
- let text = document.getSelection().toString();
- text = text ? `>${text}` : text;
- setSelectedText(text);
- if (isReplyOpen) {
- setReplyQuoteCid(comment.shortCid);
- setTriggerInsertion(Date.now());
- } else {
- setIsReplyOpen(true);
- setSelectedShortCid(comment.shortCid);
- setSelectedParentCid(comment.cid);
- }}}>{comment.shortCid}
-
-
- {comment.link ? (
-
- {commentMediaInfo?.type === 'iframe' && (
-
-
- {(isMobileThreadThumbnailClicked[index] && commentMediaInfo.url ? (
-
-
-
- ) : commentMediaInfo.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" ? (
-
-
- {comment.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
-
- ) : commentMediaInfo.type === "video" ? (
-
- {isMobileThreadThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileThread')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
- {commentMediaInfo?.type === "video" || commentMediaInfo?.type === "iframe" ? (
- isMobileThreadThumbnailClicked[index] ? (
-
- {handleThumbnailClick(index, 'mobileThread')}}
- >Close
-
- ) : (
- video
- )) : video
}
-
- ) : commentMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {commentMediaInfo?.type}
-
- ) : null
- ) : null}
-
- ) : null}
-
- {comment.content ? (
- <>
-
-
-
- >
- ) : null}
-
-
-
- {comment.replyCount === undefined ?
: null}
- {sortedReplies.map((reply, index) => {
- const replyMediaInfo = getCommentMediaInfo(reply);
- const shortParentCid = findShortParentCid(reply.parentCid, comment);
- let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
- let signerAddress;
- if (storedSigners[selectedThread]) {
- signerAddress = storedSigners[selectedThread].address;
- }
- return (
-
-
-
-
{
- replyMenuRefsMobile.current[reply.cid] = el;
- }}
- onClick={(event) => {
- event.stopPropagation();
- const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
- setMobileMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMobileMenuCid(prevCid => (prevCid === reply.cid ? null : reply.cid));
- }}
- style={{ all: 'unset', cursor: 'pointer' }}
- >...
- {createPortal(
-
{postMenuMobileRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- display: openMobileMenuCid === reply.cid ? "block" : "none",
- top: mobileMenuPosition.top + 20,
- left: mobileMenuPosition.left}}>
-
- , document.body
- )}
-
- {reply.author?.displayName
- ? reply.author?.displayName.length > 12
- ? <>
-
- {reply.author?.displayName.slice(0, 12) + " (...)"}
-
- >
- :
- {reply.author?.displayName}
- :
- Anonymous }
-
- handleAddressClick(reply.author?.shortAddress)}
- >
- (u/
- {reply.author?.shortAddress ?
- (
-
- {reply.author?.shortAddress}
-
- ) : (
- anonymousMode ? (
-
- {signerAddress?.slice(8, 20)}
-
- ) : (
-
- {account?.author?.shortAddress}
-
- )
- )
- }
- )
-
-
-
-
-
- {getDate(reply?.timestamp)}
-
- c/
- {reply.shortCid ? (
- {
- 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 ? (
- {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
}
-
-
+ 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 === comment.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);
- ) : replyMediaInfo.type === "webpage" ? (
-
-
- {reply.thumbnailUrl ? (
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- ) : null}
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "image" ? (
-
-
- {handleImageClick(e); handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- onError={(e) => e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
-
- ) : replyMediaInfo.type === "video" ? (
-
- {isMobileReplyThumbnailClicked[index] ? (
- e.target.src = fallbackImgUrl} />
- ) : (
- {handleThumbnailClick(index, 'mobileReply')}}
- style={{cursor: "pointer"}}
- id="video-thumbnail-mobile"
- onError={(e) => e.target.src = fallbackImgUrl}
- />
- )}
-
- ) : replyMediaInfo.type === "audio" ? (
-
- e.target.src = fallbackImgUrl} />
- {replyMediaInfo.type}
-
- ) : null
- ) : null}
-
- ) : null}
-
- {
- 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 === comment.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,
+ });
+ }
+ });
+ }
+ }}
+ postQuoteOnLeave={() => {
+ removeHighlight();
+ setOutOfViewCid(null);
+ }}
+ />
+
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+
{
+ threadMenuRefsMobile.current[comment.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefsMobile.current[comment.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === comment.cid ? null : comment.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === comment.cid ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ document.body,
+ )}
+
+ {comment?.author?.displayName ? (
+ comment?.author?.displayName.length > 15 ? (
+ <>
+
+ {comment?.author?.displayName.slice(0, 15) + ' (...)'}
+
+ >
+ ) : (
+
+ {comment?.author?.displayName}
+
+ )
+ ) : (
+
+ Anonymous
+
+ )}
+
+
+ handleAddressClick(
+ {({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} ,
+ )
+ }
+ >
+ (u/
+
+ {{({ shortAuthorAddress }) => shortAuthorAddress || 'aPL4c3H0Ld3r'} }
+
+ )
+
+
+ {comment.pinned ? (
+
+ ) : null}
+ {comment.locked ? (
+
+ ) : null}
+
+
+ {comment.title ? (
+ comment.title.length > 30 ? (
+ <>
+
+ {comment.title.slice(0, 30) + ' (...)'}
+
+ >
+ ) : (
+
+ {comment.title}
+
+ )
+ ) : null}
+
+
+
+ {getDate(comment?.timestamp)}
+
+
+ c/
+ {}}
+ id='reply-button'
+ key={`mob-no2-${comment.cid}`}
+ title='Reply to this post'
+ onClick={() => {
+ let text = document.getSelection().toString();
+ text = text ? `>${text}` : text;
+ setSelectedText(text);
+ if (isReplyOpen) {
+ setReplyQuoteCid(comment.shortCid);
+ setTriggerInsertion(Date.now());
+ } else {
+ setIsReplyOpen(true);
+ setSelectedShortCid(comment.shortCid);
+ setSelectedParentCid(comment.cid);
+ }
+ }}
+ >
+ {comment.shortCid}
+
+
+
+ {comment.link ? (
+
+ {commentMediaInfo?.type === 'iframe' && (
+
+
+ {isMobileThreadThumbnailClicked[index] && commentMediaInfo.url ? (
+
+
+
+ ) : commentMediaInfo.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' ? (
+
+
+ {comment.thumbnailUrl ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {commentMediaInfo?.type}
+
+
+
+ ) : commentMediaInfo.type === 'video' ? (
+
+ {isMobileThreadThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+ {commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'iframe' ? (
+ isMobileThreadThumbnailClicked[index] ? (
+
+ {
+ handleThumbnailClick(index, 'mobileThread');
+ }}
+ >
+ Close
+
+
+ ) : (
+
+ video
+
+ )
+ ) : (
+
+ video
+
+ )}
+
+ ) : commentMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)} />
+
+ {commentMediaInfo?.type}
+
+
+ ) : null
+ ) : null}
+
+ ) : null}
+
+ {comment.content ? (
+ <>
+
+
+
+ >
+ ) : null}
+
+
+
+ {comment.replyCount === undefined ?
: null}
+ {sortedReplies.map((reply, index) => {
+ const replyMediaInfo = getCommentMediaInfo(reply);
+ const shortParentCid = findShortParentCid(reply.parentCid, comment);
+ let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
+ let signerAddress;
+ if (storedSigners[selectedThread]) {
+ signerAddress = storedSigners[selectedThread].address;
+ }
+ return (
+
+
+
+
{
+ replyMenuRefsMobile.current[reply.cid] = el;
+ }}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = replyMenuRefsMobile.current[reply.cid].getBoundingClientRect();
+ setMobileMenuPosition({ top: rect.top + window.scrollY, left: rect.left });
+ setOpenMobileMenuCid((prevCid) => (prevCid === reply.cid ? null : reply.cid));
+ }}
+ style={{ all: 'unset', cursor: 'pointer' }}
+ >
+ ...
+
+ {createPortal(
+
{
+ postMenuMobileRef.current = el;
+ }}
+ onClick={(event) => event.stopPropagation()}
+ style={{
+ position: 'absolute',
+ display: openMobileMenuCid === reply.cid ? 'block' : 'none',
+ top: mobileMenuPosition.top + 20,
+ left: mobileMenuPosition.left,
+ }}
+ >
+
+ ,
+ document.body,
+ )}
+
+ {reply.author?.displayName ? (
+ reply.author?.displayName.length > 12 ? (
+ <>
+
+ {reply.author?.displayName.slice(0, 12) + ' (...)'}
+
+ >
+ ) : (
+
+ {reply.author?.displayName}
+
+ )
+ ) : (
+
+ Anonymous
+
+ )}
+
+ handleAddressClick(reply.author?.shortAddress)}
+ >
+ (u/
+ {reply.author?.shortAddress ? (
+
+ {reply.author?.shortAddress}
+
+ ) : anonymousMode ? (
+ {signerAddress?.slice(8, 20)}
+ ) : (
+ {account?.author?.shortAddress}
+ )}
+ )
+
+
+
+
+
+ {getDate(reply?.timestamp)}
+
+ c/
+ {reply.shortCid ? (
+ {
+ 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 ? (
+ {
+ 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 ? (
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ ) : null}
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'image' ? (
+
+
+ {
+ handleImageClick(e);
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+
+ {replyMediaInfo.type}
+
+
+
+ ) : replyMediaInfo.type === 'video' ? (
+
+ {isMobileReplyThumbnailClicked[index] ? (
+ (e.target.src = fallbackImgUrl)}
+ />
+ ) : (
+ {
+ handleThumbnailClick(index, 'mobileReply');
+ }}
+ style={{ cursor: 'pointer' }}
+ id='video-thumbnail-mobile'
+ onError={(e) => (e.target.src = fallbackImgUrl)}
+ />
+ )}
+
+ ) : replyMediaInfo.type === 'audio' ? (
+
+ (e.target.src = fallbackImgUrl)} />
+
+ {replyMediaInfo.type}
+
+
+ ) : null
+ ) : null}
+
+ ) : null}
+
+ {
+ 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
- });
- }
- })
- }}
- }
- postQuoteOnLeave={() => {
- removeHighlight();
- setOutOfViewCid(null);
- }}
- />
-
-
-
- {reply.replyCount > 0 ? (
-
- {reply.replies?.pages?.topAll.comments
- .sort((a, b) => a.timestamp - b.timestamp)
- .map((reply, index) => (
-
{
- backlinkRefsMobile.current[reply.cid] = el;
- }}>
- 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 (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}
-
-
- ))}
-
- ) : null}
-
-
- )
- })
- }
-
-
-
-
-
- [
- Return
- ]
-
-
- [
- Catalog
- ]
-
-
- [
- window.scrollTo(0, 0)}
- onMouseOver={(event) => event.target.style.cursor='pointer'}
- onTouchStart={() => window.scrollTo(0, 0)}>Top
- ]
-
-
- [
- {setIsReplyOpen(true); setSelectedParentCid(comment.cid); setSelectedShortCid(comment.shortCid);}} onMouseOver={(event) => event.target.style.cursor='pointer'}>Post a Reply
- ]
-
- {comment ? (
- comment.replyCount !== undefined ? (
- comment.replyCount > 0 ? (
- comment.replyCount === 1 ? (
- {comment.replyCount} reply
- ) : (
- {comment.replyCount} replies
- )
- ) : (
- No replies yet
- )
- ) : (
- {stateString}
- )
- ) : (
- {stateString}
- )}
-
-
-
- {comment.replyCount > 2 ? (
-
-
-
-
- Style:
-
-
- Yotsuba
- Yotsuba B
- Futaba
- Burichan
- Tomorrow
- Photon
-
-
- {comment ? (
- comment.replyCount !== undefined ? (
- comment.replyCount > 0 ? (
- comment.replyCount === 1 ? (
- {comment.replyCount} reply
- ) : (
- {comment.replyCount} replies
- )
- ) : (
- No replies yet
- )
- ) : (
- Loading...
- )
- ) : (
- null
- )}
-
-
-
-
-
- {setIsReplyOpen(true); setSelectedParentCid(comment.cid); setSelectedShortCid(comment.shortCid);}} onMouseOver={(event) => event.target.style.cursor='pointer'}>Post a Reply
-
-
-
-
-
- Return
-
-
-
-
- Catalog
-
-
-
-
- window.scrollTo(0, 0)}
- onMouseOver={(event) => event.target.style.cursor='pointer'}
- onTouchStart={() => window.scrollTo(0, 0)}
- style={{cursor: 'pointer', marginRight: "10px", marginLeft: "10px"}}
- >Top
-
-
-
-
-
- ) : (null)}
- >
- )) : null}
- {outOfViewCid && outOfViewPosition && createPortal(
-
- , document.body)}
-
-
-
- >
- );
-}
+ 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 === comment.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);
-export default Thread;
\ No newline at end of file
+ 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);
+ }}
+ />
+
+
+
+ {reply.replyCount > 0 ? (
+
+ {reply.replies?.pages?.topAll.comments
+ .sort((a, b) => a.timestamp - b.timestamp)
+ .map((reply, index) => (
+
{
+ backlinkRefsMobile.current[reply.cid] = el;
+ }}
+ >
+ 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);
+ }}
+ >
+ c/{reply.shortCid}
+
+
+
+ ))}
+
+ ) : null}
+
+
+ );
+ })}
+
+
+
+
+
+ [ Return]
+
+
+ [ Catalog]
+
+
+ [
+ window.scrollTo(0, 0)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ onTouchStart={() => window.scrollTo(0, 0)}
+ >
+ Top
+
+ ]
+
+
+ [
+ {
+ setIsReplyOpen(true);
+ setSelectedParentCid(comment.cid);
+ setSelectedShortCid(comment.shortCid);
+ }}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ >
+ Post a Reply
+
+ ]
+
+ {comment ? (
+ comment.replyCount !== undefined ? (
+ comment.replyCount > 0 ? (
+ comment.replyCount === 1 ? (
+ {comment.replyCount} reply
+ ) : (
+ {comment.replyCount} replies
+ )
+ ) : (
+ No replies yet
+ )
+ ) : (
+ {stateString}
+ )
+ ) : (
+ {stateString}
+ )}
+
+
+
+ {comment.replyCount > 2 ? (
+
+
+
+
+ Style:
+
+ Yotsuba
+ Yotsuba B
+ Futaba
+ Burichan
+ Tomorrow
+ Photon
+
+
+ {comment ? (
+ comment.replyCount !== undefined ? (
+ comment.replyCount > 0 ? (
+ comment.replyCount === 1 ? (
+ {comment.replyCount} reply
+ ) : (
+ {comment.replyCount} replies
+ )
+ ) : (
+ No replies yet
+ )
+ ) : (
+ Loading...
+ )
+ ) : null}
+
+
+
+
+
+ {
+ setIsReplyOpen(true);
+ setSelectedParentCid(comment.cid);
+ setSelectedShortCid(comment.shortCid);
+ }}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ >
+ Post a Reply
+
+
+
+
+
+
+ Return
+
+
+
+
+ Catalog
+
+
+
+
+ window.scrollTo(0, 0)}
+ onMouseOver={(event) => (event.target.style.cursor = 'pointer')}
+ onTouchStart={() => window.scrollTo(0, 0)}
+ style={{ cursor: 'pointer', marginRight: '10px', marginLeft: '10px' }}
+ >
+ Top
+
+
+
+
+
+
+ ) : null}
+ >
+ )
+ ) : null}
+ {outOfViewCid &&
+ outOfViewPosition &&
+ createPortal(
+ ,
+ document.body,
+ )}
+
+
+
+ >
+ );
+};
+
+export default Thread;