mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat: hide button for posts soft hides them persistently without blocking the cid
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
<>
|
||||||
|
[
|
||||||
|
<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
|
||||||
|
|||||||
Reference in New Issue
Block a user