refactor(multiboards): remove time filter system end-to-end

Backend retention now keeps only recent posts, making the time filter UI
and newerThan filtering obsolete. Deleted use-time-filter hook, canonical
multiboard routes only, removed footer suggestions, dropped i18n keys.
This commit is contained in:
plebeius
2026-02-22 15:57:42 +08:00
parent f521bdbe33
commit 8e6d81ba59
46 changed files with 84 additions and 811 deletions
+18 -15
View File
@@ -243,27 +243,30 @@ const App = () => {
<Route path='/faq' element={<FAQ />} />
<Route path='/rules/:boardIdentifier?' element={<Rules />} />
<Route element={<BoardLayout />}>
<Route path='/all/:timeFilterName/:pageNumber' element={boardFeedElement} />
<Route path='/all/:timeFilterName?' element={boardFeedElement} />
<Route path='/all/:timeFilterName?/settings' element={boardFeedElement} />
<Route path='/all/catalog/:timeFilterName?' element={catalogFeedElement} />
<Route path='/all/catalog/:timeFilterName?/settings' element={catalogFeedElement} />
{/* Canonical multiboard routes (no time filter) */}
<Route path='/all' element={boardFeedElement} />
<Route path='/all/settings' element={boardFeedElement} />
<Route path='/all/catalog' element={catalogFeedElement} />
<Route path='/all/catalog/settings' element={catalogFeedElement} />
<Route path='/subs/:timeFilterName/:pageNumber' element={boardFeedElement} />
<Route path='/subs/:timeFilterName?' element={boardFeedElement} />
<Route path='/subs/:timeFilterName?/settings' element={boardFeedElement} />
<Route path='/subs/catalog/:timeFilterName?' element={catalogFeedElement} />
<Route path='/subs/catalog/:timeFilterName?/settings' element={catalogFeedElement} />
<Route path='/subs' element={boardFeedElement} />
<Route path='/subs/settings' element={boardFeedElement} />
<Route path='/subs/catalog' element={catalogFeedElement} />
<Route path='/subs/catalog/settings' element={catalogFeedElement} />
<Route path='/mod/:timeFilterName/:pageNumber' element={boardFeedElement} />
<Route path='/mod/:timeFilterName?' element={boardFeedElement} />
<Route path='/mod/:timeFilterName?/settings' element={boardFeedElement} />
<Route path='/mod/catalog/:timeFilterName?' element={catalogFeedElement} />
<Route path='/mod/catalog/:timeFilterName?/settings' element={catalogFeedElement} />
<Route path='/mod' element={boardFeedElement} />
<Route path='/mod/settings' element={boardFeedElement} />
<Route path='/mod/catalog' element={catalogFeedElement} />
<Route path='/mod/catalog/settings' element={catalogFeedElement} />
<Route path='/mod/modqueue' element={<ModQueueRoute />} />
<Route path='/mod/modqueue/settings' element={<ModQueueRoute />} />
{/* Invalid subpaths: old time-filter URLs (e.g. /all/24h) -> not-found */}
<Route path='/all/*' element={<Navigate to='/not-found' replace />} />
<Route path='/subs/*' element={<Navigate to='/not-found' replace />} />
<Route path='/mod/*' element={<Navigate to='/not-found' replace />} />
<Route path='/:boardIdentifier/:pageNumber' element={boardFeedElement} />
<Route path='/:boardIdentifier' element={boardFeedElement} />
<Route path='/:boardIdentifier/settings' element={boardFeedElement} />
+10 -80
View File
@@ -15,7 +15,6 @@ import useModQueueStore from '../../stores/use-mod-queue-store';
import useFeedViewSettingsStore from '../../stores/use-feed-view-settings-store';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useIsMobile from '../../hooks/use-is-mobile';
import useTimeFilter from '../../hooks/use-time-filter';
import CatalogFilters from '../catalog-filters';
import CatalogSearch from '../catalog-search';
import Tooltip from '../tooltip';
@@ -39,16 +38,9 @@ const CatalogButton = ({ address, isInAllView, isInSubscriptionsView, isInModVie
const directories = useDirectories();
const createCatalogLink = () => {
if (isInAllView) {
if (params?.timeFilterName) return `/all/catalog/${params.timeFilterName}`;
return `/all/catalog`;
} else if (isInSubscriptionsView) {
if (params?.timeFilterName) return `/subs/catalog/${params.timeFilterName}`;
return `/subs/catalog`;
} else if (isInModView) {
if (params?.timeFilterName) return `/mod/catalog/${params.timeFilterName}`;
return `/mod/catalog`;
}
if (isInAllView) return `/all/catalog`;
if (isInSubscriptionsView) return `/subs/catalog`;
if (isInModView) return `/mod/catalog`;
let boardPath = '';
if (address) {
boardPath = getBoardPath(address, directories);
@@ -82,22 +74,16 @@ const ReturnButton = ({ address, isInAllView, isInSubscriptionsView, isInModView
const directories = useDirectories();
const createReturnLink = () => {
if (isInAllView) {
if (params?.timeFilterName) return `/all/${params.timeFilterName}`;
return `/all`;
} else if (isInSubscriptionsView) {
if (params?.timeFilterName) return `/subs/${params.timeFilterName}`;
return `/subs`;
} else if (isInModQueueView) {
if (isInAllView) return `/all`;
if (isInSubscriptionsView) return `/subs`;
if (isInModQueueView) {
// If in mod queue view, return to /mod or /:boardIdentifier
if (params?.boardIdentifier) {
return `/${params.boardIdentifier}`;
}
return `/mod`;
} else if (isInModView) {
if (params?.timeFilterName) return `/mod/${params.timeFilterName}`;
return `/mod`;
}
if (isInModView) return `/mod`;
let boardPath = '';
if (address) {
boardPath = getBoardPath(address, directories);
@@ -335,53 +321,6 @@ const ShowOPCommentOption = () => {
);
};
export const TimeFilter = ({ isInAllView, isInCatalogView, isInSubscriptionsView, isInModView, isTopbar = false }: BoardButtonsProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { timeFilterName, timeFilterNames } = useTimeFilter();
const changeTimeFilter = (event: React.ChangeEvent<HTMLSelectElement>) => {
const timeFilterName = event.target.value;
const link = isInAllView
? isInCatalogView
? `/all/catalog/${timeFilterName}`
: `/all/${timeFilterName}`
: isInSubscriptionsView
? isInCatalogView
? `/subs/catalog/${timeFilterName}`
: `/subs/${timeFilterName}`
: isInModView
? isInCatalogView
? `/mod/catalog/${timeFilterName}`
: `/mod/${timeFilterName}`
: null;
link && navigate(link);
};
const { sortType } = useSortingStore();
const allTimeFilterNames = timeFilterName ? Array.from(new Set([timeFilterName, ...timeFilterNames])) : timeFilterNames;
return (
<>
{!isTopbar ? (
<>
<span>{isInCatalogView ? (sortType === 'active' ? t('last_bumped') : t('newer_than')) : t('last_bumped')}</span>:&nbsp;
</>
) : (
<> </>
)}
<select onChange={changeTimeFilter} className={[styles.feedName, styles.menuItem, 'capitalize'].join(' ')} value={timeFilterName}>
{allTimeFilterNames.map((name, i) => (
<option key={name + i} value={name}>
{name}
</option>
))}
</select>
</>
);
};
const AllFeedFilter = () => {
const { t } = useTranslation();
const { filter, setFilter } = useAllFeedFilterStore();
@@ -643,9 +582,6 @@ export const DesktopBoardButtons = () => {
</>
)}
{isInAllView && <AllFeedFilter />}
{(isInAllView || isInSubscriptionsView || isInModView) && (
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
)}
{!(isInAllView || isInSubscriptionsView || isInModView) && (
<>
[
@@ -684,17 +620,11 @@ const SearchOPsBar = () => {
let catalogUrl = '';
if (isInAllView) {
catalogUrl = params?.timeFilterName
? `/all/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}`
: `/all/catalog?q=${encodeURIComponent(searchQuery)}`;
catalogUrl = `/all/catalog?q=${encodeURIComponent(searchQuery)}`;
} else if (isInSubscriptionsView) {
catalogUrl = params?.timeFilterName
? `/subs/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}`
: `/subs/catalog?q=${encodeURIComponent(searchQuery)}`;
catalogUrl = `/subs/catalog?q=${encodeURIComponent(searchQuery)}`;
} else if (isInModView) {
catalogUrl = params?.timeFilterName
? `/mod/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}`
: `/mod/catalog?q=${encodeURIComponent(searchQuery)}`;
catalogUrl = `/mod/catalog?q=${encodeURIComponent(searchQuery)}`;
} else {
catalogUrl = `/${boardPath}/catalog?q=${encodeURIComponent(searchQuery)}`;
}
+1 -1
View File
@@ -1 +1 @@
export { TimeFilter, MobileBoardButtons, DesktopBoardButtons } from './board-buttons';
export { MobileBoardButtons, DesktopBoardButtons } from './board-buttons';
@@ -9,7 +9,6 @@ import styles from './feed-cache-container.module.css';
interface FeedContextFromKey {
viewType: 'all' | 'subs' | 'mod' | 'board';
boardIdentifier?: string;
timeFilterName?: string;
}
const parseFeedKey = (key: string): FeedContextFromKey => {
@@ -17,28 +16,18 @@ const parseFeedKey = (key: string): FeedContextFromKey => {
const filteredSegments = segments.filter((s) => s !== 'catalog');
if (filteredSegments[0] === 'all') {
return {
viewType: 'all',
timeFilterName: filteredSegments[1],
};
return { viewType: 'all' };
}
if (filteredSegments[0] === 'subs') {
return {
viewType: 'subs',
timeFilterName: filteredSegments[1],
};
return { viewType: 'subs' };
}
if (filteredSegments[0] === 'mod') {
return {
viewType: 'mod',
timeFilterName: filteredSegments[1],
};
return { viewType: 'mod' };
}
return {
viewType: 'board',
boardIdentifier: filteredSegments[0],
timeFilterName: filteredSegments[1],
};
};
@@ -53,21 +42,9 @@ const CachedFeedWrapper = ({ feed, isVisible }: CachedFeedWrapperProps) => {
return (
<div className={isVisible ? styles.visible : styles.hidden}>
{feed.type === 'catalog' ? (
<Catalog
feedCacheKey={feed.key}
viewType={context.viewType}
boardIdentifier={context.boardIdentifier}
timeFilterNameFromCache={context.timeFilterName}
isVisible={isVisible}
/>
<Catalog feedCacheKey={feed.key} viewType={context.viewType} boardIdentifier={context.boardIdentifier} isVisible={isVisible} />
) : (
<Board
feedCacheKey={feed.key}
viewType={context.viewType}
boardIdentifier={context.boardIdentifier}
timeFilterNameFromCache={context.timeFilterName}
isVisible={isVisible}
/>
<Board feedCacheKey={feed.key} viewType={context.viewType} boardIdentifier={context.boardIdentifier} isVisible={isVisible} />
)}
</div>
);
-4
View File
@@ -8,7 +8,6 @@ import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/v
import { useDirectories, DirectoryCommunity } from '../../hooks/use-directories';
import { useBoardPath, useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import { getBoardPath, extractDirectoryFromTitle } from '../../lib/utils/route-utils';
import { TimeFilter } from '../board-buttons';
import useCreateBoardModalStore from '../../stores/use-create-board-modal-store';
import useTopbarEditModalStore from '../../stores/use-topbar-edit-modal-store';
import useTopbarVisibilityStore from '../../stores/use-topbar-visibility-store';
@@ -334,9 +333,6 @@ const TopBarMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
<div className={styles.boardSelect}>
<strong>{t('board')}</strong>
{boardSelect}
{(isInAllView || isInSubscriptionsView) && (
<TimeFilter isTopbar={true} isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
)}
</div>
<div className={styles.pageJump}>
<Link to={useLocation().pathname.replace(/\/$/, '') + '/settings'}>{t('settings')}</Link>
-149
View File
@@ -1,149 +0,0 @@
import assert from 'assert';
import { useParams } from 'react-router-dom';
import { isBoardFeedPageNumber } from '../lib/utils/route-utils';
// the timestamp the last time the user visited
const lastVisitTimestamp = localStorage.getItem('5chanLastVisitTimestamp');
// Singleton pattern to ensure only one interval is ever created
// This prevents memory leaks from multiple intervals being created during HMR or module re-evaluation
// Using window object to persist across hot module reloads
declare global {
interface Window {
_5chanLastVisitTimestampIntervalId?: number;
}
}
if (!window._5chanLastVisitTimestampIntervalId) {
// update the last visited timestamp every n seconds
window._5chanLastVisitTimestampIntervalId = window.setInterval(() => {
localStorage.setItem('5chanLastVisitTimestamp', Date.now().toString());
}, 60 * 1000);
}
const timeFilterNamesToSeconds: Record<string, number | undefined> = {
'1h': 60 * 60,
'12h': 60 * 60 * 12,
'24h': 60 * 60 * 24,
'48h': 60 * 60 * 24 * 2,
'1w': 60 * 60 * 24 * 7,
'1m': 60 * 60 * 24 * 30,
'1y': 60 * 60 * 24 * 365,
all: undefined,
};
// calculate the last visit timeFilterNamesToSeconds
const secondsSinceLastVisit = lastVisitTimestamp ? (Date.now() - parseInt(lastVisitTimestamp, 10)) / 1000 : Infinity;
const day = 24 * 60 * 60;
let lastVisitTimeFilterName: string | undefined;
if (secondsSinceLastVisit > 30 * day) {
lastVisitTimeFilterName = '1m';
timeFilterNamesToSeconds[lastVisitTimeFilterName] = timeFilterNamesToSeconds['1m'];
} else if (secondsSinceLastVisit > 7 * day) {
const weeks = Math.ceil(secondsSinceLastVisit / day / 7);
lastVisitTimeFilterName = `${weeks}w`;
timeFilterNamesToSeconds[lastVisitTimeFilterName] = 60 * 60 * 24 * 7 * weeks;
} else if (secondsSinceLastVisit > day) {
const days = Math.ceil(secondsSinceLastVisit / day);
lastVisitTimeFilterName = `${days}d`;
timeFilterNamesToSeconds[lastVisitTimeFilterName] = 60 * 60 * 24 * days;
} else {
lastVisitTimeFilterName = '24h';
timeFilterNamesToSeconds[lastVisitTimeFilterName] = timeFilterNamesToSeconds['24h'];
}
export const timeFilterNames = [lastVisitTimeFilterName, '1h', '12h', '24h', '48h', '1w', '1m', '1y', 'all'];
export const timeFilterNameToSeconds = (timeFilterName: string | undefined): number | undefined => {
if (!timeFilterName || timeFilterName === 'all') return undefined;
if (timeFilterName in timeFilterNamesToSeconds) {
return timeFilterNamesToSeconds[timeFilterName as keyof typeof timeFilterNamesToSeconds];
}
const match = timeFilterName.match(/^(\d+)([hdwmy])$/);
if (match) {
const [, value, unit] = match;
const numValue = parseInt(value, 10);
switch (unit) {
case 'h':
return numValue * 60 * 60;
case 'd':
return numValue * 24 * 60 * 60;
case 'w':
return numValue * 7 * 24 * 60 * 60;
case 'm':
return numValue * 30 * 24 * 60 * 60;
case 'y':
return numValue * 365 * 24 * 60 * 60;
}
}
return undefined;
};
function convertTimeStringToSeconds(timeString: string): number {
const match = timeString.match(/^(\d+)([hdwmy])$/);
if (!match) {
throw new Error(`Invalid time filter format: ${timeString}`);
}
const [, value, unit] = match;
const numValue = parseInt(value, 10);
switch (unit) {
case 'h':
return numValue * 60 * 60;
case 'd':
return numValue * 24 * 60 * 60;
case 'w':
return numValue * 7 * 24 * 60 * 60;
case 'm':
return numValue * 30 * 24 * 60 * 60;
case 'y':
return numValue * 365 * 24 * 60 * 60;
default:
throw new Error(`Invalid time unit: ${unit}`);
}
}
const useTimeFilter = () => {
const params = useParams();
let timeFilterName = params.timeFilterName;
// Ignore when param is a board feed page number (e.g. /all/3, /biz/2)
if (timeFilterName && isBoardFeedPageNumber(timeFilterName)) {
timeFilterName = undefined;
}
// the default time filter is the last visit time filter
if (!timeFilterName) {
timeFilterName = lastVisitTimeFilterName;
}
let timeFilterSeconds: number | undefined;
if (timeFilterName === 'all') {
timeFilterSeconds = undefined;
} else if (timeFilterName && timeFilterName in timeFilterNamesToSeconds) {
timeFilterSeconds = timeFilterNamesToSeconds[timeFilterName as keyof typeof timeFilterNamesToSeconds];
} else if (timeFilterName) {
try {
timeFilterSeconds = convertTimeStringToSeconds(timeFilterName);
} catch {
console.error(`Invalid time filter format: ${timeFilterName}`);
timeFilterSeconds = undefined;
}
}
// If we still don't have a valid timeFilterSeconds, use the default (24h)
if (timeFilterSeconds === undefined && timeFilterName !== 'all') {
timeFilterSeconds = timeFilterNamesToSeconds['24h'];
}
assert(timeFilterName === 'all' || timeFilterSeconds !== undefined, `useTimeFilter no filter for timeFilterName '${timeFilterName}'`);
return { timeFilterSeconds, timeFilterNames, timeFilterName, lastVisitTimeFilterName };
};
export default useTimeFilter;
+21 -6
View File
@@ -1,15 +1,11 @@
import { describe, it, expect } from 'vitest';
import { normalizeMultiboardFeedPath } from '../route-utils';
import { isFeedRoute, normalizeMultiboardFeedPath } from '../route-utils';
describe('normalizeMultiboardFeedPath', () => {
it('normalizes /all/3 -> /all', () => {
expect(normalizeMultiboardFeedPath('/all/3')).toBe('/all');
});
it('normalizes /all/1w/3 -> /all/1w', () => {
expect(normalizeMultiboardFeedPath('/all/1w/3')).toBe('/all/1w');
});
it('normalizes /subs/2/settings -> /subs/settings', () => {
expect(normalizeMultiboardFeedPath('/subs/2/settings')).toBe('/subs/settings');
});
@@ -21,9 +17,28 @@ describe('normalizeMultiboardFeedPath', () => {
it('leaves non-multiboard paths unchanged', () => {
expect(normalizeMultiboardFeedPath('/biz')).toBe('/biz');
expect(normalizeMultiboardFeedPath('/biz/3')).toBe('/biz/3');
expect(normalizeMultiboardFeedPath('/biz/1w/2')).toBe('/biz/1w/2');
expect(normalizeMultiboardFeedPath('/biz/catalog/4')).toBe('/biz/catalog/4');
expect(normalizeMultiboardFeedPath('/pending/0')).toBe('/pending/0');
expect(normalizeMultiboardFeedPath('/')).toBe('/');
});
});
describe('isFeedRoute', () => {
it('returns true for canonical multiboard paths', () => {
expect(isFeedRoute('/all')).toBe(true);
expect(isFeedRoute('/all/catalog')).toBe(true);
expect(isFeedRoute('/subs')).toBe(true);
expect(isFeedRoute('/subs/catalog')).toBe(true);
expect(isFeedRoute('/mod')).toBe(true);
expect(isFeedRoute('/mod/catalog')).toBe(true);
expect(isFeedRoute('/all/3')).toBe(true);
expect(isFeedRoute('/subs/2')).toBe(true);
expect(isFeedRoute('/mod/catalog/4')).toBe(true);
});
it('returns false for time-filter paths', () => {
expect(isFeedRoute('/all/24h')).toBe(false);
expect(isFeedRoute('/subs/catalog/1w')).toBe(false);
expect(isFeedRoute('/all/1w/3')).toBe(false);
});
});
+4 -11
View File
@@ -109,19 +109,12 @@ export const isFeedRoute = (pathname: string): boolean => {
if (normalizedPath.includes('/modqueue')) return false;
const pathWithoutSettings = normalizedPath.replace(/\/settings$/, '');
if (pathWithoutSettings.startsWith('/all')) return true;
if (pathWithoutSettings.startsWith('/subs')) return true;
if (pathWithoutSettings.startsWith('/mod')) return true;
const segments = pathWithoutSettings.split('/').filter(Boolean);
if (segments.length >= 1) {
if (segments.length === 1) return true;
if (segments.length === 2 && segments[1] === 'catalog') return true;
if (segments.length === 2 && (/^(?:\d+(?:h|d|w|m|y)|all)$/.test(segments[1]) || /^([1-9]|10)$/.test(segments[1]))) return true;
if (segments.length === 3 && segments[1] === 'catalog' && /^(?:\d+(?:h|d|w|m|y)|all)$/.test(segments[2])) return true;
if (segments.length === 3 && /^(?:\d+(?:h|d|w|m|y)|all)$/.test(segments[1]) && /^([1-9]|10)$/.test(segments[2])) return true;
if (segments.length === 2 && segments[0] !== 'all' && segments[0] !== 'subs' && segments[0] !== 'mod' && /^([1-9]|10)$/.test(segments[1])) return true;
if (segments.length === 2 && /^([1-9]|10)$/.test(segments[1])) return true;
if (segments.length === 3 && segments[1] === 'catalog' && /^([1-9]|10)$/.test(segments[2])) return true;
}
return false;
@@ -163,8 +156,8 @@ function isMultiboardFeedPath(pathname: string): boolean {
}
/**
* Normalize multiboard feed paths by removing trailing page-number segments (110),
* while preserving /settings and valid time-filter segments.
* Normalize multiboard feed paths by removing trailing page-number segments (110)
* and preserving /settings.
* Non-multiboard paths are returned unchanged.
*/
export const normalizeMultiboardFeedPath = (pathname: string): string => {
+3 -20
View File
@@ -3,7 +3,6 @@ export type ParamsType = {
boardIdentifier?: string;
commentCid?: string;
subplebbitAddress?: string; // deprecated, kept for backward compatibility
timeFilterName?: string;
};
export const isAllView = (pathname: string): boolean => {
@@ -30,7 +29,7 @@ export const isBoardView = (pathname: string, params: ParamsType): boolean => {
};
export const isCatalogView = (pathname: string, params: ParamsType): boolean => {
const { boardIdentifier, subplebbitAddress, timeFilterName } = params;
const { boardIdentifier, subplebbitAddress } = params;
const identifier = boardIdentifier || subplebbitAddress;
const decodedPathname = decodeURIComponent(pathname);
@@ -38,16 +37,10 @@ export const isCatalogView = (pathname: string, params: ParamsType): boolean =>
(identifier && (decodedPathname === `/${identifier}/catalog` || decodedPathname === `/${identifier}/catalog/settings`)) ||
decodedPathname === `/all/catalog` ||
decodedPathname === `/all/catalog/settings` ||
decodedPathname === `/all/catalog/${timeFilterName}` ||
decodedPathname === `/all/catalog/${timeFilterName}/settings` ||
decodedPathname === `/subs/catalog` ||
decodedPathname === `/subs/catalog/settings` ||
decodedPathname === `/subs/catalog/${timeFilterName}` ||
decodedPathname === `/subs/catalog/${timeFilterName}/settings` ||
decodedPathname === `/mod/catalog` ||
decodedPathname === `/mod/catalog/settings` ||
decodedPathname === `/mod/catalog/${timeFilterName}` ||
decodedPathname === `/mod/catalog/${timeFilterName}/settings`
decodedPathname === `/mod/catalog/settings`
);
};
@@ -83,17 +76,7 @@ export const isSettingsView = (pathname: string, params: ParamsType): boolean =>
};
export const isSubscriptionsView = (pathname: string, params: ParamsType): boolean => {
const { timeFilterName } = params;
return (
pathname === '/subs' ||
pathname === '/subs/settings' ||
pathname === `/subs/${timeFilterName}` ||
pathname === `/subs/${timeFilterName}/settings` ||
pathname === '/subs/catalog' ||
pathname === '/subs/catalog/settings' ||
pathname === `/subs/catalog/${timeFilterName}` ||
pathname === `/subs/catalog/${timeFilterName}/settings`
);
return pathname === '/subs' || pathname === '/subs/settings' || pathname === '/subs/catalog' || pathname === '/subs/catalog/settings';
};
export const isNotFoundView = (pathname: string, params: ParamsType): boolean => {
+9 -143
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useLocation, useNavigate, useNavigationType, useParams } from 'react-router-dom';
import { useEffect, useMemo, useRef } from 'react';
import { useLocation, useNavigate, useNavigationType, useParams } from 'react-router-dom';
import { Comment, useAccount, useAccountComments, useAccountSubplebbits, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
@@ -8,9 +8,8 @@ import styles from './board.module.css';
import { shouldShowSnow } from '../../lib/snow';
import { useDirectoryAddresses, useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
import { useFilteredDirectoryAddresses } from '../../hooks/use-filtered-directory-addresses';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter, { timeFilterNameToSeconds } from '../../hooks/use-time-filter';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useSortingStore from '../../stores/use-sorting-store';
import useFeedViewSettingsStore from '../../stores/use-feed-view-settings-store';
@@ -33,13 +32,6 @@ interface BoardFooterProps {
isInAllView: boolean;
isInSubscriptionsView: boolean;
isInModView: boolean;
showMorePostsSuggestion: boolean;
feedLength: number;
weeklyFeedLength: number;
monthlyFeedLength: number;
yearlyFeedLength: number;
boardPath: string | undefined;
currentTimeFilterName: string | undefined;
subplebbitState: string | undefined;
subscriptionsLength: number;
accountSubplebbitAddressesLength: number;
@@ -59,13 +51,6 @@ const BoardFooter = ({
isInAllView,
isInSubscriptionsView,
isInModView,
showMorePostsSuggestion,
feedLength,
weeklyFeedLength,
monthlyFeedLength,
yearlyFeedLength,
boardPath,
currentTimeFilterName,
subplebbitState,
subscriptionsLength,
accountSubplebbitAddressesLength,
@@ -73,11 +58,7 @@ const BoardFooter = ({
}: BoardFooterProps) => {
const { t } = useTranslation();
const loadingStateString =
useFeedStateString(subplebbitAddresses) ||
(feedLength === 0 && !(weeklyFeedLength > feedLength || monthlyFeedLength > feedLength || yearlyFeedLength > monthlyFeedLength)
? t('loading_feed')
: t('looking_for_more_posts'));
const loadingStateString = useFeedStateString(subplebbitAddresses) || (combinedFeedLength === 0 ? t('loading_feed') : t('looking_for_more_posts'));
let footerContent;
if (combinedFeedLength === 0) {
@@ -86,7 +67,7 @@ const BoardFooter = ({
if (hasMore || (subplebbitAddresses && subplebbitAddresses.length === 0)) {
footerContent = (
<>
{subplebbitAddressesWithNewerPosts.length > 0 ? (
{subplebbitAddressesWithNewerPosts.length > 0 && (
<div className={styles.morePostsSuggestion}>
<Trans
i18nKey='newer_threads_available'
@@ -95,44 +76,6 @@ const BoardFooter = ({
}}
/>
</div>
) : (
(isInAllView || isInSubscriptionsView || isInModView) &&
showMorePostsSuggestion &&
(monthlyFeedLength > feedLength || yearlyFeedLength > monthlyFeedLength) &&
(() => {
const basePath = isInAllView ? '/all' : isInSubscriptionsView ? '/subs' : isInModView ? '/mod' : boardPath ? `/${boardPath}` : '';
return weeklyFeedLength > feedLength ? (
<div className={styles.morePostsSuggestion}>
<Trans
i18nKey='more_threads_last_week'
values={{ currentTimeFilterName, count: feedLength }}
components={{
1: <Link to={`${basePath}/1w`} />,
}}
/>
</div>
) : monthlyFeedLength > feedLength ? (
<div className={styles.morePostsSuggestion}>
<Trans
i18nKey='more_threads_last_month'
values={{ currentTimeFilterName, count: feedLength }}
components={{
1: <Link to={`${basePath}/1m`} />,
}}
/>
</div>
) : (
<div className={styles.morePostsSuggestion}>
<Trans
i18nKey='more_threads_last_year'
values={{ currentTimeFilterName, count: feedLength }}
components={{
1: <Link to={`${basePath}/1y`} />,
}}
/>
</div>
);
})()
)}
</>
);
@@ -159,11 +102,10 @@ export interface BoardProps {
feedCacheKey?: string;
viewType?: 'all' | 'subs' | 'mod' | 'board';
boardIdentifier?: string;
timeFilterNameFromCache?: string;
isVisible?: boolean;
}
const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, timeFilterNameFromCache, isVisible = true }: BoardProps) => {
const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, isVisible = true }: BoardProps) => {
const { t } = useTranslation();
const location = useLocation();
const params = useParams();
@@ -180,7 +122,6 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
return resolvedAddressFromUrl;
}, [boardIdentifierProp, directories, resolvedAddressFromUrl]);
const boardPath = useBoardPath(subplebbitAddress);
const directoryAddresses = useDirectoryAddresses();
const filteredDirectoryAddresses = useFilteredDirectoryAddresses();
@@ -204,9 +145,6 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
}, [isInAllView, isInSubscriptionsView, isInModView, subplebbitAddress, directoryAddresses, filteredDirectoryAddresses, subscriptions, accountSubplebbitAddresses]);
const { sortType } = useSortingStore();
const { timeFilterSeconds: timeFilterSecondsFromHook, timeFilterName: timeFilterNameFromHook } = useTimeFilter();
const timeFilterName = timeFilterNameFromCache || timeFilterNameFromHook;
const timeFilterSeconds = timeFilterNameFromCache ? timeFilterNameToSeconds(timeFilterNameFromCache) : timeFilterSecondsFromHook;
const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll);
const isForcedInfiniteScroll = isInAllView || isInSubscriptionsView || isInModView;
@@ -219,25 +157,14 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
subplebbitAddresses,
sortType,
postsPerPage: effectiveInfiniteScroll ? infiniteFeedPostsPerPage : paginationFeedPostsPerPage,
...(isInAllView || isInSubscriptionsView || isInModView ? { newerThan: timeFilterSeconds } : {}),
}),
[
subplebbitAddresses,
sortType,
effectiveInfiniteScroll,
infiniteFeedPostsPerPage,
paginationFeedPostsPerPage,
isInAllView,
isInSubscriptionsView,
isInModView,
timeFilterSeconds,
],
[subplebbitAddresses, sortType, effectiveInfiniteScroll, infiniteFeedPostsPerPage, paginationFeedPostsPerPage],
);
const { feed, hasMore, loadMore, reset, subplebbitAddressesWithNewerPosts } = useFeed(feedOptions);
const { accountComments } = useAccountComments();
const feedContextKey = `${isInAllView ? 'all' : isInSubscriptionsView ? 'subs' : isInModView ? 'mod' : (subplebbitAddress ?? 'board')}-${sortType}-${timeFilterSeconds}-${viewType ?? 'board'}-${effectiveInfiniteScroll}`;
const feedContextKey = `${isInAllView ? 'all' : isInSubscriptionsView ? 'subs' : isInModView ? 'mod' : (subplebbitAddress ?? 'board')}-${sortType}-${viewType ?? 'board'}-${effectiveInfiniteScroll}`;
const pathWithoutSettings = location.pathname.replace(/\/settings$/, '');
const currentPage = getPageFromFeedPath(pathWithoutSettings);
const paginationBasePath = stripPageFromFeedPath(pathWithoutSettings);
@@ -338,39 +265,6 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
}, 300);
};
// suggest the user to change time filter if there aren't enough posts
const { feed: weeklyFeed } = useFeed({
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 7,
});
const { feed: monthlyFeed } = useFeed({
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 30,
});
const { feed: yearlyFeed } = useFeed({
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 365,
});
const feedLength = feed.length;
const weeklyFeedLength = weeklyFeed.length;
const monthlyFeedLength = monthlyFeed.length;
const yearlyFeedLength = yearlyFeed.length;
const [showMorePostsSuggestion, setShowMorePostsSuggestion] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setShowMorePostsSuggestion(true);
}, 5000);
return () => clearTimeout(timer);
}, []);
const currentTimeFilterName = timeFilterName || params?.timeFilterName;
// Memoize footer component to preserve identity across renders (Virtuoso optimization)
// Note: useFeedStateString is called inside BoardFooter to isolate re-renders from backend state changes
const footerComponents = useMemo(
@@ -385,13 +279,6 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
showMorePostsSuggestion={showMorePostsSuggestion}
feedLength={feedLength}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName}
subplebbitState={subplebbitState}
subscriptionsLength={subscriptions?.length || 0}
accountSubplebbitAddressesLength={accountSubplebbitAddresses?.length || 0}
@@ -408,13 +295,6 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
isInAllView,
isInSubscriptionsView,
isInModView,
showMorePostsSuggestion,
feedLength,
weeklyFeedLength,
monthlyFeedLength,
yearlyFeedLength,
boardPath,
currentTimeFilterName,
subplebbitState,
subscriptions?.length,
accountSubplebbitAddresses?.length,
@@ -424,7 +304,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
);
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
const virtuosoStateKey = feedCacheKey ? `${feedCacheKey}-${sortType}-${timeFilterSeconds}` : `${location.pathname}-${sortType}-${timeFilterSeconds}`;
const virtuosoStateKey = feedCacheKey ? `${feedCacheKey}-${sortType}` : `${location.pathname}-${sortType}`;
const navigationType = useNavigationType();
const hasBeenVisibleRef = useRef(false);
@@ -514,13 +394,6 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
showMorePostsSuggestion={showMorePostsSuggestion}
feedLength={feedLength}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName}
subplebbitState={subplebbitState}
subscriptionsLength={subscriptions?.length || 0}
accountSubplebbitAddressesLength={accountSubplebbitAddresses?.length || 0}
@@ -544,13 +417,6 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
showMorePostsSuggestion={showMorePostsSuggestion}
feedLength={feedLength}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName}
subplebbitState={subplebbitState}
subscriptionsLength={subscriptions?.length || 0}
accountSubplebbitAddressesLength={accountSubplebbitAddresses?.length || 0}
+13 -179
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { Link, useLocation, useNavigate, useNavigationType, useParams } from 'react-router-dom';
import { useLocation, useNavigate, useNavigationType, useParams } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import { Comment, useAccount, useFeed, useSubplebbit, useAccountComments } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
@@ -7,9 +7,8 @@ import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
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 { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter, { timeFilterNameToSeconds } from '../../hooks/use-time-filter';
import useWindowWidth from '../../hooks/use-window-width';
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store';
@@ -28,19 +27,12 @@ const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
interface CatalogFooterProps {
subplebbitAddresses: string[];
hasMore: boolean;
feedLength: number;
combinedFeedLength: number;
subplebbitAddressesWithNewerPosts: string[];
onNewerPostsClick: () => void;
isInAllView: boolean;
isInSubscriptionsView: boolean;
isInModView: boolean;
showMorePostsSuggestion: boolean;
weeklyFeedLength: number;
monthlyFeedLength: number;
yearlyFeedLength: number;
boardPath: string | undefined;
currentTimeFilterName: string | undefined;
/** When false, suppress the loading ellipsis (e.g. non-infinite mode) */
showLoadingEllipsis?: boolean;
}
@@ -51,39 +43,26 @@ interface CatalogFooterProps {
const CatalogFooter = ({
subplebbitAddresses,
hasMore,
feedLength,
combinedFeedLength,
subplebbitAddressesWithNewerPosts,
onNewerPostsClick,
isInAllView,
isInSubscriptionsView,
isInModView,
showMorePostsSuggestion,
weeklyFeedLength,
monthlyFeedLength,
yearlyFeedLength,
boardPath,
currentTimeFilterName,
showLoadingEllipsis = true,
}: CatalogFooterProps) => {
const { t } = useTranslation();
const loadingStateString =
useFeedStateString(subplebbitAddresses) ||
(feedLength === 0 && !(weeklyFeedLength > feedLength || monthlyFeedLength > feedLength || yearlyFeedLength > monthlyFeedLength))
? t('loading_feed')
: t('looking_for_more_posts');
const loadingStateString = useFeedStateString(subplebbitAddresses) || (combinedFeedLength === 0 ? t('loading_feed') : t('looking_for_more_posts'));
let footerContent;
if (feedLength === 0) {
if (combinedFeedLength === 0) {
footerContent = t('no_threads');
}
if (combinedFeedLength === 0) {
footerContent = t('no_threads');
}
if (hasMore || (subplebbitAddresses && subplebbitAddresses.length === 0)) {
footerContent = (
<>
{subplebbitAddressesWithNewerPosts.length > 0 ? (
{subplebbitAddressesWithNewerPosts.length > 0 && (
<div className={styles.stateString}>
<Trans
i18nKey='newer_threads_available'
@@ -92,53 +71,6 @@ const CatalogFooter = ({
}}
/>
</div>
) : (
(isInAllView || isInSubscriptionsView || isInModView) &&
showMorePostsSuggestion &&
(monthlyFeedLength > feedLength || yearlyFeedLength > monthlyFeedLength) &&
(weeklyFeedLength > feedLength ? (
<div className={styles.stateString}>
<Trans
i18nKey='more_threads_last_week'
values={{ currentTimeFilterName, count: feedLength }}
components={{
1: (
<Link
to={(isInAllView ? '/all/catalog' : isInSubscriptionsView ? '/subs/catalog' : isInModView ? '/mod/catalog' : `/${boardPath}/catalog`) + '/1w'}
/>
),
}}
/>
</div>
) : monthlyFeedLength > feedLength ? (
<div className={styles.stateString}>
<Trans
i18nKey='more_threads_last_month'
values={{ currentTimeFilterName, count: feedLength }}
components={{
1: (
<Link
to={(isInAllView ? '/all/catalog' : isInSubscriptionsView ? '/subs/catalog' : isInModView ? '/mod/catalog' : `/${boardPath}/catalog`) + '/1m'}
/>
),
}}
/>
</div>
) : (
<div className={styles.stateString}>
<Trans
i18nKey='more_threads_last_year'
values={{ currentTimeFilterName, count: feedLength }}
components={{
1: (
<Link
to={(isInAllView ? '/all/catalog' : isInSubscriptionsView ? '/subs/catalog' : isInModView ? '/mod/catalog' : `/${boardPath}/catalog`) + '/1y'}
/>
),
}}
/>
</div>
))
)}
{showLoadingEllipsis && (
<div className={styles.stateString}>
@@ -156,35 +88,17 @@ const CatalogFooter = ({
interface CatalogLoadingProps {
subplebbitAddresses: string[];
hasMore: boolean;
feedLength: number;
weeklyFeedLength: number;
monthlyFeedLength: number;
yearlyFeedLength: number;
combinedFeedLength: number;
state: string | undefined;
subscriptionsLength: number;
combinedFeedLength: number;
error: Error | undefined;
}
const CatalogLoading = ({
subplebbitAddresses,
hasMore,
feedLength,
weeklyFeedLength,
monthlyFeedLength,
yearlyFeedLength,
state,
subscriptionsLength,
combinedFeedLength,
error,
}: CatalogLoadingProps) => {
const CatalogLoading = ({ subplebbitAddresses, hasMore, combinedFeedLength, state, subscriptionsLength, error }: CatalogLoadingProps) => {
const { t } = useTranslation();
const rawFeedStateString = useFeedStateString(subplebbitAddresses);
const loadingStateString =
rawFeedStateString || (feedLength === 0 && !(weeklyFeedLength > feedLength || monthlyFeedLength > feedLength || yearlyFeedLength > monthlyFeedLength))
? t('loading_feed')
: t('looking_for_more_posts');
const loadingStateString = rawFeedStateString || (combinedFeedLength === 0 ? t('loading_feed') : t('looking_for_more_posts'));
return (
<div className={styles.stateString}>
@@ -289,11 +203,10 @@ export interface CatalogProps {
feedCacheKey?: string;
viewType?: 'all' | 'subs' | 'mod' | 'board';
boardIdentifier?: string;
timeFilterNameFromCache?: string;
isVisible?: boolean;
}
const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, timeFilterNameFromCache, isVisible = true }: CatalogProps) => {
const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, isVisible = true }: CatalogProps) => {
const { t } = useTranslation();
const location = useLocation();
const navigate = useNavigate();
@@ -316,7 +229,6 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
return resolvedAddressFromUrl;
}, [boardIdentifierProp, directories, resolvedAddressFromUrl]);
const boardPath = useBoardPath(subplebbitAddress);
const { filterItems, searchText, clearMatchedFilters } = useCatalogFiltersStore();
const account = useAccount();
@@ -352,10 +264,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
}
}, [isInAllView, isInSubscriptionsView, isInModView, location.pathname, navigate]);
const { timeFilterSeconds: timeFilterSecondsFromHook, timeFilterName: timeFilterNameFromHook } = useTimeFilter();
const { sortType } = useSortingStore();
const timeFilterName = timeFilterNameFromCache || timeFilterNameFromHook;
const timeFilterSeconds = timeFilterNameFromCache ? timeFilterNameToSeconds(timeFilterNameFromCache) : timeFilterSecondsFromHook;
// Create a stable callback for filter matching
const handleFilterMatch = useCallback((filterIndex: number, cid: string, subplebbitAddress: string) => {
@@ -380,18 +289,12 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
? infiniteFeedPostsPerPage
: paginationFeedPostsPerPage;
const options: any = {
return {
subplebbitAddresses,
sortType,
postsPerPage: catalogPostsPerPage,
filter: createCombinedFilter(filterItems, searchText, subplebbitAddress || 'all', handleFilterMatch),
};
if (isInAllView || isInSubscriptionsView || isInModView) {
options.newerThan = timeFilterSeconds;
}
return options;
}, [
subplebbitAddresses,
sortType,
@@ -401,7 +304,6 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
effectiveInfiniteScroll,
infiniteFeedPostsPerPage,
paginationFeedPostsPerPage,
timeFilterSeconds,
filterItems,
searchText,
subplebbitAddress,
@@ -474,36 +376,6 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
};
// suggest the user to change time filter if there aren't enough posts
const { feed: weeklyFeed } = useFeed({
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 7,
filter: createCombinedFilter(filterItems, searchText, subplebbitAddress || 'all', handleFilterMatch),
});
const { feed: monthlyFeed } = useFeed({
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 30,
filter: createCombinedFilter(filterItems, searchText, subplebbitAddress || 'all', handleFilterMatch),
});
const { feed: yearlyFeed } = useFeed({
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 365,
filter: createCombinedFilter(filterItems, searchText, subplebbitAddress || 'all', handleFilterMatch),
});
const [showMorePostsSuggestion, setShowMorePostsSuggestion] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setShowMorePostsSuggestion(true);
}, 5000);
return () => clearTimeout(timer);
}, []);
const setResetFunction = useFeedResetStore((state) => state.setResetFunction);
useEffect(() => {
if (isVisible) {
@@ -513,12 +385,6 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
const subplebbit = useSubplebbit({ subplebbitAddress });
const { error, shortAddress, state, title } = subplebbit || {};
const feedLength = feed.length;
const weeklyFeedLength = weeklyFeed.length;
const monthlyFeedLength = monthlyFeed.length;
const yearlyFeedLength = yearlyFeed.length;
const currentTimeFilterName = timeFilterName || params?.timeFilterName;
// Memoize footer component to preserve identity across renders (Virtuoso optimization)
// Note: useFeedStateString is called inside CatalogFooter to isolate re-renders from backend state changes
@@ -528,19 +394,12 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
<CatalogFooter
subplebbitAddresses={subplebbitAddresses}
hasMore={hasMore}
feedLength={feedLength}
combinedFeedLength={cappedFeed.length}
subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts}
onNewerPostsClick={handleNewerPostsButtonClick}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
showMorePostsSuggestion={showMorePostsSuggestion}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName}
showLoadingEllipsis={effectiveInfiniteScroll}
/>
),
@@ -548,19 +407,12 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
[
subplebbitAddresses,
hasMore,
feedLength,
cappedFeed.length,
subplebbitAddressesWithNewerPosts,
handleNewerPostsButtonClick,
isInAllView,
isInSubscriptionsView,
isInModView,
showMorePostsSuggestion,
weeklyFeedLength,
monthlyFeedLength,
yearlyFeedLength,
boardPath,
currentTimeFilterName,
effectiveInfiniteScroll,
],
);
@@ -603,7 +455,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
const rows = useCatalogFeedRows(columnCount, processedFeed, isFeedLoaded, subplebbit);
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
const virtuosoStateKey = feedCacheKey ? `${feedCacheKey}-${sortType}-${timeFilterSeconds}` : `${location.pathname}-${sortType}-${timeFilterSeconds}-catalog`;
const virtuosoStateKey = feedCacheKey ? `${feedCacheKey}-${sortType}` : `${location.pathname}-${sortType}-catalog`;
const navigationType = useNavigationType();
const hasBeenVisibleRef = useRef(false);
@@ -709,19 +561,12 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
<CatalogFooter
subplebbitAddresses={subplebbitAddresses}
hasMore={hasMore}
feedLength={feedLength}
combinedFeedLength={cappedFeed.length}
subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts}
onNewerPostsClick={handleNewerPostsButtonClick}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
showMorePostsSuggestion={showMorePostsSuggestion}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName}
showLoadingEllipsis={true}
/>
</>
@@ -734,19 +579,12 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
<CatalogFooter
subplebbitAddresses={subplebbitAddresses}
hasMore={hasMore}
feedLength={feedLength}
combinedFeedLength={cappedFeed.length}
subplebbitAddressesWithNewerPosts={subplebbitAddressesWithNewerPosts}
onNewerPostsClick={handleNewerPostsButtonClick}
isInAllView={isInAllView}
isInSubscriptionsView={isInSubscriptionsView}
isInModView={isInModView}
showMorePostsSuggestion={showMorePostsSuggestion}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
boardPath={boardPath}
currentTimeFilterName={currentTimeFilterName}
showLoadingEllipsis={false}
/>
</>
@@ -757,13 +595,9 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
<CatalogLoading
subplebbitAddresses={subplebbitAddresses}
hasMore={hasMore}
feedLength={feedLength}
weeklyFeedLength={weeklyFeedLength}
monthlyFeedLength={monthlyFeedLength}
yearlyFeedLength={yearlyFeedLength}
combinedFeedLength={cappedFeed.length}
state={state}
subscriptionsLength={isInSubscriptionsView ? subscriptions?.length || 0 : 1}
combinedFeedLength={cappedFeed.length}
error={error}
/>
</div>