From fd092f1bee9899a25167e156f7f82c40bfa9ba7e Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Thu, 30 May 2024 18:01:37 +0200 Subject: [PATCH] feat: add "hidden threads" counter and button in catalog and board view, store and hook --- .../board-buttons/board-buttons.module.css | 9 ++++ .../board-buttons/board-buttons.tsx | 17 +++++- .../comment-media/comment-media.tsx | 1 - .../post/post-desktop/post-desktop.tsx | 25 ++++----- .../post-menu-desktop/post-menu-desktop.tsx | 8 ++- src/components/post/post.tsx | 2 +- src/hooks/use-blocked-comments.ts | 31 +++++++++++ src/stores/useBlockedCommentsStore.ts | 15 ++++++ src/views/board/board.tsx | 53 +++++++++++++------ src/views/catalog/catalog.tsx | 32 ++++++++--- 10 files changed, 153 insertions(+), 40 deletions(-) create mode 100644 src/hooks/use-blocked-comments.ts create mode 100644 src/stores/useBlockedCommentsStore.ts diff --git a/src/components/board-buttons/board-buttons.module.css b/src/components/board-buttons/board-buttons.module.css index 06028aaf..8c3bb2ae 100644 --- a/src/components/board-buttons/board-buttons.module.css +++ b/src/components/board-buttons/board-buttons.module.css @@ -17,6 +17,15 @@ padding-bottom: 20px; } +.showBlockedButton { + color: var(--button-desktop-text-color); +} + +.showBlockedButton:hover { + color: var(--button-desktop-text-color-hover); + cursor: pointer; +} + @media (max-width: 640px) { .desktopBoardButtons { display: none; diff --git a/src/components/board-buttons/board-buttons.tsx b/src/components/board-buttons/board-buttons.tsx index 6daec02e..02724463 100644 --- a/src/components/board-buttons/board-buttons.tsx +++ b/src/components/board-buttons/board-buttons.tsx @@ -3,10 +3,12 @@ import { Link, useLocation, useParams } from 'react-router-dom'; import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks'; import styles from './board-buttons.module.css'; import { isAllView, isCatalogView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import useBlockedComments from '../../hooks/use-blocked-comments'; +import useBlockedCommentsStore from '../../stores/useBlockedCommentsStore'; interface BoardButtonsProps { isInAllView?: boolean; - address: string | undefined; + address?: string | undefined; isInCatalogView?: boolean; isInSubscriptionsView?: boolean; } @@ -105,6 +107,9 @@ export const DesktopBoardButtons = () => { const isInPostView = isPostPageView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname); + const { showBlockedComments, setShowBlockedComments } = useBlockedCommentsStore(); + const blockedComments = useBlockedComments(params?.subplebbitAddress); + return (

@@ -120,6 +125,16 @@ export const DesktopBoardButtons = () => { <> [] [ ] + {blockedComments?.length > 0 && ( + + {' '} + — Hidden threads: {blockedComments.length} [ + setShowBlockedComments(!showBlockedComments)}> + {showBlockedComments ? 'Back' : 'Show'} + + ] + + )} {!(isInAllView || isInSubscriptionsView) && ( [] diff --git a/src/components/comment-media/comment-media.tsx b/src/components/comment-media/comment-media.tsx index de48e6b6..b6cb6c69 100644 --- a/src/components/comment-media/comment-media.tsx +++ b/src/components/comment-media/comment-media.tsx @@ -15,7 +15,6 @@ interface MediaProps { linkWidth?: number; showThumbnail?: boolean; setShowThumbnail: (showThumbnail: boolean) => void; - toggleExpanded?: () => void; } const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, setShowThumbnail }: MediaProps) => { diff --git a/src/components/post/post-desktop/post-desktop.tsx b/src/components/post/post-desktop/post-desktop.tsx index c00c5e8c..526a6f13 100644 --- a/src/components/post/post-desktop/post-desktop.tsx +++ b/src/components/post/post-desktop/post-desktop.tsx @@ -20,7 +20,7 @@ import PostMenuDesktop from './post-menu-desktop/'; import { PostProps } from '../post'; import _ from 'lodash'; -const PostInfo = ({ openReplyModal, post, roles, isBlocked }: PostProps) => { +const PostInfo = ({ openReplyModal, post, roles, isHidden }: PostProps) => { const { t } = useTranslation(); const { author, cid, locked, pinned, parentCid, postCid, shortCid, state, subplebbitAddress, timestamp, title } = post || {}; const { address, displayName, shortAddress } = author || {}; @@ -44,7 +44,7 @@ const PostInfo = ({ openReplyModal, post, roles, isBlocked }: PostProps) => { return (
- {!isBlocked && ( + {!isHidden && ( @@ -92,7 +92,7 @@ const PostInfo = ({ openReplyModal, post, roles, isBlocked }: PostProps) => { )} - {!isInPostView && !isReply && !isBlocked && ( + {!isInPostView && !isReply && !isHidden && ( [
-
+
{!isInPostPageView && !isDescription && !isRules && ( )} - {link && !blocked && isValidURL(link) && } - - {!blocked && !content &&
} - {!blocked && content && } - {!blocked && !isDescription && !isRules && !isInPendingPostView && (replies.length > 5 || (pinned && replies.length > 0)) && !isInPostPageView && ( + {link && !isHidden && isValidURL(link) && } + + {!isHidden && !content &&
} + {!isHidden && content && } + {!isHidden && !isDescription && !isRules && !isInPendingPostView && (replies.length > 5 || (pinned && replies.length > 0)) && !isInPostPageView && ( @@ -261,7 +262,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps) )} )} - {!blocked && + {!isHidden && !(pinned && !isInPostPageView) && !isInPendingPostView && !isDescription && diff --git a/src/components/post/post-desktop/post-menu-desktop/post-menu-desktop.tsx b/src/components/post/post-desktop/post-menu-desktop/post-menu-desktop.tsx index 41d5b89b..55939c44 100644 --- a/src/components/post/post-desktop/post-menu-desktop/post-menu-desktop.tsx +++ b/src/components/post/post-desktop/post-menu-desktop/post-menu-desktop.tsx @@ -141,7 +141,13 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
{cid && subplebbitAddress && } {!isInPostPageView && !isDescription && !isRules && ( -
+
{ + blocked ? unblock() : block(); + handleClose(); + }} + > {blocked ? 'Unhide' : 'Hide'} {postCid === cid ? 'thread' : 'post'}
)} diff --git a/src/components/post/post.tsx b/src/components/post/post.tsx index d1f4e853..b8f6d424 100644 --- a/src/components/post/post.tsx +++ b/src/components/post/post.tsx @@ -6,7 +6,7 @@ import PostMobile from './post-mobile'; export interface PostProps { index?: number; - isBlocked?: boolean; + isHidden?: boolean; post?: any; reply?: any; roles?: Role[]; diff --git a/src/hooks/use-blocked-comments.ts b/src/hooks/use-blocked-comments.ts new file mode 100644 index 00000000..2d4ba625 --- /dev/null +++ b/src/hooks/use-blocked-comments.ts @@ -0,0 +1,31 @@ +import { useMemo } from 'react'; +import { Comment, useAccount, useComments } from '@plebbit/plebbit-react-hooks'; + +const useBlockedComments = (subplebbitAddress?: string): Comment[] => { + const account = useAccount(); + + const commentCids = useMemo(() => { + return Object.entries(account.blockedAddresses) + .filter(([address, isBlocked]) => { + const isValidCommentCid = !address || /^Qm[a-zA-Z0-9]{44}$/.test(address); + return isBlocked && isValidCommentCid; + }) + .map(([address]) => address); + }, [account]); + + const { comments } = useComments({ commentCids }); + + const filteredComments = useMemo(() => { + const validComments = comments.filter((comment): comment is Comment => comment !== undefined); + + if (!subplebbitAddress) { + return validComments; + } + + return validComments.filter((comment: Comment) => comment.subplebbitAddress === subplebbitAddress); + }, [comments, subplebbitAddress]); + + return filteredComments; +}; + +export default useBlockedComments; diff --git a/src/stores/useBlockedCommentsStore.ts b/src/stores/useBlockedCommentsStore.ts new file mode 100644 index 00000000..2ae1456c --- /dev/null +++ b/src/stores/useBlockedCommentsStore.ts @@ -0,0 +1,15 @@ +import { create } from 'zustand'; + +interface StoreState { + showBlockedComments: boolean; + setShowBlockedComments: (show: boolean) => void; + resetShowBlockedComments: () => void; +} + +const useStore = create((set) => ({ + showBlockedComments: false, + setShowBlockedComments: (show) => set({ showBlockedComments: show }), + resetShowBlockedComments: () => set({ showBlockedComments: false }), +})); + +export default useStore; diff --git a/src/views/board/board.tsx b/src/views/board/board.tsx index 123831e6..1274d93f 100644 --- a/src/views/board/board.tsx +++ b/src/views/board/board.tsx @@ -5,6 +5,8 @@ import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { useTranslation } from 'react-i18next'; import styles from './board.module.css'; import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import useBlockedCommentsStore from '../../stores/useBlockedCommentsStore'; +import useBlockedComments from '../../hooks/use-blocked-comments'; import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import useFeedStateString from '../../hooks/use-feed-state-string'; import useReplyModal from '../../hooks/use-reply-modal'; @@ -42,6 +44,19 @@ const Board = () => { const sortType = 'active'; const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType }); + const { resetShowBlockedComments, showBlockedComments, setShowBlockedComments } = useBlockedCommentsStore(); + const blockedComments = useBlockedComments(subplebbitAddress); + + useEffect(() => { + resetShowBlockedComments(); + }, [subplebbitAddress, resetShowBlockedComments]); + + useEffect(() => { + if (blockedComments.length === 0) { + setShowBlockedComments(false); + } + }, [blockedComments, setShowBlockedComments]); + const subplebbit = useSubplebbit({ subplebbitAddress }); const { createdAt, description, rules, shortAddress, state, suggested } = subplebbit || {}; const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : subplebbit?.title; @@ -96,7 +111,7 @@ const Board = () => {
{location.pathname.endsWith('/settings') && } {showReplyModal && activeCid && } - {feed.length > 0 && ( + {feed.length > 0 && !showBlockedComments && ( <> {rules && rules.length > 0 && } {((description && description.length > 0) || isInAllView) && ( @@ -111,23 +126,27 @@ const Board = () => { )} )} - { - const { deleted, locked, removed } = post || {}; - const isThreadClosed = deleted || locked || removed; + {showBlockedComments ? ( + blockedComments.map((comment) => ) + ) : ( + { + const { deleted, locked, removed } = post || {}; + const isThreadClosed = deleted || locked || removed; - return alert(t('thread_closed_alert')) : openReplyModal} />; - }} - useWindowScroll={true} - components={{ Footer }} - endReached={loadMore} - ref={virtuosoRef} - restoreStateFrom={lastVirtuosoState} - initialScrollTop={lastVirtuosoState?.scrollTop} - /> + return alert(t('thread_closed_alert')) : openReplyModal} />; + }} + useWindowScroll={true} + components={{ Footer }} + endReached={loadMore} + ref={virtuosoRef} + restoreStateFrom={lastVirtuosoState} + initialScrollTop={lastVirtuosoState?.scrollTop} + /> + )}
); }; diff --git a/src/views/catalog/catalog.tsx b/src/views/catalog/catalog.tsx index dc735a48..14a6a3a7 100644 --- a/src/views/catalog/catalog.tsx +++ b/src/views/catalog/catalog.tsx @@ -4,6 +4,8 @@ import { useTranslation } from 'react-i18next'; import { useAccount, Subplebbit, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks'; import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import useBlockedCommentsStore from '../../stores/useBlockedCommentsStore'; +import useBlockedComments from '../../hooks/use-blocked-comments'; import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import useFeedStateString from '../../hooks/use-feed-state-string'; import { useMultisubMetadata } from '../../hooks/use-default-subplebbits'; @@ -16,7 +18,7 @@ import _ from 'lodash'; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; -const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subplebbit: Subplebbit) => { +const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subplebbit: Subplebbit, includeDescriptionAndRules: boolean) => { const { t } = useTranslation(); const { address, createdAt, description, rules, shortAddress, suggested, title } = subplebbit || {}; const { avatarUrl } = suggested || {}; @@ -29,7 +31,7 @@ const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subp if (!isFeedLoaded) { return []; // prevent rules and description from appearing while feed is loading } - if (!description && !rules && !isInAllView) { + if (!includeDescriptionAndRules || (!description && !rules && !isInAllView)) { return feed; } const _feed = [...feed]; @@ -59,7 +61,7 @@ const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subp }); } return _feed; - }, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t, isInAllView, multisub]); + }, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t, isInAllView, multisub, includeDescriptionAndRules]); // Memoize rows calculation, ensuring it updates on changes to the modified feed or column count const rows = useMemo(() => { @@ -102,8 +104,25 @@ const Catalog = () => { // eslint-disable-next-line const postsPerPage = useMemo(() => (columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25), []); + const blockedComments = useBlockedComments(subplebbitAddress); + const blockedCommentsAddresses = useMemo(() => blockedComments.map((comment) => comment.cid), [blockedComments]); + const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType: 'active', postsPerPage }); + const filteredFeed = useMemo(() => feed.filter((comment) => !blockedCommentsAddresses.includes(comment.cid)), [feed, blockedCommentsAddresses]); + + const { resetShowBlockedComments, showBlockedComments, setShowBlockedComments } = useBlockedCommentsStore(); + + useEffect(() => { + resetShowBlockedComments(); + }, [subplebbitAddress, resetShowBlockedComments]); + + useEffect(() => { + if (blockedComments.length === 0) { + setShowBlockedComments(false); + } + }, [blockedComments, setShowBlockedComments]); + const subplebbit = useSubplebbit({ subplebbitAddress }); const { shortAddress, state, title } = subplebbit || {}; const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading'); @@ -121,7 +140,7 @@ const Catalog = () => { const Footer = () => { let footerContent; - if (feed.length === 0) { + if ((showBlockedComments ? blockedComments : filteredFeed).length === 0) { footerContent = t('no_posts'); } if (hasMore || subplebbitAddresses.length === 0) { @@ -130,10 +149,9 @@ const Catalog = () => { return
{footerContent}
; }; - const isFeedloaded = feed.length > 0 || state === 'failed'; + const isFeedLoaded = (showBlockedComments ? blockedComments : filteredFeed).length > 0 || state === 'failed'; - // split feed into rows - const rows = useFeedRows(columnCount, feed, isFeedloaded, subplebbit); + const rows = useFeedRows(columnCount, showBlockedComments ? blockedComments : filteredFeed, isFeedLoaded, subplebbit, !showBlockedComments); // save the last Virtuoso state to restore it when navigating back const virtuosoRef = useRef(null);