mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(catalog): hide threads across board catalogs
This commit is contained in:
@@ -5,6 +5,7 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { DesktopBoardButtons, MobileBoardButtons } from '../board-buttons';
|
||||
import useThreadLiveUpdatesStore from '../../../stores/use-thread-live-updates-store';
|
||||
import useHiddenCatalogThreadsStore from '../../../stores/use-hidden-catalog-threads-store';
|
||||
import { clearStableLastVisitTimeFilterName, LAST_VISIT_STORAGE_KEY } from '../../../lib/utils/time-filter-utils';
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
@@ -17,6 +18,8 @@ type DirectoryEntry = {
|
||||
};
|
||||
|
||||
const testState = vi.hoisted(() => ({
|
||||
account: { blockedCids: {} as Record<string, boolean>, subscriptions: [] as string[] },
|
||||
accountCommunityAddresses: [] as string[],
|
||||
accountComment: undefined as { communityAddress?: string } | undefined,
|
||||
alertThresholdUnit: 'minutes' as 'hours' | 'minutes',
|
||||
alertThresholdValue: 5,
|
||||
@@ -28,6 +31,8 @@ const testState = vi.hoisted(() => ({
|
||||
enableInfiniteScroll: false,
|
||||
filter: 'all' as 'all' | 'nsfw' | 'sfw',
|
||||
filteredCount: 0,
|
||||
filteredDirectoryAddresses: ['music-posting.eth', 'tech-posting.eth'] as string[],
|
||||
hiddenThreadsByScope: {} as Record<string, Array<{ cid: string }>>,
|
||||
imageSize: 'Small' as 'Small' | 'Large',
|
||||
isMobile: true,
|
||||
linkCount: 3,
|
||||
@@ -73,7 +78,7 @@ vi.mock('react-router-dom', async () => {
|
||||
});
|
||||
|
||||
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
|
||||
useAccount: () => undefined,
|
||||
useAccount: () => testState.account,
|
||||
useAccountComment: () => testState.accountComment,
|
||||
useComment: ({ commentCid }: { commentCid?: string }) => (commentCid ? testState.commentsByCid[commentCid] : undefined),
|
||||
useSubscribe: () => ({
|
||||
@@ -83,6 +88,26 @@ vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../../hooks/use-account-community-addresses', () => ({
|
||||
useAccountCommunityAddresses: () => testState.accountCommunityAddresses,
|
||||
}));
|
||||
|
||||
vi.mock('../../../hooks/use-filtered-directory-addresses', () => ({
|
||||
useFilteredDirectoryAddresses: () => testState.filteredDirectoryAddresses,
|
||||
}));
|
||||
|
||||
vi.mock('../../../hooks/use-hidden-catalog-threads', () => ({
|
||||
default: ({ communityAddresses }: { communityAddresses: string[] }) => {
|
||||
const scopeKey = communityAddresses.filter(Boolean).slice().sort().join('\u0000');
|
||||
return {
|
||||
hiddenCatalogThreads: testState.hiddenThreadsByScope[scopeKey] || [],
|
||||
hiddenThreadCandidates: testState.hiddenThreadsByScope[scopeKey] || [],
|
||||
isLoadingHiddenCatalogThreads: false,
|
||||
scopeKey,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../hooks/use-post-page-number', () => ({
|
||||
usePostPageNumber: () => testState.pageNumber,
|
||||
}));
|
||||
@@ -117,10 +142,13 @@ vi.mock('../../../stores/use-feed-reset-store', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores/use-sorting-store', () => ({
|
||||
default: () => ({
|
||||
setSortType: testState.setSortTypeMock,
|
||||
sortType: testState.sortType,
|
||||
}),
|
||||
default: (selector?: (state: { setSortType: typeof testState.setSortTypeMock; sortType: typeof testState.sortType }) => unknown) => {
|
||||
const state = {
|
||||
setSortType: testState.setSortTypeMock,
|
||||
sortType: testState.sortType,
|
||||
};
|
||||
return selector ? selector(state) : state;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores/use-all-feed-filter-store', () => ({
|
||||
@@ -223,9 +251,13 @@ const setTrackedInputValue = (input: HTMLInputElement, value: string) => {
|
||||
descriptor?.set?.call(input, value);
|
||||
};
|
||||
|
||||
const getScopeKey = (communityAddresses: string[]) => communityAddresses.filter(Boolean).slice().sort().join('\u0000');
|
||||
|
||||
describe('BoardButtons', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
testState.account = { blockedCids: {}, subscriptions: [] };
|
||||
testState.accountCommunityAddresses = [];
|
||||
testState.accountComment = undefined;
|
||||
testState.alertThresholdUnit = 'minutes';
|
||||
testState.alertThresholdValue = 5;
|
||||
@@ -237,6 +269,8 @@ describe('BoardButtons', () => {
|
||||
testState.enableInfiniteScroll = false;
|
||||
testState.filter = 'all';
|
||||
testState.filteredCount = 0;
|
||||
testState.filteredDirectoryAddresses = ['music-posting.eth', 'tech-posting.eth'];
|
||||
testState.hiddenThreadsByScope = {};
|
||||
testState.imageSize = 'Small';
|
||||
testState.isMobile = true;
|
||||
testState.linkCount = 3;
|
||||
@@ -248,6 +282,7 @@ describe('BoardButtons', () => {
|
||||
testState.subscribed = false;
|
||||
testState.viewMode = 'compact';
|
||||
useThreadLiveUpdatesStore.getState().resetState();
|
||||
useHiddenCatalogThreadsStore.setState({ hiddenCommentsByCid: {}, scopeHiddenThreadsCounts: {}, shownScopeKey: null });
|
||||
clearStableLastVisitTimeFilterName();
|
||||
localStorage.setItem(LAST_VISIT_STORAGE_KEY, String(Date.now()));
|
||||
Object.defineProperty(globalThis, 'alert', {
|
||||
@@ -274,6 +309,7 @@ describe('BoardButtons', () => {
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
useHiddenCatalogThreadsStore.setState({ hiddenCommentsByCid: {}, scopeHiddenThreadsCounts: {}, shownScopeKey: null });
|
||||
clearStableLastVisitTimeFilterName();
|
||||
localStorage.clear();
|
||||
});
|
||||
@@ -340,6 +376,51 @@ describe('BoardButtons', () => {
|
||||
expect(testState.resetMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('renders the desktop hidden-thread catalog control immediately after refresh', async () => {
|
||||
testState.hiddenThreadsByScope[getScopeKey(['music-posting.eth'])] = [{ cid: 'hidden-thread' }];
|
||||
|
||||
await renderWithRoute(createElement(DesktopBoardButtons), '/mu/catalog');
|
||||
|
||||
const control = container.querySelector<HTMLElement>('[data-testid="hidden-threads-control"]');
|
||||
expect(control?.dataset.placement).toBe('desktop');
|
||||
expect(control?.textContent).toContain('Hidden threads: 1');
|
||||
expect(control?.querySelector('strong')?.textContent).toBe('1');
|
||||
expect(container.textContent?.indexOf('refresh')).toBeLessThan(container.textContent?.indexOf('Hidden threads') ?? -1);
|
||||
|
||||
await clickButton('Show');
|
||||
|
||||
expect(useHiddenCatalogThreadsStore.getState().shownScopeKey).toBe(getScopeKey(['music-posting.eth']));
|
||||
expect(container.querySelector<HTMLElement>('[data-testid="hidden-threads-control"]')?.textContent).toContain('Back');
|
||||
});
|
||||
|
||||
it('renders the mobile hidden-thread catalog control below the refresh row', async () => {
|
||||
testState.hiddenThreadsByScope[getScopeKey(['music-posting.eth'])] = [{ cid: 'hidden-thread' }, { cid: 'second-hidden-thread' }];
|
||||
|
||||
await renderWithRoute(createElement(MobileBoardButtons), '/mu/catalog');
|
||||
|
||||
const control = container.querySelector<HTMLElement>('[data-testid="hidden-threads-control"]');
|
||||
expect(control?.dataset.placement).toBe('mobile');
|
||||
expect(control?.className).toContain('mobileHiddenCatalogThreadsToggle');
|
||||
expect(control?.textContent).toContain('Hidden threads: 2');
|
||||
expect(container.textContent?.indexOf('refresh')).toBeLessThan(container.textContent?.indexOf('Hidden threads') ?? -1);
|
||||
});
|
||||
|
||||
it('counts hidden threads for every board in the all catalog scope', async () => {
|
||||
testState.hiddenThreadsByScope[getScopeKey(['music-posting.eth', 'tech-posting.eth'])] = [{ cid: 'hidden-music' }, { cid: 'hidden-tech' }];
|
||||
|
||||
await renderWithRoute(createElement(DesktopBoardButtons), '/all/catalog?t=24h');
|
||||
|
||||
expect(container.querySelector<HTMLElement>('[data-testid="hidden-threads-control"]')?.textContent).toContain('Hidden threads: 2');
|
||||
});
|
||||
|
||||
it('uses the catalog-provided hidden count when the blocked cid lookup has not resolved yet', async () => {
|
||||
useHiddenCatalogThreadsStore.getState().setScopeHiddenThreadsCount(getScopeKey(['music-posting.eth']), 1);
|
||||
|
||||
await renderWithRoute(createElement(DesktopBoardButtons), '/mu/catalog');
|
||||
|
||||
expect(container.querySelector<HTMLElement>('[data-testid="hidden-threads-control"]')?.textContent).toContain('Hidden threads: 1');
|
||||
});
|
||||
|
||||
it('preserves the current multiboard time filter when searching OPs', async () => {
|
||||
localStorage.setItem(LAST_VISIT_STORAGE_KEY, String(Date.now() - 3 * 24 * 60 * 60 * 1000));
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.mobileBoardButtons a:not([class~='button']), .desktopBoardButtons a:not([class~='button']) {
|
||||
.mobileBoardButtons a:not([class~='button']),
|
||||
.desktopBoardButtons a:not([class~='button']) {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@
|
||||
}
|
||||
|
||||
.desktopBoardButtons::after {
|
||||
content: "";
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
@@ -107,6 +108,30 @@
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.hiddenCatalogThreadsToggle {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.desktopHiddenCatalogThreadsToggle {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.hiddenCatalogThreadsToggleAction {
|
||||
all: unset;
|
||||
color: var(--button-desktop-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hiddenCatalogThreadsToggleAction:hover {
|
||||
color: var(--button-desktop-text-color-hover);
|
||||
}
|
||||
|
||||
.mobileHiddenCatalogThreadsToggle {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.mobileBoardButtons {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useComment, useSubscribe } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { useAccount, useComment, useSubscribe } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { isAllView, isCatalogView, isModView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { usePostPageNumber } from '../../hooks/use-post-page-number';
|
||||
import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
|
||||
import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses';
|
||||
import { useFilteredDirectoryAddresses } from '../../hooks/use-filtered-directory-addresses';
|
||||
import { getBoardPath, isDirectoryBoard } from '../../lib/utils/route-utils';
|
||||
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
|
||||
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
|
||||
import useHiddenCatalogThreads from '../../hooks/use-hidden-catalog-threads';
|
||||
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
|
||||
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
|
||||
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||
import useHiddenCatalogThreadsStore from '../../stores/use-hidden-catalog-threads-store';
|
||||
import useSortingStore from '../../stores/use-sorting-store';
|
||||
import useAllFeedFilterStore from '../../stores/use-all-feed-filter-store';
|
||||
import useModQueueStore from '../../stores/use-mod-queue-store';
|
||||
@@ -37,6 +42,8 @@ interface BoardButtonsProps {
|
||||
isTopbar?: boolean;
|
||||
}
|
||||
|
||||
const EMPTY_COMMUNITY_ADDRESSES: string[] = [];
|
||||
|
||||
const getMultiboardPath = ({
|
||||
isInAllView,
|
||||
isInCatalogView,
|
||||
@@ -190,6 +197,60 @@ export const RefreshButton = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const HiddenCatalogThreadsToggle = ({
|
||||
address,
|
||||
isInAllView = false,
|
||||
isInCatalogView = false,
|
||||
isInSubscriptionsView = false,
|
||||
isInModView = false,
|
||||
isMobilePlacement = false,
|
||||
}: BoardButtonsProps & { isMobilePlacement?: boolean }) => {
|
||||
const account = useAccount();
|
||||
const accountCommunityAddresses = useAccountCommunityAddresses();
|
||||
const filteredDirectoryAddresses = useFilteredDirectoryAddresses();
|
||||
const sortType = useSortingStore((state) => state.sortType);
|
||||
const toggleShownScopeKey = useHiddenCatalogThreadsStore((state) => state.toggleShownScopeKey);
|
||||
const communityAddresses = useMemo(() => {
|
||||
if (isInAllView) {
|
||||
return filteredDirectoryAddresses;
|
||||
}
|
||||
if (isInSubscriptionsView) {
|
||||
return account?.subscriptions?.filter(Boolean) || EMPTY_COMMUNITY_ADDRESSES;
|
||||
}
|
||||
if (isInModView) {
|
||||
return accountCommunityAddresses;
|
||||
}
|
||||
|
||||
return address ? [address] : EMPTY_COMMUNITY_ADDRESSES;
|
||||
}, [account?.subscriptions, accountCommunityAddresses, address, filteredDirectoryAddresses, isInAllView, isInModView, isInSubscriptionsView]);
|
||||
const { hiddenCatalogThreads, isLoadingHiddenCatalogThreads, scopeKey } = useHiddenCatalogThreads({
|
||||
communityAddresses,
|
||||
sortType: sortType === 'new' ? 'new' : 'active',
|
||||
});
|
||||
const storedHiddenThreadsCount = useHiddenCatalogThreadsStore((state) => state.scopeHiddenThreadsCounts[scopeKey] || 0);
|
||||
const hiddenThreadsCount = Math.max(hiddenCatalogThreads.length, storedHiddenThreadsCount);
|
||||
const requestedShowHiddenThreads = useHiddenCatalogThreadsStore((state) => state.shownScopeKey === scopeKey);
|
||||
const showHiddenThreads = requestedShowHiddenThreads && (hiddenThreadsCount > 0 || isLoadingHiddenCatalogThreads);
|
||||
|
||||
if (!isInCatalogView || hiddenThreadsCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`${styles.hiddenCatalogThreadsToggle} ${isMobilePlacement ? styles.mobileHiddenCatalogThreadsToggle : styles.desktopHiddenCatalogThreadsToggle}`}
|
||||
data-testid='hidden-threads-control'
|
||||
data-placement={isMobilePlacement ? 'mobile' : 'desktop'}
|
||||
>
|
||||
— Hidden threads: <strong>{hiddenThreadsCount}</strong> [
|
||||
<button type='button' className={styles.hiddenCatalogThreadsToggleAction} data-testid='hidden-threads-toggle' onClick={() => toggleShownScopeKey(scopeKey)}>
|
||||
{showHiddenThreads ? 'Back' : 'Show'}
|
||||
</button>
|
||||
]
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const UpdateButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const requestUpdate = useThreadLiveUpdatesStore((state) => state.requestUpdate);
|
||||
@@ -504,6 +565,14 @@ export const MobileBoardButtons = () => {
|
||||
<ArchiveButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
|
||||
{showBottomButton && <BottomButton />}
|
||||
<RefreshButton />
|
||||
<HiddenCatalogThreadsToggle
|
||||
address={communityAddress}
|
||||
isInAllView={isInAllView}
|
||||
isInCatalogView={isInCatalogView}
|
||||
isInSubscriptionsView={isInSubscriptionsView}
|
||||
isInModView={isInModView}
|
||||
isMobilePlacement={true}
|
||||
/>
|
||||
{searchText ? (
|
||||
<span className={styles.filteredThreadsCount}>
|
||||
{' '}
|
||||
@@ -705,6 +774,18 @@ export const DesktopBoardButtons = () => {
|
||||
</>
|
||||
)}{' '}
|
||||
[<RefreshButton />]
|
||||
{isInCatalogView && (
|
||||
<>
|
||||
{' '}
|
||||
<HiddenCatalogThreadsToggle
|
||||
address={communityAddress}
|
||||
isInAllView={isInAllView}
|
||||
isInCatalogView={isInCatalogView}
|
||||
isInSubscriptionsView={isInSubscriptionsView}
|
||||
isInModView={isInModView}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!(isInAllView || isInSubscriptionsView) && (
|
||||
<>
|
||||
{' '}
|
||||
|
||||
@@ -492,14 +492,15 @@ describe('CatalogRow', () => {
|
||||
testState.hiddenCids = new Set(['hidden-1']);
|
||||
testState.showOPComment = false;
|
||||
|
||||
const hiddenPost: TestComment = {
|
||||
author: { address: 'hidden-author', displayName: 'Ghost' },
|
||||
cid: 'hidden-1',
|
||||
content: 'hidden text',
|
||||
link: 'https://example.com/hidden.png',
|
||||
communityAddress: 'music-posting.eth',
|
||||
};
|
||||
const posts: TestComment[] = [
|
||||
{
|
||||
author: { address: 'hidden-author', displayName: 'Ghost' },
|
||||
cid: 'hidden-1',
|
||||
content: 'hidden text',
|
||||
link: 'https://example.com/hidden.png',
|
||||
communityAddress: 'music-posting.eth',
|
||||
},
|
||||
hiddenPost,
|
||||
{
|
||||
author: { address: 'text-author', displayName: 'Anon' },
|
||||
cid: 'text-1',
|
||||
@@ -516,7 +517,13 @@ describe('CatalogRow', () => {
|
||||
expect(links).toContain('/mu/thread/hidden-1');
|
||||
expect(links).toContain('/mu/thread/text-1');
|
||||
expect(container.textContent).toContain('(hidden)');
|
||||
expect(container.textContent).not.toContain('hidden text');
|
||||
expect(container.textContent).toContain('Text title: Plain thread body');
|
||||
|
||||
await renderWithRouter(createElement(CatalogRow, { row: [hiddenPost], showHiddenPosts: true }), '/mu/catalog');
|
||||
|
||||
expect(container.textContent).toContain('hidden text');
|
||||
expect(container.textContent).not.toContain('(hidden)');
|
||||
});
|
||||
|
||||
it('preserves literal catalog teaser markers without applying body markdown styles', async () => {
|
||||
|
||||
@@ -118,7 +118,7 @@ export const CatalogPostMedia = ({ cid, commentMediaInfo, linkWidth, linkHeight,
|
||||
|
||||
// Memoize CatalogPost to prevent rerenders when parent rerenders due to updatingState
|
||||
const CatalogPost = memo(
|
||||
({ matchedFilterColor, post }: { matchedFilterColor?: string; post: Comment }) => {
|
||||
({ matchedFilterColor, post, showHiddenPost = false }: { matchedFilterColor?: string; post: Comment; showHiddenPost?: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const resolvedPost = useMemo(() => withResolvedCommentCommunityAddress(post), [post]);
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, spoiler, communityAddress, timestamp, title, thumbnailUrl } =
|
||||
@@ -129,7 +129,8 @@ const CatalogPost = memo(
|
||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
const { hidden } = useHide({ cid });
|
||||
const { hidden } = useHide({ cid, comment: resolvedPost });
|
||||
const shouldMaskPost = hidden && !showHiddenPost;
|
||||
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
@@ -207,8 +208,8 @@ const CatalogPost = memo(
|
||||
const lastReplyAuthorBadge = getAuthorBadge({ address: lastReply?.author?.address, role: lastReplyAuthorRole });
|
||||
|
||||
const postContent = (
|
||||
<div className={`${styles.teaser} ${hidden && styles.hidden}`}>
|
||||
{hidden ? (
|
||||
<div className={`${styles.teaser} ${shouldMaskPost && styles.hidden}`}>
|
||||
{shouldMaskPost ? (
|
||||
<b>({t('hidden')})</b>
|
||||
) : (
|
||||
<>
|
||||
@@ -238,7 +239,7 @@ const CatalogPost = memo(
|
||||
<>
|
||||
<div className={`${styles.post} ${imageSize === 'Large' ? styles.large : ''}`} style={CSSProperties}>
|
||||
<div onMouseOver={() => setHoveredCid(cid)} onMouseLeave={() => setHoveredCid(null)}>
|
||||
{hidden ? (
|
||||
{shouldMaskPost ? (
|
||||
<Link to={postLink}>
|
||||
<span className={styles.hiddenThumbnail} />
|
||||
</Link>
|
||||
@@ -247,7 +248,7 @@ const CatalogPost = memo(
|
||||
{shouldShowSnow() && hasThumbnail && <img src='assets/xmashat.gif' className={styles.xmasHat} alt='' />}
|
||||
<Link to={postLink}>
|
||||
<div
|
||||
className={`${styles.mediaPaddingWrapper} ${hidden && styles.hidden}`}
|
||||
className={`${styles.mediaPaddingWrapper} ${shouldMaskPost && styles.hidden}`}
|
||||
ref={refs.setReference}
|
||||
onMouseOver={() => (timeoutRef.current = setTimeout(() => setShowPortal(true), 250))}
|
||||
onMouseLeave={() => {
|
||||
@@ -352,7 +353,8 @@ const CatalogPost = memo(
|
||||
prev?.linkWidth === next?.linkWidth &&
|
||||
prev?.linkHeight === next?.linkHeight &&
|
||||
prevCommunityAddress === nextCommunityAddress &&
|
||||
prevProps.matchedFilterColor === nextProps.matchedFilterColor
|
||||
prevProps.matchedFilterColor === nextProps.matchedFilterColor &&
|
||||
prevProps.showHiddenPost === nextProps.showHiddenPost
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -362,20 +364,25 @@ interface CatalogRowProps {
|
||||
index?: number;
|
||||
matchedFilterColors?: Map<string, string>;
|
||||
row: Comment[];
|
||||
showHiddenPosts?: boolean;
|
||||
}
|
||||
|
||||
const CatalogRow = memo(
|
||||
({ estimatedHeight, matchedFilterColors, row }: CatalogRowProps) => {
|
||||
({ estimatedHeight, matchedFilterColors, row, showHiddenPosts = false }: CatalogRowProps) => {
|
||||
return (
|
||||
<div className={styles.row} data-pretext-height={estimatedHeight}>
|
||||
{row.map((post, index) => (
|
||||
<CatalogPost key={post?.cid || index} matchedFilterColor={matchedFilterColors?.get(post?.cid || '')} post={post} />
|
||||
<CatalogPost key={post?.cid || index} matchedFilterColor={matchedFilterColors?.get(post?.cid || '')} post={post} showHiddenPost={showHiddenPosts} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
if (prevProps.estimatedHeight !== nextProps.estimatedHeight || prevProps.row.length !== nextProps.row.length) {
|
||||
if (
|
||||
prevProps.estimatedHeight !== nextProps.estimatedHeight ||
|
||||
prevProps.row.length !== nextProps.row.length ||
|
||||
prevProps.showHiddenPosts !== nextProps.showHiddenPosts
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
||||
|
||||
const { hidden, unhide, hide } = useHide({ cid: cid || '' });
|
||||
const { hidden, unhide, hide } = useHide({ cid: cid || '', comment: postMenu.comment });
|
||||
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
|
||||
@@ -51,6 +51,7 @@ async function copyUserIdSafe(address: string): Promise<void> {
|
||||
|
||||
type HideButtonProps = {
|
||||
cid?: string;
|
||||
comment?: Comment;
|
||||
isReply?: boolean;
|
||||
postCid?: string;
|
||||
onClose?: () => void;
|
||||
@@ -267,9 +268,9 @@ const DeletePostButton = ({ post, onClose }: DeletePostButtonProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const HidePostButton = ({ cid, isReply, onClose, postCid }: HideButtonProps) => {
|
||||
const HidePostButton = ({ cid, comment, isReply, onClose, postCid }: HideButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { hide, hidden, unhide } = useHide({ cid: cid || '' });
|
||||
const { hide, hidden, unhide } = useHide({ cid: cid || '', comment });
|
||||
const isInPostView = isPostPageView(useLocation().pathname, useParams());
|
||||
|
||||
const togglePostHidden = () => {
|
||||
@@ -365,7 +366,7 @@ const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
|
||||
<FloatingFocusManager context={context} modal={false}>
|
||||
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
||||
<ReportPostButton onClose={handleClose} />
|
||||
{cid && communityAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
|
||||
{cid && communityAddress && <HidePostButton cid={cid} comment={postMenu.comment} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
|
||||
{(isAccountCommentAuthor || canAttemptAuthorDelete) && cid && editMenuPost && <DeletePostButton post={editMenuPost} onClose={handleClose} />}
|
||||
{cid && communityAddress && <CopyLinkButton cid={cid} communityAddress={communityAddress} linkType='thread' onClose={handleClose} />}
|
||||
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
|
||||
|
||||
Reference in New Issue
Block a user