refactor: /world sorting logic to always use topics/sorted logic

- New params for getSortedTopics (includeRemote, followingOnly)
- Ability to show latest (followers only) or latest (all), ?all query param to discriminate
- World now always shows posts from the local forum
- Popular sort will be followers-only + local
This commit is contained in:
Julian Lam
2026-03-13 12:41:55 -04:00
parent 38a1da4609
commit e2131d1d2e
3 changed files with 40 additions and 33 deletions

View File

@@ -1,6 +1,7 @@
{ {
"name": "World", "name": "World",
"latest": "Latest", "latest": "Latest (Following)",
"latest-all": "Latest (All)",
"popular-day": "Popular (Day)", "popular-day": "Popular (Day)",
"popular-week": "Popular (Week)", "popular-week": "Popular (Week)",
"popular-month": "Popular (Month)", "popular-month": "Popular (Month)",

View File

@@ -2,7 +2,6 @@
const _ = require('lodash'); const _ = require('lodash');
const db = require('../../database');
const meta = require('../../meta'); const meta = require('../../meta');
const user = require('../../user'); const user = require('../../user');
const topics = require('../../topics'); const topics = require('../../topics');
@@ -20,8 +19,8 @@ controller.list = async function (req, res) {
const { topicsPerPage } = await user.getSettings(req.uid); const { topicsPerPage } = await user.getSettings(req.uid);
let { page, after } = req.query; let { page, after } = req.query;
page = parseInt(page, 10) || 1; page = parseInt(page, 10) || 1;
let start = Math.max(0, (page - 1) * topicsPerPage); const start = Math.max(0, (page - 1) * topicsPerPage);
let stop = start + topicsPerPage - 1; const stop = start + topicsPerPage - 1;
const [userSettings, userPrivileges] = await Promise.all([ const [userSettings, userPrivileges] = await Promise.all([
user.getSettings(req.uid), user.getSettings(req.uid),
@@ -50,27 +49,24 @@ controller.list = async function (req, res) {
if (req.query.sort === 'popular') { if (req.query.sort === 'popular') {
cidQuery = { cidQuery = {
...cidQuery, ...cidQuery,
cids: ['-1'],
sort: 'posts', sort: 'posts',
term: req.query.term || 'day', term: req.query.term || 'day',
includeRemote: true,
followingOnly: !req.query.all || !parseInt(req.query.all, 10),
}; };
delete cidQuery.cid; delete cidQuery.cid;
({ tids, topicCount } = await topics.getSortedTopics(cidQuery)); ({ tids, topicCount } = await topics.getSortedTopics(cidQuery));
tids = tids.slice(start, stop !== -1 ? stop + 1 : undefined); tids = tids.slice(start, stop !== -1 ? stop + 1 : undefined);
} else { } else {
if (after) { cidQuery = {
// Update start/stop with values inferred from `after` ...cidQuery,
const set = await categories.buildTopicsSortedSet(cidQuery); term: req.query.term,
const index = await db.sortedSetRevRank(set, decodeURIComponent(after)); includeRemote: true,
if (index && start - index < 1) { followingOnly: !req.query.all || !parseInt(req.query.all, 10),
const count = stop - start; };
start = index + 1; delete cidQuery.cid;
stop = start + count; ({ tids, topicCount } = await topics.getSortedTopics(cidQuery));
} tids = tids.slice(start, stop !== -1 ? stop + 1 : undefined);
}
tids = await categories.getTopicIds({ ...cidQuery, start, stop });
topicCount = await categories.getTopicCount(cidQuery);
} }
data.topicCount = topicCount; data.topicCount = topicCount;

View File

@@ -46,7 +46,8 @@ module.exports = function (Topics) {
let tids; let tids;
if (params.term !== 'alltime') { if (params.term !== 'alltime') {
if (params.sort === 'posts') { if (params.sort === 'posts') {
tids = await getTidsWithMostPostsInTerm(params.cids, params.uid, params.term); const { cids, uid, term, includeRemote, followingOnly } = params;
tids = await getTidsWithMostPostsInTerm({ cids, uid, term, includeRemote, followingOnly });
} else { } else {
const cids = await getCids(params.cids, params.uid); const cids = await getCids(params.cids, params.uid);
tids = await Topics.getLatestTidsFromSet( tids = await Topics.getLatestTidsFromSet(
@@ -74,17 +75,18 @@ module.exports = function (Topics) {
} }
async function getInbox(tids, params) { async function getInbox(tids, params) {
if (!Array.isArray(params.cids) || !params.cids.includes('-1')) { if (!params.includeRemote) {
return tids; return tids;
} }
let inbox; let inbox;
const set = params.followingOnly ? `uid:${params.uid}:inbox` : 'cid:-1:tids';
if (params.term !== 'alltime') { if (params.term !== 'alltime') {
const method = params.sort === 'old' ? const method = params.sort === 'old' ?
'getSortedSetRangeByScore' : 'getSortedSetRangeByScore' :
'getSortedSetRevRangeByScore'; 'getSortedSetRevRangeByScore';
inbox = await db[method]( inbox = await db[method](
`uid:${params.uid}:inbox`, set,
0, 0,
1000, 1000,
'+inf', '+inf',
@@ -94,7 +96,7 @@ module.exports = function (Topics) {
const method = params.sort === 'old' ? const method = params.sort === 'old' ?
'getSortedSetRange' : 'getSortedSetRange' :
'getSortedSetRevRange'; 'getSortedSetRevRange';
inbox = await db[method](`uid:${params.uid}:inbox`, 0, meta.config.recentMaxTopics - 1); inbox = await db[method](set, 0, meta.config.recentMaxTopics - 1);
} }
return _.uniq(tids.concat(inbox)); return _.uniq(tids.concat(inbox));
@@ -115,33 +117,41 @@ module.exports = function (Topics) {
return 'topics:recent'; return 'topics:recent';
} }
async function getCids(cids, uid) { async function getCids(cids, uid, includeRemote) {
if (Array.isArray(cids)) { if (Array.isArray(cids)) {
cids = await privileges.categories.filterCids('topics:read', cids, uid); cids = await privileges.categories.filterCids('topics:read', cids, uid);
} else { } else {
cids = await categories.getCidsByPrivilege('categories:cid', uid, 'topics:read'); cids = await categories.getCidsByPrivilege('categories:cid', uid, 'topics:read');
cids = cids.filter(cid => cid !== -1); cids = cids.filter(cid => cid !== -1);
} }
if (includeRemote) {
const remoteCids = await db.getObjectValues('handle:cid');
cids = [-1, ...cids, ...remoteCids];
}
return cids; return cids;
} }
async function getTidsWithMostPostsInTerm(cids, uid, term) { async function getTidsWithMostPostsInTerm({ cids, uid, term, includeRemote, followingOnly }) {
cids = await getCids(cids, uid); cids = await getCids(cids, uid, includeRemote);
const pids = await db.getSortedSetRevRangeByScore( const sets = cids.map(cid => `cid:${cid}:tids`);
cids.map(cid => `cid:${cid}:pids`), if (followingOnly && sets.includes('cid:-1:tids')) {
sets.splice(sets.indexOf('cid:-1:tids'), 1, `uid:${uid}:inbox`);
}
const tids = await db.getSortedSetRevRangeByScore(
sets,
0, 0,
1000, 1000,
'+inf', '+inf',
Date.now() - Topics.getSinceFromTerm(term) Date.now() - Topics.getSinceFromTerm(term)
); );
const postObjs = await db.getObjectsFields(pids.map(pid => `post:${pid}`), ['tid']);
const tidToCount = {}; const tidToCount = {};
postObjs.forEach((post) => { tids.forEach((tid) => {
tidToCount[post.tid] = tidToCount[post.tid] || 0; tidToCount[tid] = tidToCount[tid] || 0;
tidToCount[post.tid] += 1; tidToCount[tid] += 1;
}); });
return _.uniq(postObjs.map(post => String(post.tid))) return _.uniq(tids)
.sort((t1, t2) => tidToCount[t2] - tidToCount[t1]); .sort((t1, t2) => tidToCount[t2] - tidToCount[t1]);
} }
@@ -200,7 +210,7 @@ module.exports = function (Topics) {
} }
async function sortTids(tids, params) { async function sortTids(tids, params) {
if (params.term === 'alltime' && !params.cids && !params.tags.length && params.filter !== 'watched' && !params.floatPinned) { if (params.term === 'alltime' && !params.cids && !params.tags.length && params.filter !== 'watched' && !params.floatPinned && !params.includeRemote) {
return tids; return tids;
} }