2026-01-02 16:42:16 +01:00
|
|
|
import { useMemo, useRef } from 'react';
|
2026-02-28 15:02:56 +08:00
|
|
|
import { Comment, Subplebbit } from '@bitsocialhq/pkc-react-hooks';
|
2024-05-10 21:47:00 +02:00
|
|
|
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
|
|
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
const MAX_POSTS = 8;
|
|
|
|
|
const MAX_PER_SUB = 3;
|
|
|
|
|
|
2026-01-02 16:42:16 +01:00
|
|
|
/**
|
2026-02-25 16:49:43 +08:00
|
|
|
* Ranked by replyCount instead of a static threshold so the box
|
|
|
|
|
* adapts to both low- and high-activity periods.
|
|
|
|
|
*
|
|
|
|
|
* Grow-only commit: once a post enters the grid it never shifts or
|
|
|
|
|
* disappears — new posts fill remaining slots until the cap is reached.
|
2026-01-02 16:42:16 +01:00
|
|
|
*/
|
2024-05-10 21:47:00 +02:00
|
|
|
const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
2026-02-25 16:49:43 +08:00
|
|
|
const committedRef = useRef<{ posts: Comment[]; cids: Set<string> }>({
|
|
|
|
|
posts: [],
|
|
|
|
|
cids: new Set(),
|
|
|
|
|
});
|
|
|
|
|
const prevInputKeyRef = useRef('');
|
|
|
|
|
|
|
|
|
|
// Reset committed when the board set changes (e.g. NSFW filter toggle)
|
|
|
|
|
const inputKey = subplebbits
|
|
|
|
|
.map((s) => s?.address)
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.sort()
|
|
|
|
|
.join(',');
|
|
|
|
|
if (prevInputKeyRef.current !== inputKey) {
|
|
|
|
|
prevInputKeyRef.current = inputKey;
|
|
|
|
|
committedRef.current = { posts: [], cids: new Set() };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const candidates = useMemo(() => {
|
|
|
|
|
if (committedRef.current.posts.length >= MAX_POSTS) return [];
|
2024-05-10 21:47:00 +02:00
|
|
|
|
2026-01-02 16:42:16 +01:00
|
|
|
try {
|
2026-02-25 16:49:43 +08:00
|
|
|
const uniqueLinks = new Set<string>();
|
2026-01-02 16:42:16 +01:00
|
|
|
const allPosts: Comment[] = [];
|
2024-06-03 17:26:47 +02:00
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
for (const sub of subplebbits) {
|
|
|
|
|
if (!sub?.posts?.pages?.hot?.comments) continue;
|
2024-05-10 21:47:00 +02:00
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
const subPosts: Comment[] = [];
|
|
|
|
|
for (const post of Object.values(sub.posts.pages.hot.comments as Comment)) {
|
|
|
|
|
const { deleted, link, linkHeight, linkWidth, locked, pinned, removed, thumbnailUrl } = post;
|
2024-05-10 21:47:00 +02:00
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
try {
|
|
|
|
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
|
|
|
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
2024-05-10 21:47:00 +02:00
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
if (hasThumbnail && !deleted && !removed && !locked && !pinned && !uniqueLinks.has(link)) {
|
|
|
|
|
subPosts.push(post);
|
|
|
|
|
uniqueLinks.add(link);
|
2024-10-12 17:09:23 +02:00
|
|
|
}
|
2026-02-25 16:49:43 +08:00
|
|
|
} catch {
|
|
|
|
|
// skip posts with malformed media URLs
|
2024-05-10 21:47:00 +02:00
|
|
|
}
|
2026-01-02 16:42:16 +01:00
|
|
|
}
|
2026-02-25 16:49:43 +08:00
|
|
|
|
|
|
|
|
subPosts.sort((a, b) => (b.replyCount ?? 0) - (a.replyCount ?? 0));
|
|
|
|
|
allPosts.push(...subPosts.slice(0, MAX_PER_SUB));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Primary: most replies first. Tiebreaker: newest first.
|
|
|
|
|
allPosts.sort((a, b) => {
|
|
|
|
|
const diff = (b.replyCount ?? 0) - (a.replyCount ?? 0);
|
|
|
|
|
return diff !== 0 ? diff : (b.timestamp ?? 0) - (a.timestamp ?? 0);
|
2026-01-02 16:42:16 +01:00
|
|
|
});
|
2024-10-12 17:09:23 +02:00
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
return allPosts;
|
2026-01-02 16:42:16 +01:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error in usePopularPosts:', err);
|
2026-02-25 16:49:43 +08:00
|
|
|
return [];
|
2026-01-02 16:42:16 +01:00
|
|
|
}
|
2024-05-10 21:47:00 +02:00
|
|
|
}, [subplebbits]);
|
|
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
// Grow-only: committed posts keep their position, new ones fill empty slots
|
|
|
|
|
const { posts, cids } = committedRef.current;
|
|
|
|
|
for (const post of candidates) {
|
|
|
|
|
if (posts.length >= MAX_POSTS) break;
|
|
|
|
|
if (post.cid && !cids.has(post.cid)) {
|
|
|
|
|
posts.push(post);
|
|
|
|
|
cids.add(post.cid);
|
|
|
|
|
}
|
2026-01-02 16:42:16 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-03 16:14:08 +01:00
|
|
|
const hasLoadedData = subplebbits.some((sub) => sub?.posts?.pages?.hot?.comments);
|
|
|
|
|
const isLoading = subplebbits.length > 0 && !hasLoadedData;
|
2026-01-02 16:42:16 +01:00
|
|
|
|
2026-02-25 16:49:43 +08:00
|
|
|
return { popularPosts: posts, isLoading, error: null as string | null };
|
2024-05-10 21:47:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default usePopularPosts;
|