feat: hide button for posts soft hides them persistently without blocking the cid

This commit is contained in:
Tom (plebeius.eth)
2024-06-13 17:50:05 +02:00
parent 0abc5a3841
commit 67e3bc8d68
4 changed files with 123 additions and 12 deletions
+6 -5
View File
@@ -1,14 +1,15 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom'; import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAccount, useBlock, useComment } from '@plebbit/plebbit-react-hooks'; import { Comment, useAccount, useComment } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css'; import styles from '../../views/post/post.module.css';
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils'; import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
import { isValidURL } from '../../lib/utils/url-utils'; import { isValidURL } from '../../lib/utils/url-utils';
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useEditCommentPrivileges from '../../hooks/use-author-privileges'; import useEditCommentPrivileges from '../../hooks/use-author-privileges';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useHide from '../../hooks/use-hide';
import useReplies from '../../hooks/use-replies'; import useReplies from '../../hooks/use-replies';
import useStateString from '../../hooks/use-state-string'; import useStateString from '../../hooks/use-state-string';
import CommentMedia from '../comment-media'; import CommentMedia from '../comment-media';
@@ -220,8 +221,8 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
const isInPendingPostView = isPendingPostView(location.pathname, params); const isInPendingPostView = isPendingPostView(location.pathname, params);
const isInPostPageView = isPostPageView(location.pathname, params); const isInPostPageView = isPostPageView(location.pathname, params);
const { blocked, unblock, block } = useBlock({ cid }); const { hidden, unhide, hide } = useHide({ cid });
const isHidden = blocked && !isInPostPageView; const isHidden = hidden && !isInPostPageView;
const replies = useReplies(post); const replies = useReplies(post);
const visiblelinksCount = useCountLinksInReplies(post, 5); const visiblelinksCount = useCountLinksInReplies(post, 5);
@@ -250,7 +251,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
<div className={isHidden ? styles.postDesktopBlocked : ''}> <div className={isHidden ? styles.postDesktopBlocked : ''}>
{!isInPostPageView && !isDescription && !isRules && showReplies && ( {!isInPostPageView && !isDescription && !isRules && showReplies && (
<span className={styles.hideButtonWrapper}> <span className={styles.hideButtonWrapper}>
<span className={`${styles.hideButton} ${blocked ? styles.unhideThread : styles.hideThread}`} onClick={blocked ? unblock : block} /> <span className={`${styles.hideButton} ${hidden ? styles.unhideThread : styles.hideThread}`} onClick={hidden ? unhide : hide} />
</span> </span>
)} )}
{link && !isHidden && isValidURL(link) && <PostMedia post={post} />} {link && !isHidden && isValidURL(link) && <PostMedia post={post} />}
@@ -8,6 +8,7 @@ import styles from './post-menu-desktop.module.css';
import { getCommentMediaInfo } from '../../../lib/utils/media-utils'; import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-utils'; import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-utils';
import { isAllView, isCatalogView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils'; import { isAllView, isCatalogView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils';
import useHide from '../../../hooks/use-hide';
import _ from 'lodash'; import _ from 'lodash';
interface PostMenuDesktopProps { interface PostMenuDesktopProps {
@@ -102,14 +103,34 @@ const ViewOnButton = ({ cid, isDescription, isInAllView, isInSubscriptionsView,
); );
}; };
const BlockUserButton = ({ address }: { address: string }) => {
const { blocked, unblock, block } = useBlock({ address });
return (
<div className={styles.postMenuItem} onClick={blocked ? unblock : block}>
{blocked ? 'Unblock' : 'Block'} user
</div>
);
};
const BlockBoardButton = ({ address }: { address: string }) => {
const { blocked, unblock, block } = useBlock({ address });
return (
<div className={styles.postMenuItem} onClick={blocked ? unblock : block}>
{blocked ? 'Unblock' : 'Block'} board
</div>
);
};
const PostMenuDesktop = ({ post }: { post: Comment }) => { const PostMenuDesktop = ({ post }: { post: Comment }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { cid, isDescription, isRules, link, postCid, subplebbitAddress } = post || {}; const { author, cid, isDescription, isRules, link, postCid, subplebbitAddress } = post || {};
const commentMediaInfo = getCommentMediaInfo(post); const commentMediaInfo = getCommentMediaInfo(post);
const { thumbnail, type, url } = commentMediaInfo || {}; const { thumbnail, type, url } = commentMediaInfo || {};
const [menuBtnRotated, setMenuBtnRotated] = useState(false); const [menuBtnRotated, setMenuBtnRotated] = useState(false);
const { blocked, unblock, block } = useBlock({ cid }); const { hidden, unhide, hide } = useHide({ cid });
const location = useLocation(); const location = useLocation();
const params = useParams(); const params = useParams();
@@ -154,11 +175,11 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
<div <div
className={styles.postMenuItem} className={styles.postMenuItem}
onClick={() => { onClick={() => {
blocked ? unblock() : block(); hidden ? unhide() : hide();
handleClose(); handleClose();
}} }}
> >
{blocked ? (postCid === cid ? t('unhide_thread') : t('unhide_post')) : postCid === cid ? t('hide_thread') : t('hide_post')} {hidden ? (postCid === cid ? t('unhide_thread') : t('unhide_post')) : postCid === cid ? t('hide_thread') : t('hide_post')}
</div> </div>
)} )}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />} {link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
@@ -171,6 +192,8 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
subplebbitAddress={subplebbitAddress} subplebbitAddress={subplebbitAddress}
onClose={handleClose} onClose={handleClose}
/> />
<BlockUserButton address={author?.address} />
{(isInAllView || isInSubscriptionsView) && <BlockBoardButton address={subplebbitAddress} />}
</div> </div>
</FloatingFocusManager>, </FloatingFocusManager>,
document.body, document.body,
+62
View File
@@ -1 +1,63 @@
import { useEffect, useCallback } from 'react';
import { create } from 'zustand';
import localForageLru from '@plebbit/plebbit-react-hooks/dist/lib/localforage-lru/index.js'; import localForageLru from '@plebbit/plebbit-react-hooks/dist/lib/localforage-lru/index.js';
interface HideStoreState {
hiddenCids: { [key: string]: boolean };
hide: (cid: string) => void;
unhide: (cid: string) => void;
}
const hideStore = localForageLru.createInstance({
name: 'hideStore',
size: 1000,
});
const useHideStore = create<HideStoreState>((set) => ({
hiddenCids: {},
hide: (cid: string) => {
set((state) => ({
hiddenCids: { ...state.hiddenCids, [cid]: true },
}));
hideStore.setItem(cid, true);
},
unhide: (cid: string) => {
set((state) => {
const newHiddenCids = { ...state.hiddenCids };
delete newHiddenCids[cid];
return { hiddenCids: newHiddenCids };
});
hideStore.removeItem(cid);
},
}));
const initializeHideStore = async () => {
const entries: [string, boolean][] = await hideStore.entries();
const hiddenCids: { [key: string]: boolean } = {};
entries.forEach(([key, value]) => {
hiddenCids[key] = value;
});
useHideStore.setState((state) => ({
hiddenCids: { ...hiddenCids, ...state.hiddenCids },
}));
};
const useHide = ({ cid }: { cid: string }) => {
const hiddenCids = useHideStore((state) => state.hiddenCids);
const hide = useHideStore((state) => state.hide);
const unhide = useHideStore((state) => state.unhide);
const hidden = !!hiddenCids[cid];
useEffect(() => {
initializeHideStore();
}, []);
const hideCallback = useCallback(() => hide(cid), [hide, cid]);
const unhideCallback = useCallback(() => unhide(cid), [unhide, cid]);
return { hidden, hide: hideCallback, unhide: unhideCallback };
};
export default useHide;
+28 -3
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef } from 'react';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { useAccount, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks'; import { useAccount, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import styles from './board.module.css'; import styles from './board.module.css';
@@ -87,15 +87,40 @@ const Board = () => {
</div> </div>
); );
const { blocked, unblock } = useBlock({ address: subplebbitAddress });
const Footer = () => { const Footer = () => {
let footerContent; let footerContent;
if (feed.length === 0) { if (feed.length === 0) {
footerContent = t('no_posts'); if (blocked) {
footerContent = 'you have blocked this board';
} else {
footerContent = t('no_posts');
}
} }
if (hasMore || subplebbitAddresses.length === 0) { if (hasMore || subplebbitAddresses.length === 0) {
footerContent = loadingString; footerContent = loadingString;
} }
return <div className={styles.footer}>{footerContent}</div>; return (
<div className={styles.footer}>
{footerContent}
{blocked && (
<>
&nbsp;&nbsp;[
<span
className='button'
onClick={() => {
unblock();
reset();
}}
>
Unblock
</span>
]
</>
)}
</div>
);
}; };
// save the last Virtuoso state to restore it when navigating back // save the last Virtuoso state to restore it when navigating back