mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat: finalize pretext feed sizing rollout (#1120)
* feat(feeds): add pretext-backed item sizing across board, catalog, and thread replies * feat: finalize pretext feed sizing rollout * fix(catalog): raise multiboard viewport buffer * fix(board): preserve pretext query overrides
This commit is contained in:
@@ -54,7 +54,6 @@ const testState = vi.hoisted(() => ({
|
||||
hiddenCids: new Set<string>(),
|
||||
imageSize: 'Small' as 'Large' | 'Small',
|
||||
linkCount: 0,
|
||||
matchedFilters: new Map<string, string>(),
|
||||
mediaInfoByLink: {} as Record<string, { patternThumbnailUrl?: string; thumbnail?: string; type: string; url: string }>,
|
||||
lastRepliesComment: undefined as TestComment | undefined,
|
||||
replies: [] as TestComment[],
|
||||
@@ -63,12 +62,6 @@ const testState = vi.hoisted(() => ({
|
||||
showSnow: false,
|
||||
}));
|
||||
|
||||
function getCatalogFiltersState() {
|
||||
return {
|
||||
matchedFilters: testState.matchedFilters,
|
||||
};
|
||||
}
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
@@ -156,13 +149,6 @@ vi.mock('../../../hooks/use-directories', () => ({
|
||||
useDirectories: () => testState.directories,
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores/use-catalog-filters-store', () => ({
|
||||
default: <T,>(selector?: (state: ReturnType<typeof getCatalogFiltersState>) => T) => {
|
||||
const state = getCatalogFiltersState();
|
||||
return selector ? selector(state) : (state as T);
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores/use-catalog-style-store', () => ({
|
||||
default: () => ({
|
||||
imageSize: testState.imageSize,
|
||||
@@ -232,7 +218,6 @@ describe('CatalogRow', () => {
|
||||
testState.hiddenCids = new Set<string>();
|
||||
testState.imageSize = 'Small';
|
||||
testState.linkCount = 0;
|
||||
testState.matchedFilters = new Map<string, string>();
|
||||
testState.mediaInfoByLink = {};
|
||||
testState.lastRepliesComment = undefined;
|
||||
testState.replies = [];
|
||||
@@ -253,7 +238,6 @@ describe('CatalogRow', () => {
|
||||
it('renders gif frames with matched filter borders and falls back to deleted media on load errors', async () => {
|
||||
testState.gifFrameStatus = 'ready';
|
||||
testState.gifFrameUrl = 'https://cdn.example/frame.png';
|
||||
testState.matchedFilters = new Map([['post-1', 'red']]);
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
@@ -262,6 +246,7 @@ describe('CatalogRow', () => {
|
||||
commentMediaInfo: { type: 'gif', url: 'https://example.com/source.gif' },
|
||||
linkHeight: 200,
|
||||
linkWidth: 400,
|
||||
matchedFilterColor: 'red',
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -416,7 +401,7 @@ describe('CatalogRow', () => {
|
||||
expect(container.textContent).toContain('/ I: 3');
|
||||
expect(container.textContent).not.toContain('/ L: 3');
|
||||
expect(document.body.querySelector('a[href="/mu/thread/post-alias"]')).toBeTruthy();
|
||||
expect(container.querySelector('[title=\"(R)eplies / (I)mage Replies\"]')).toBeTruthy();
|
||||
expect(container.querySelector('[title="(R)eplies / (I)mage Replies"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('normalizes legacy board addresses before fetching hover preview replies', async () => {
|
||||
@@ -495,4 +480,16 @@ describe('CatalogRow', () => {
|
||||
expect(container.textContent).toContain('(hidden)');
|
||||
expect(container.textContent).toContain('Text title: Plain thread body');
|
||||
});
|
||||
|
||||
it('applies the estimated row height to the virtualization wrapper', async () => {
|
||||
const post: TestComment = {
|
||||
cid: 'estimated-post',
|
||||
content: 'Estimated row body',
|
||||
communityAddress: 'music-posting.eth',
|
||||
};
|
||||
|
||||
await renderWithRouter(createElement(CatalogRow, { estimatedHeight: 246, row: [post] }), '/mu/catalog');
|
||||
|
||||
expect(container.querySelector('[data-pretext-height="246"]')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { findDirectoryByAddress, useDirectories } from '../../hooks/use-directories';
|
||||
import { getBoardPath } from '../../lib/utils/route-utils';
|
||||
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
|
||||
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
|
||||
import useEditCommentPrivileges from '../../hooks/use-author-privileges';
|
||||
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
|
||||
@@ -32,9 +31,11 @@ interface CatalogPostMediaProps {
|
||||
isOutOfFeed?: boolean;
|
||||
linkWidth?: number;
|
||||
linkHeight?: number;
|
||||
matchedFilterColor?: string;
|
||||
}
|
||||
|
||||
export const CatalogPostMedia = ({ cid, commentMediaInfo, linkWidth, linkHeight }: CatalogPostMediaProps) => {
|
||||
export const CatalogPostMedia = ({ cid, commentMediaInfo, linkWidth, linkHeight, matchedFilterColor }: CatalogPostMediaProps) => {
|
||||
void cid;
|
||||
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
|
||||
const iframeThumbnail = patternThumbnailUrl || thumbnail;
|
||||
const { frameUrl: gifFrameUrl, status: gifFrameStatus } = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||
@@ -100,8 +101,6 @@ export const CatalogPostMedia = ({ cid, commentMediaInfo, linkWidth, linkHeight
|
||||
thumbnailComponent = <audio src={url} controls />;
|
||||
}
|
||||
|
||||
const matchedFilterColor = useCatalogFiltersStore((state) => state.matchedFilters.get(cid || ''));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={hasError ? '' : styles.mediaWrapper}
|
||||
@@ -118,7 +117,7 @@ export const CatalogPostMedia = ({ cid, commentMediaInfo, linkWidth, linkHeight
|
||||
|
||||
// Memoize CatalogPost to prevent rerenders when parent rerenders due to updatingState
|
||||
const CatalogPost = memo(
|
||||
({ post }: { post: Comment }) => {
|
||||
({ matchedFilterColor, post }: { matchedFilterColor?: string; post: Comment }) => {
|
||||
const { t } = useTranslation();
|
||||
const resolvedPost = useMemo(() => withResolvedCommentCommunityAddress(post), [post]);
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, spoiler, communityAddress, timestamp, title, thumbnailUrl } =
|
||||
@@ -260,7 +259,13 @@ const CatalogPost = memo(
|
||||
{spoiler ? (
|
||||
<img src='assets/spoiler.png' alt='' />
|
||||
) : (
|
||||
<CatalogPostMedia cid={cid} commentMediaInfo={commentMediaInfo} linkWidth={linkWidth} linkHeight={linkHeight} />
|
||||
<CatalogPostMedia
|
||||
cid={cid}
|
||||
commentMediaInfo={commentMediaInfo}
|
||||
linkWidth={linkWidth}
|
||||
linkHeight={linkHeight}
|
||||
matchedFilterColor={matchedFilterColor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -339,21 +344,24 @@ const CatalogPost = memo(
|
||||
prev?.thumbnailUrl === next?.thumbnailUrl &&
|
||||
prev?.linkWidth === next?.linkWidth &&
|
||||
prev?.linkHeight === next?.linkHeight &&
|
||||
prevCommunityAddress === nextCommunityAddress
|
||||
prevCommunityAddress === nextCommunityAddress &&
|
||||
prevProps.matchedFilterColor === nextProps.matchedFilterColor
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
interface CatalogRowProps {
|
||||
estimatedHeight?: number;
|
||||
index?: number;
|
||||
matchedFilterColors?: Map<string, string>;
|
||||
row: Comment[];
|
||||
}
|
||||
|
||||
const CatalogRow = memo(({ row }: CatalogRowProps) => {
|
||||
const CatalogRow = memo(({ estimatedHeight, matchedFilterColors, row }: CatalogRowProps) => {
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<div className={styles.row} data-pretext-height={estimatedHeight}>
|
||||
{row.map((post, index) => (
|
||||
<CatalogPost key={post?.cid || index} post={post} />
|
||||
<CatalogPost key={post?.cid || index} matchedFilterColor={matchedFilterColors?.get(post?.cid || '')} post={post} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user