fix(catalog): align pagination and postsPerPage with board view

Use useBoardFeedPageSize and useDirectoryByAddress so catalog respects
directory postsPerPage, caps feed to guiPostsPerPage * 10 in non-infinite
mode, and conditionally renders Virtuoso based on enableInfiniteScroll.
This commit is contained in:
plebeius
2026-02-21 13:14:50 +08:00
parent 082076ba83
commit 1067ff39e8
+90 -31
View File
@@ -4,7 +4,8 @@ import { Trans, useTranslation } from 'react-i18next';
import { Comment, useAccount, useFeed, useSubplebbit, useAccountComments } from '@plebbit/plebbit-react-hooks'; import { Comment, useAccount, useFeed, useSubplebbit, useAccountComments } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows'; import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
import { useDirectories } from '../../hooks/use-directories'; import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
import { useBoardFeedPageSize } from '../../hooks/use-board-feed-page-size';
import { useFilteredDirectoryAddresses } from '../../hooks/use-filtered-directory-addresses'; import { useFilteredDirectoryAddresses } from '../../hooks/use-filtered-directory-addresses';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address'; import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string'; import { useFeedStateString } from '../../hooks/use-state-string';
@@ -12,6 +13,7 @@ import useTimeFilter, { timeFilterNameToSeconds } from '../../hooks/use-time-fil
import useWindowWidth from '../../hooks/use-window-width'; import useWindowWidth from '../../hooks/use-window-width';
import useCatalogStyleStore from '../../stores/use-catalog-style-store'; import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store'; import useFeedResetStore from '../../stores/use-feed-reset-store';
import useFeedViewSettingsStore from '../../stores/use-feed-view-settings-store';
import useSortingStore from '../../stores/use-sorting-store'; import useSortingStore from '../../stores/use-sorting-store';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import { getSubplebbitAddress, isDirectoryBoard } from '../../lib/utils/route-utils'; import { getSubplebbitAddress, isDirectoryBoard } from '../../lib/utils/route-utils';
@@ -38,6 +40,8 @@ interface CatalogFooterProps {
yearlyFeedLength: number; yearlyFeedLength: number;
boardPath: string | undefined; boardPath: string | undefined;
currentTimeFilterName: string | undefined; currentTimeFilterName: string | undefined;
/** When false, suppress the loading ellipsis (e.g. non-infinite mode) */
showLoadingEllipsis?: boolean;
} }
// Defined outside Catalog to preserve component identity across renders (Virtuoso optimization) // Defined outside Catalog to preserve component identity across renders (Virtuoso optimization)
@@ -58,6 +62,7 @@ const CatalogFooter = ({
yearlyFeedLength, yearlyFeedLength,
boardPath, boardPath,
currentTimeFilterName, currentTimeFilterName,
showLoadingEllipsis = true,
}: CatalogFooterProps) => { }: CatalogFooterProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -121,9 +126,11 @@ const CatalogFooter = ({
</div> </div>
)) ))
)} )}
<div className={styles.stateString}> {showLoadingEllipsis && (
<LoadingEllipsis string={loadingStateString} /> <div className={styles.stateString}>
</div> <LoadingEllipsis string={loadingStateString} />
</div>
)}
</> </>
); );
} }
@@ -313,6 +320,10 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
const columnCount = Math.floor(useWindowWidth() / columnWidth); const columnCount = Math.floor(useWindowWidth() / columnWidth);
const postsPerPage = columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25; const postsPerPage = columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25;
const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll);
const community = useDirectoryByAddress(isInAllView || isInSubscriptionsView ? undefined : subplebbitAddress);
const { guiPostsPerPage: boardPostsPerPage, maxGuiPages, paginationFeedPostsPerPage, infiniteFeedPostsPerPage } = useBoardFeedPageSize(community);
const { timeFilterSeconds: timeFilterSecondsFromHook, timeFilterName: timeFilterNameFromHook } = useTimeFilter(); const { timeFilterSeconds: timeFilterSecondsFromHook, timeFilterName: timeFilterNameFromHook } = useTimeFilter();
const { sortType } = useSortingStore(); const { sortType } = useSortingStore();
const timeFilterName = timeFilterNameFromCache || timeFilterNameFromHook; const timeFilterName = timeFilterNameFromCache || timeFilterNameFromHook;
@@ -332,10 +343,13 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
}, [subplebbitAddress]); }, [subplebbitAddress]);
const feedOptions = useMemo(() => { const feedOptions = useMemo(() => {
const catalogPostsPerPage =
isInAllView || isInSubscriptionsView ? (enableInfiniteScroll ? 10 : 100) : enableInfiniteScroll ? infiniteFeedPostsPerPage : paginationFeedPostsPerPage;
const options: any = { const options: any = {
subplebbitAddresses, subplebbitAddresses,
sortType, sortType,
postsPerPage: isInAllView || isInSubscriptionsView ? 10 : postsPerPage, postsPerPage: catalogPostsPerPage,
filter: createCombinedFilter(filterItems, searchText, subplebbitAddress || 'all', handleFilterMatch), filter: createCombinedFilter(filterItems, searchText, subplebbitAddress || 'all', handleFilterMatch),
}; };
@@ -344,7 +358,20 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
} }
return options; return options;
}, [subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, timeFilterSeconds, filterItems, searchText, subplebbitAddress, handleFilterMatch]); }, [
subplebbitAddresses,
sortType,
isInAllView,
isInSubscriptionsView,
enableInfiniteScroll,
infiniteFeedPostsPerPage,
paginationFeedPostsPerPage,
timeFilterSeconds,
filterItems,
searchText,
subplebbitAddress,
handleFilterMatch,
]);
const { feed, hasMore, loadMore, reset, subplebbitAddressesWithNewerPosts } = useFeed(feedOptions); const { feed, hasMore, loadMore, reset, subplebbitAddressesWithNewerPosts } = useFeed(feedOptions);
const { accountComments } = useAccountComments(); const { accountComments } = useAccountComments();
@@ -392,6 +419,11 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
return newFeed; return newFeed;
}, [feed, filteredComments]); }, [feed, filteredComments]);
const cappedFeed = useMemo(
() => (enableInfiniteScroll ? combinedFeed : combinedFeed.slice(0, boardPostsPerPage * maxGuiPages)),
[enableInfiniteScroll, combinedFeed, boardPostsPerPage, maxGuiPages],
);
useEffect(() => { useEffect(() => {
if (filteredComments.length > 0 && !resetTriggeredRef.current) { if (filteredComments.length > 0 && !resetTriggeredRef.current) {
reset(); reset();
@@ -462,7 +494,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
subplebbitAddresses={subplebbitAddresses} subplebbitAddresses={subplebbitAddresses}
hasMore={hasMore} hasMore={hasMore}
feedLength={feedLength} feedLength={feedLength}
combinedFeedLength={combinedFeed.length} combinedFeedLength={cappedFeed.length}
subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts} subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts}
onNewerPostsClick={handleNewerPostsButtonClick} onNewerPostsClick={handleNewerPostsButtonClick}
isInAllView={isInAllView} isInAllView={isInAllView}
@@ -473,6 +505,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
yearlyFeedLength={yearlyFeedLength} yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath} boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName} currentTimeFilterName={currentTimeFilterName}
showLoadingEllipsis={enableInfiniteScroll}
/> />
), ),
}), }),
@@ -480,7 +513,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
subplebbitAddresses, subplebbitAddresses,
hasMore, hasMore,
feedLength, feedLength,
combinedFeed.length, cappedFeed.length,
subplebbitAddressesWithNewerPosts, subplebbitAddressesWithNewerPosts,
handleNewerPostsButtonClick, handleNewerPostsButtonClick,
isInAllView, isInAllView,
@@ -491,6 +524,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
yearlyFeedLength, yearlyFeedLength,
boardPath, boardPath,
currentTimeFilterName, currentTimeFilterName,
enableInfiniteScroll,
], ],
); );
@@ -498,16 +532,16 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
// Process the feed to move "top" posts to the top // Process the feed to move "top" posts to the top
const processedFeed = useMemo(() => { const processedFeed = useMemo(() => {
if (!combinedFeed || combinedFeed.length === 0) return combinedFeed; if (!cappedFeed || cappedFeed.length === 0) return cappedFeed;
const enabledTopFilters = filterItems.filter((item) => item.enabled && item.text.trim() !== '' && item.top); const enabledTopFilters = filterItems.filter((item) => item.enabled && item.text.trim() !== '' && item.top);
if (enabledTopFilters.length === 0) return combinedFeed; if (enabledTopFilters.length === 0) return cappedFeed;
// Separate posts that match "top" filters // Separate posts that match "top" filters
const topPosts: Comment[] = []; const topPosts: Comment[] = [];
const regularPosts: Comment[] = []; const regularPosts: Comment[] = [];
combinedFeed.forEach((comment) => { cappedFeed.forEach((comment) => {
if (!comment) return; if (!comment) return;
let isTop = false; let isTop = false;
@@ -527,7 +561,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
// Return top posts followed by regular posts // Return top posts followed by regular posts
return [...topPosts, ...regularPosts]; return [...topPosts, ...regularPosts];
}, [combinedFeed, filterItems]); }, [cappedFeed, filterItems]);
const rows = useCatalogFeedRows(columnCount, processedFeed, isFeedLoaded, subplebbit); const rows = useCatalogFeedRows(columnCount, processedFeed, isFeedLoaded, subplebbit);
@@ -589,12 +623,12 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
// Memoize filter color application to avoid redundant iterations // Memoize filter color application to avoid redundant iterations
useMemo(() => { useMemo(() => {
if (combinedFeed.length > 0 && filterItems.length > 0) { if (cappedFeed.length > 0 && filterItems.length > 0) {
// Clear existing matched filters // Clear existing matched filters
clearMatchedFilters(); clearMatchedFilters();
// Apply colors to posts that match filters // Apply colors to posts that match filters
combinedFeed.forEach((comment) => { cappedFeed.forEach((comment) => {
if (!comment?.cid) return; if (!comment?.cid) return;
// Check each filter // Check each filter
@@ -608,7 +642,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
} }
}); });
} }
}, [combinedFeed, filterItems, clearMatchedFilters]); }, [cappedFeed, filterItems, clearMatchedFilters]);
return ( return (
<div className={styles.content}> <div className={styles.content}>
@@ -616,20 +650,44 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
<div className={styles.catalog}> <div className={styles.catalog}>
{processedFeed?.length !== 0 ? ( {processedFeed?.length !== 0 ? (
<> <>
{/* Use Virtuoso for infinite scroll only when there's more content to paginate */} {enableInfiniteScroll ? (
{hasMore ? ( hasMore ? (
<Virtuoso <Virtuoso
increaseViewportBy={{ bottom: 1200, top: 1200 }} increaseViewportBy={{ bottom: 1200, top: 1200 }}
totalCount={rows?.length || 0} totalCount={rows?.length || 0}
data={rows} data={rows}
itemContent={(index, row) => <CatalogRow index={index} row={row} />} itemContent={(index, row) => <CatalogRow index={index} row={row} />}
useWindowScroll={true} useWindowScroll={true}
components={footerComponents} components={footerComponents}
endReached={loadMore} endReached={loadMore}
ref={virtuosoRef} ref={virtuosoRef}
restoreStateFrom={lastVirtuosoState} restoreStateFrom={lastVirtuosoState}
initialScrollTop={lastVirtuosoState?.scrollTop} initialScrollTop={lastVirtuosoState?.scrollTop}
/> />
) : (
<>
{rows.map((row, index) => (
<CatalogRow key={index} index={index} row={row} />
))}
<CatalogFooter
subplebbitAddresses={subplebbitAddresses}
hasMore={hasMore}
feedLength={feedLength}
combinedFeedLength={cappedFeed.length}
subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts}
onNewerPostsClick={handleNewerPostsButtonClick}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
showMorePostsSuggestion={showMorePostsSuggestion}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName}
showLoadingEllipsis={true}
/>
</>
)
) : ( ) : (
<> <>
{rows.map((row, index) => ( {rows.map((row, index) => (
@@ -639,7 +697,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
subplebbitAddresses={subplebbitAddresses} subplebbitAddresses={subplebbitAddresses}
hasMore={hasMore} hasMore={hasMore}
feedLength={feedLength} feedLength={feedLength}
combinedFeedLength={combinedFeed.length} combinedFeedLength={cappedFeed.length}
subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts} subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts}
onNewerPostsClick={handleNewerPostsButtonClick} onNewerPostsClick={handleNewerPostsButtonClick}
isInAllView={isInAllView} isInAllView={isInAllView}
@@ -650,6 +708,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
yearlyFeedLength={yearlyFeedLength} yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath} boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName} currentTimeFilterName={currentTimeFilterName}
showLoadingEllipsis={false}
/> />
</> </>
)} )}
@@ -665,7 +724,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
yearlyFeedLength={yearlyFeedLength} yearlyFeedLength={yearlyFeedLength}
state={state} state={state}
subscriptionsLength={isInSubscriptionsView ? subscriptions?.length || 0 : 1} subscriptionsLength={isInSubscriptionsView ? subscriptions?.length || 0 : 1}
combinedFeedLength={combinedFeed.length} combinedFeedLength={cappedFeed.length}
error={error} error={error}
/> />
</div> </div>