mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(popular-posts): rank by time-decayed popularity instead of raw reply count
This commit is contained in:
@@ -5,9 +5,24 @@ import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
|
|||||||
const MAX_POSTS = 8;
|
const MAX_POSTS = 8;
|
||||||
const MAX_PER_SUB = 3;
|
const MAX_PER_SUB = 3;
|
||||||
|
|
||||||
|
// Activity relevance halves every 3 days
|
||||||
|
const HALF_LIFE_SECONDS = 72 * 3600;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ranked by replyCount instead of a static threshold so the box
|
* Time-decayed popularity: replyCount divided by age of latest
|
||||||
* adapts to both low- and high-activity periods.
|
* activity so a stale post with many old replies loses to a newer
|
||||||
|
* post with a few recent replies.
|
||||||
|
*/
|
||||||
|
function popularityScore(post: Comment, nowSeconds: number): number {
|
||||||
|
const lastActivity = post.lastReplyTimestamp ?? post.timestamp ?? 0;
|
||||||
|
const ageSeconds = Math.max(0, nowSeconds - lastActivity);
|
||||||
|
const replies = post.replyCount ?? 0;
|
||||||
|
return Math.max(replies, 0.1) / (1 + ageSeconds / HALF_LIFE_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ranked by time-decayed popularity so the box surfaces posts with
|
||||||
|
* recent engagement rather than stale all-time reply leaders.
|
||||||
*
|
*
|
||||||
* Grow-only commit: once a post enters the grid it never shifts or
|
* Grow-only commit: once a post enters the grid it never shifts or
|
||||||
* disappears — new posts fill remaining slots until the cap is reached.
|
* disappears — new posts fill remaining slots until the cap is reached.
|
||||||
@@ -33,6 +48,8 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
|||||||
const candidates = useMemo(() => {
|
const candidates = useMemo(() => {
|
||||||
if (committedRef.current.posts.length >= MAX_POSTS) return [];
|
if (committedRef.current.posts.length >= MAX_POSTS) return [];
|
||||||
|
|
||||||
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uniqueLinks = new Set<string>();
|
const uniqueLinks = new Set<string>();
|
||||||
const allPosts: Comment[] = [];
|
const allPosts: Comment[] = [];
|
||||||
@@ -57,15 +74,11 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subPosts.sort((a, b) => (b.replyCount ?? 0) - (a.replyCount ?? 0));
|
subPosts.sort((a, b) => popularityScore(b, nowSeconds) - popularityScore(a, nowSeconds));
|
||||||
allPosts.push(...subPosts.slice(0, MAX_PER_SUB));
|
allPosts.push(...subPosts.slice(0, MAX_PER_SUB));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primary: most replies first. Tiebreaker: newest first.
|
allPosts.sort((a, b) => popularityScore(b, nowSeconds) - popularityScore(a, nowSeconds));
|
||||||
allPosts.sort((a, b) => {
|
|
||||||
const diff = (b.replyCount ?? 0) - (a.replyCount ?? 0);
|
|
||||||
return diff !== 0 ? diff : (b.timestamp ?? 0) - (a.timestamp ?? 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
return allPosts;
|
return allPosts;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user