From aa875b9c9ab44aeaa6aa6dc4d2fb87f3e5e14e8c Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 25 Apr 2023 14:34:48 +0200 Subject: [PATCH] fix pending reply logic, add it to board --- src/components/views/Board.jsx | 156 +++++++++++++++++++++++------- src/components/views/Thread.jsx | 41 ++++++-- src/utils/renderComments.js | 30 ------ src/utils/renderThreadComments.js | 24 ----- 4 files changed, 154 insertions(+), 97 deletions(-) delete mode 100644 src/utils/renderComments.js delete mode 100644 src/utils/renderThreadComments.js diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index 8427ae43..7ea65060 100644 --- a/src/components/views/Board.jsx +++ b/src/components/views/Board.jsx @@ -1,9 +1,10 @@ -import React, { useState, useEffect, useRef, Fragment } from 'react'; +import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import InfiniteScroll from 'react-infinite-scroller'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; -import { useFeed, usePublishComment } from '@plebbit/plebbit-react-hooks'; +import { useAccount, useAccountComments, useFeed, usePublishComment } from '@plebbit/plebbit-react-hooks'; +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, PostFormLink, PostFormTable, PostForm, TopBar, BoardForm } from '../styled/Board.styled'; @@ -19,7 +20,6 @@ import getDate from '../../utils/getDate'; import handleAddressClick from '../../utils/handleAddressClick'; import handleQuoteClick from '../../utils/handleQuoteClick'; import handleStyleChange from '../../utils/handleStyleChange'; -import renderComments from '../../utils/renderComments'; import useClickForm from '../../hooks/useClickForm'; import useError from '../../hooks/useError'; import packageJson from '../../../package.json' @@ -62,6 +62,62 @@ const Board = () => { const [errorMessage, setErrorMessage] = useState(null); useError(errorMessage, [errorMessage]); + const account = useAccount(); + + + 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 [...allThreadCids, ...allRepliesCids]; + }, [flattenedRepliesByThread, selectedFeed]); + + + const filter = useMemo(() => ({ + parentCids: allParentCids + }), [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]); + + // temporary title from JSON, gets subplebbitAddress from URL useEffect(() => { setSelectedAddress(subplebbitAddress); @@ -396,8 +452,8 @@ const Board = () => { hasMore={hasMore} > {selectedFeed.map((thread) => { - const { replies: { pages: { topAll: { comments } = {} } = {} } = {} } = thread; - const { renderedComments, omittedCount } = renderComments(comments); + const { replies: { pages: { topAll: {} = {} } = {} } = {} } = thread; + const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {}; const commentMediaInfo = getCommentMediaInfo(thread); const fallbackImgUrl = "/assets/filedeleted-res.gif"; return ( @@ -542,7 +598,7 @@ const Board = () => { ) : null} - {renderedComments?.map((reply) => { + {displayedReplies?.map((reply) => { const replyMediaInfo = getCommentMediaInfo(reply); const fallbackImgUrl = "/assets/filedeleted-res.gif"; const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); @@ -572,9 +628,21 @@ const Board = () => { onClick={() => handleAddressClick(reply.author.shortAddress)} > (u/ - - {reply.author.shortAddress} - + {reply.author?.shortAddress ? + ( + + {reply.author?.shortAddress} + + ) : ( + + {account?.author?.address.slice(0, 10) + "(...)"} + + ) + } ) @@ -583,14 +651,18 @@ const Board = () => {   {}} key={`pl1-${reply.cid}`} onClick={() => {}} title="Link to this post">c/ - { - if (e.button === 2) return; - e.preventDefault(); - setIsReplyOpen(true); - setSelectedShortCid(reply.shortCid); - setSelectedParentCid(reply.cid); - }} title="Reply to this post">{reply.shortCid} + {reply.shortCid ? ( + { + if (e.button === 2) return; + e.preventDefault(); + setIsReplyOpen(true); + setSelectedShortCid(reply.shortCid); + setSelectedParentCid(reply.cid); + }} title="Reply to this post">{reply.shortCid} + ) : ( + Pending + )}  
{ - thread.replyCount === 0 ? + (thread.replyCount + pendingReplyCounts[thread.cid]) === 0 ? ("No replies") - : thread.replyCount === 1 ? + : (thread.replyCount + pendingReplyCounts[thread.cid]) === 1 ? ("1 reply") - : thread.replyCount > 1 ? - (thread.replyCount + " replies") + : (thread.replyCount + pendingReplyCounts[thread.cid]) > 1 ? + ((thread.replyCount + pendingReplyCounts[thread.cid]) + " replies") : null } setSelectedThread(thread.cid)} className="button-mobile" >View Thread
- {renderedComments?.map((reply) => { + {displayedReplies?.map((reply) => { const replyMediaInfo = getCommentMediaInfo(reply); const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); return ( @@ -837,9 +909,21 @@ const Board = () => { onClick={() => handleAddressClick(reply.author.shortAddress)} > (u/ - - {reply.author.shortAddress} - + {reply.author?.shortAddress ? + ( + + {reply.author?.shortAddress} + + ) : ( + + {account?.author?.address.slice(0, 8) + "(...)"} + + ) + } ) 
@@ -847,15 +931,19 @@ const Board = () => { {getDate(reply.timestamp)}  {}} key={`mob-pl1-${reply.cid}`} onClick={() => {}} title="Link to this post">c/ - { - if (e.button === 2) return; - e.preventDefault(); - setIsReplyOpen(true); - setSelectedShortCid(reply.shortCid); - setSelectedParentCid(reply.cid); - }} title="Reply to this post">{reply.shortCid} - + {reply.shortCid ? ( + { + if (e.button === 2) return; + e.preventDefault(); + setIsReplyOpen(true); + setSelectedShortCid(reply.shortCid); + setSelectedParentCid(reply.cid); + }} title="Reply to this post">{reply.shortCid} + + ) : ( + Pending + )} {reply.link ? ( diff --git a/src/components/views/Thread.jsx b/src/components/views/Thread.jsx index 66649c11..fc3ee46d 100644 --- a/src/components/views/Thread.jsx +++ b/src/components/views/Thread.jsx @@ -1,8 +1,9 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; -import { useAccount, useAccountComment, useComment, usePublishComment } from '@plebbit/plebbit-react-hooks'; +import { useAccount, useAccountComments, useComment, usePublishComment } from '@plebbit/plebbit-react-hooks'; +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, PostForm, PostFormTable } from '../styled/Board.styled'; @@ -19,7 +20,6 @@ import getDate from '../../utils/getDate'; import handleAddressClick from '../../utils/handleAddressClick'; import handleQuoteClick from '../../utils/handleQuoteClick'; import handleStyleChange from '../../utils/handleStyleChange'; -import renderThreadComments from '../../utils/renderThreadComments'; import useClickForm from '../../hooks/useClickForm'; import useError from '../../hooks/useError'; import packageJson from '../../../package.json' @@ -35,7 +35,7 @@ const Thread = () => { isSettingsOpen, setIsSettingsOpen, setResolveCaptchaPromise, setPendingComment, - pendingCommentIndex, setPendingCommentIndex, + setPendingCommentIndex, selectedAddress, setSelectedAddress, setSelectedParentCid, setSelectedShortCid, @@ -65,9 +65,32 @@ const Thread = () => { const [errorMessage, setErrorMessage] = useState(null); useError(errorMessage, [errorMessage]); - const pendingComment = useAccountComment({commentIndex: pendingCommentIndex}); - const renderedComments = renderThreadComments(comment, pendingComment); + const flattenedReplies = useMemo(() => + flattenCommentsPages(comment.replies), [comment.replies] + ); + + + const filter = useMemo(() => ({ + parentCids: [ + selectedThread || 'n/a', ...flattenedReplies.map(reply => reply.cid) + ] + }), [flattenedReplies]); + + + const { accountComments } = useAccountComments({filter}); + + + const accountRepliesNotYetInCommentReplies = useMemo(() => { + const commentReplyCids = new Set(flattenedReplies.map(reply => reply.cid)) + return accountComments.filter(accountReply => !commentReplyCids.has(accountReply.cid)) + }, [flattenedReplies, accountComments]); + + + const sortedReplies = useMemo(() => [ + ...accountRepliesNotYetInCommentReplies, ...flattenedReplies + ].sort((a, b) => a.timestamp - b.timestamp + ), [accountRepliesNotYetInCommentReplies, flattenedReplies]); // temporary title from JSON, gets subplebbitAddress and threadCid from URL useEffect(() => { @@ -522,7 +545,7 @@ const Thread = () => { {comment.state === "fetching-ipns" ? (null) : (comment.replyCount === undefined ? () : (null))} - {renderedComments.map((reply, index) => { + {sortedReplies.map((reply, index) => { const replyMediaInfo = getCommentMediaInfo(reply); const fallbackImgUrl = "/assets/filedeleted-res.gif"; const shortParentCid = findShortParentCid(reply.parentCid, comment); @@ -758,7 +781,7 @@ const Thread = () => { {comment.replyCount === undefined ? : null} - {renderedComments.map((reply, index) => { + {sortedReplies.map((reply, index) => { const replyMediaInfo = getCommentMediaInfo(reply); const shortParentCid = findShortParentCid(reply.parentCid, comment); return ( @@ -798,7 +821,7 @@ const Thread = () => { data-tooltip-content={account?.author?.address} data-tooltip-place="top" > - {account?.author?.address.slice(0, 6) + "(...)"} + {account?.author?.address.slice(0, 8) + "(...)"} ) } diff --git a/src/utils/renderComments.js b/src/utils/renderComments.js deleted file mode 100644 index d47827fb..00000000 --- a/src/utils/renderComments.js +++ /dev/null @@ -1,30 +0,0 @@ -function renderComments(comments) { - let displayedCount = 0; - let omittedCount = 0; - - const renderComment = (comment) => { - const { replyCount, replies: { pages: { topAll: { comments: nestedComments = [] } = {} } = {} } = {} } = comment; - let renderedNestedComments = []; - - if (replyCount > 0 && nestedComments.length > 0) { - const result = renderComments(nestedComments); - renderedNestedComments = result.renderedComments; - omittedCount += result.omittedCount; - } - - const allComments = [comment, ...renderedNestedComments]; - const displayedComments = allComments.slice(0, 5 - displayedCount); - - displayedCount += displayedComments.length; - omittedCount += allComments.length - displayedComments.length; - - return displayedComments; - }; - - const renderedComments = comments?.flatMap(renderComment); - renderedComments?.sort((a, b) => a.timestamp - b.timestamp); - - return { renderedComments, omittedCount }; -} - -export default renderComments; \ No newline at end of file diff --git a/src/utils/renderThreadComments.js b/src/utils/renderThreadComments.js deleted file mode 100644 index 0579f381..00000000 --- a/src/utils/renderThreadComments.js +++ /dev/null @@ -1,24 +0,0 @@ -function renderThreadComments(comment, pendingComment = null) { - const nestedComments = comment?.replies?.pages?.topAll?.comments || {}; - const commentKeys = Object.keys(nestedComments); - - const renderedComments = commentKeys.map(key => { - const comment = nestedComments[key]; - const { replies: { pages: { topAll: { comments: childNestedComments = [] } = {} } = {} } = {} } = comment; - if (comment.replyCount > 0 && childNestedComments) { - const renderedNestedComments = renderThreadComments(comment); - return [comment, ...renderedNestedComments]; - } - return [comment]; - }).flat(); - - const sortedComments = renderedComments.sort((a, b) => a.timestamp - b.timestamp); - - if (pendingComment?.parentCid === comment?.cid) { - sortedComments.push(pendingComment); - } - - return sortedComments; -} - -export default renderThreadComments; \ No newline at end of file