feat: add new sorting option to categories

add new zset for category topics
fix sorting names
This commit is contained in:
Barış Soner Uşaklı
2024-03-04 16:06:04 -05:00
parent db2f7c0331
commit 2a9b0a3c9c
15 changed files with 107 additions and 24 deletions

View File

@@ -103,10 +103,10 @@
"nodebb-plugin-ntfy": "1.7.3",
"nodebb-plugin-spam-be-gone": "2.2.1",
"nodebb-rewards-essentials": "1.0.0",
"nodebb-theme-harmony": "1.2.36",
"nodebb-theme-harmony": "1.2.37",
"nodebb-theme-lavender": "7.1.7",
"nodebb-theme-peace": "2.2.4",
"nodebb-theme-persona": "13.3.10",
"nodebb-theme-persona": "13.3.11",
"nodebb-widget-essentials": "7.0.15",
"nodemailer": "6.9.11",
"nprogress": "0.2.0",

View File

@@ -4,8 +4,11 @@
"sorting.post-default": "Default Post Sorting",
"sorting.oldest-to-newest": "Oldest to Newest",
"sorting.newest-to-oldest": "Newest to Oldest",
"sorting.recently-replied": "Recently Replied",
"sorting.recently-created": "Recently Created",
"sorting.most-votes": "Most Votes",
"sorting.most-posts": "Most Posts",
"sorting.most-views": "Most Views",
"sorting.topic-default": "Default Topic Sorting",
"length": "Post Length",
"post-queue": "Post Queue",

View File

@@ -206,6 +206,8 @@
"sort-by": "Sort by",
"oldest-to-newest": "Oldest to Newest",
"newest-to-oldest": "Newest to Oldest",
"recently-replied": "Recently Replied",
"recently-created": "Recently Created",
"most-votes": "Most Votes",
"most-posts": "Most Posts",
"most-views": "Most Views",

View File

@@ -124,7 +124,7 @@ categoriesAPI.getTopics = async (caller, data) => {
}
const infScrollTopicsPerPage = 20;
const sort = data.sort || data.categoryTopicSort || meta.config.categoryTopicSort || 'newest_to_oldest';
const sort = data.sort || data.categoryTopicSort || meta.config.categoryTopicSort || 'recently_replied';
let start = Math.max(0, parseInt(data.after || 0, 10));

View File

@@ -27,10 +27,9 @@ module.exports = function (Categories) {
};
Categories.getTopicIds = async function (data) {
const [pinnedTids, set, direction] = await Promise.all([
const [pinnedTids, set] = await Promise.all([
Categories.getPinnedTids({ ...data, start: 0, stop: -1 }),
Categories.buildTopicsSortedSet(data),
Categories.getSortedSetRangeDirection(data.sort),
]);
const totalPinnedCount = pinnedTids.length;
@@ -62,12 +61,11 @@ module.exports = function (Categories) {
const stop = data.stop === -1 ? data.stop : start + normalTidsToGet - 1;
let normalTids;
const reverse = direction === 'highest-to-lowest';
if (Array.isArray(set)) {
const weights = set.map((s, index) => (index ? 0 : 1));
normalTids = await db[reverse ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({ sets: set, start: start, stop: stop, weights: weights });
normalTids = await db.getSortedSetRevIntersect({ sets: set, start: start, stop: stop, weights: weights });
} else {
normalTids = await db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop);
normalTids = await db.getSortedSetRevRange(set, start, stop);
}
normalTids = normalTids.filter(tid => !pinnedTids.includes(tid));
return pinnedTidsOnPage.concat(normalTids);
@@ -92,16 +90,16 @@ module.exports = function (Categories) {
Categories.buildTopicsSortedSet = async function (data) {
const { cid } = data;
let set = `cid:${cid}:tids`;
const sort = data.sort || (data.settings && data.settings.categoryTopicSort) || meta.config.categoryTopicSort || 'newest_to_oldest';
const sort = data.sort || (data.settings && data.settings.categoryTopicSort) || meta.config.categoryTopicSort || 'recently_replied';
const sortToSet = {
recently_replied: `cid:${cid}:tids`,
recently_created: `cid:${cid}:tids:create`,
most_posts: `cid:${cid}:tids:posts`,
most_votes: `cid:${cid}:tids:votes`,
most_views: `cid:${cid}:tids:views`,
};
if (sort === 'most_posts') {
set = `cid:${cid}:tids:posts`;
} else if (sort === 'most_votes') {
set = `cid:${cid}:tids:votes`;
} else if (sort === 'most_views') {
set = `cid:${cid}:tids:views`;
}
let set = sortToSet.hasOwnProperty(sort) ? sortToSet[sort] : `cid:${cid}:tids`;
if (data.tag) {
if (Array.isArray(data.tag)) {
@@ -123,7 +121,8 @@ module.exports = function (Categories) {
};
Categories.getSortedSetRangeDirection = async function (sort) {
sort = sort || 'newest_to_oldest';
console.warn('[deprecated] Will be removed in 4.x');
sort = sort || 'recently_replied';
const direction = ['newest_to_oldest', 'most_posts', 'most_votes', 'most_views'].includes(sort) ? 'highest-to-lowest' : 'lowest-to-highest';
const result = await plugins.hooks.fire('filter:categories.getSortedSetRangeDirection', {
sort: sort,

View File

@@ -69,7 +69,7 @@ apiController.loadConfig = async function (req) {
uid: req.uid,
'cache-buster': meta.config['cache-buster'] || '',
topicPostSort: meta.config.topicPostSort || 'oldest_to_newest',
categoryTopicSort: meta.config.categoryTopicSort || 'newest_to_oldest',
categoryTopicSort: meta.config.categoryTopicSort || 'recently_replied',
csrf_token: req.uid >= 0 ? generateToken(req) : false,
searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
searchDefaultInQuick: meta.config.searchDefaultInQuick || 'titles',

View File

@@ -20,7 +20,9 @@ const categoryController = module.exports;
const url = nconf.get('url');
const relative_path = nconf.get('relative_path');
const validSorts = ['newest_to_oldest', 'oldest_to_newest', 'most_posts', 'most_votes', 'most_views'];
const validSorts = [
'recently_replied', 'recently_created', 'most_posts', 'most_votes', 'most_views',
];
categoryController.get = async function (req, res, next) {
const cid = req.params.category_id;

View File

@@ -110,6 +110,7 @@ module.exports = function (Topics) {
db.sortedSetsRemove([
`cid:${topicData.cid}:tids`,
`cid:${topicData.cid}:tids:pinned`,
`cid:${topicData.cid}:tids:create`,
`cid:${topicData.cid}:tids:posts`,
`cid:${topicData.cid}:tids:lastposttime`,
`cid:${topicData.cid}:tids:votes`,

View File

@@ -58,6 +58,7 @@ Scheduled.pin = async function (tid, topicData) {
db.sortedSetAdd(`cid:${topicData.cid}:tids:pinned`, Date.now(), tid),
db.sortedSetsRemove([
`cid:${topicData.cid}:tids`,
`cid:${topicData.cid}:tids:create`,
`cid:${topicData.cid}:tids:posts`,
`cid:${topicData.cid}:tids:votes`,
`cid:${topicData.cid}:tids:views`,
@@ -96,6 +97,7 @@ function unpin(tid, topicData) {
db.sortedSetRemove(`cid:${topicData.cid}:tids:pinned`, tid),
db.sortedSetAddBulk([
[`cid:${topicData.cid}:tids`, topicData.lastposttime, tid],
[`cid:${topicData.cid}:tids:create`, topicData.timestamp, tid],
[`cid:${topicData.cid}:tids:posts`, topicData.postcount, tid],
[`cid:${topicData.cid}:tids:votes`, parseInt(topicData.votes, 10) || 0, tid],
[`cid:${topicData.cid}:tids:views`, topicData.viewcount, tid],

View File

@@ -65,7 +65,7 @@ module.exports = function (Topics) {
const cid = await Topics.getTopicField(tid, 'cid');
const tids = cutoff === 0 ?
await db.getSortedSetRevRange(`cid:${cid}:tids:lastposttime`, 0, 9) :
await db.getSortedSetRevRangeByScore(`cid:${cid}:tids:lastposttime`, 0, 9, '+inf', Date.now() - cutoff);
await db.getSortedSetRevRangeByScore(`cid:${cid}:tids:lastposttime`, 0, 10, '+inf', Date.now() - cutoff);
return _.shuffle(tids.map(Number).filter(_tid => _tid !== tid));
}
};

View File

@@ -171,6 +171,7 @@ module.exports = function (Topics) {
promises.push(db.sortedSetAdd(`cid:${topicData.cid}:tids:pinned`, Date.now(), tid));
promises.push(db.sortedSetsRemove([
`cid:${topicData.cid}:tids`,
`cid:${topicData.cid}:tids:create`,
`cid:${topicData.cid}:tids:posts`,
`cid:${topicData.cid}:tids:votes`,
`cid:${topicData.cid}:tids:views`,
@@ -180,6 +181,7 @@ module.exports = function (Topics) {
promises.push(Topics.deleteTopicField(tid, 'pinExpiry'));
promises.push(db.sortedSetAddBulk([
[`cid:${topicData.cid}:tids`, topicData.lastposttime, tid],
[`cid:${topicData.cid}:tids:create`, topicData.timestamp, tid],
[`cid:${topicData.cid}:tids:posts`, topicData.postcount, tid],
[`cid:${topicData.cid}:tids:votes`, parseInt(topicData.votes, 10) || 0, tid],
[`cid:${topicData.cid}:tids:views`, topicData.viewcount, tid],
@@ -242,6 +244,7 @@ module.exports = function (Topics) {
const tags = await Topics.getTopicTags(tid);
await db.sortedSetsRemove([
`cid:${topicData.cid}:tids`,
`cid:${topicData.cid}:tids:create`,
`cid:${topicData.cid}:tids:pinned`,
`cid:${topicData.cid}:tids:posts`,
`cid:${topicData.cid}:tids:votes`,
@@ -264,6 +267,7 @@ module.exports = function (Topics) {
bulk.push([`cid:${cid}:tids:pinned`, Date.now(), tid]);
} else {
bulk.push([`cid:${cid}:tids`, topicData.lastposttime, tid]);
bulk.push([`cid:${cid}:tids:create`, topicData.timestamp, tid]);
bulk.push([`cid:${cid}:tids:posts`, topicData.postcount, tid]);
bulk.push([`cid:${cid}:tids:votes`, votes, tid]);
bulk.push([`cid:${cid}:tids:views`, topicData.viewcount, tid]);

View File

@@ -0,0 +1,31 @@
'use strict';
const db = require('../../database');
module.exports = {
name: 'New sorted set cid:<cid>:tids:create',
timestamp: Date.UTC(2024, 2, 4),
method: async function () {
const { progress } = this;
const batch = require('../../batch');
await batch.processSortedSet('topics:tid', async (tids) => {
let topicData = await db.getObjectsFields(
tids.map(tid => `topic:${tid}`),
['tid', 'cid', 'timestamp']
);
topicData = topicData.filter(Boolean);
topicData.forEach((t) => {
t.timestamp = t.timestamp || Date.now();
});
await db.sortedSetAddBulk(
topicData.map(t => ([`cid:${t.cid}:tids:create`, t.timestamp, t.tid]))
);
progress.incr(tids.length);
}, {
progress: this.progress,
});
},
};

View File

@@ -0,0 +1,37 @@
'use strict';
const db = require('../../database');
const batch = require('../../batch');
module.exports = {
name: 'Change category sort settings',
timestamp: Date.UTC(2024, 2, 4),
method: async function () {
const { progress } = this;
const currentSort = await db.getObjectField('config', 'categoryTopicSort');
if (currentSort === 'oldest_to_newest' || currentSort === 'newest_to_oldest') {
await db.setObjectField('config', 'categoryTopicSort', 'recently_replied');
}
await batch.processSortedSet('users:joindate', async (uids) => {
progress.incr(uids.length);
const usersSettings = await db.getObjects(uids.map(uid => `user:${uid}:settings`));
const bulkSet = [];
usersSettings.forEach((userSetting, i) => {
if (userSetting && (
userSetting.categoryTopicSort === 'newest_to_oldest' ||
userSetting.categoryTopicSort === 'oldest_to_newest')) {
bulkSet.push([
`user:${uids[i]}:settings`, { categoryTopicSort: 'recently_replied' },
]);
}
});
await db.setObjectBulk(bulkSet);
}, {
batch: 500,
progress: progress,
});
},
};

View File

@@ -67,7 +67,7 @@ module.exports = function (User) {
settings.userLang = settings.userLang || meta.config.defaultLang || 'en-GB';
settings.acpLang = settings.acpLang || settings.userLang;
settings.topicPostSort = getSetting(settings, 'topicPostSort', 'oldest_to_newest');
settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'newest_to_oldest');
settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'recently_replied');
settings.followTopicsOnCreate = parseInt(getSetting(settings, 'followTopicsOnCreate', 1), 10) === 1;
settings.followTopicsOnReply = parseInt(getSetting(settings, 'followTopicsOnReply', 0), 10) === 1;
settings.upvoteNotifFreq = getSetting(settings, 'upvoteNotifFreq', 'all');

View File

@@ -18,9 +18,11 @@
<div class="mb-3">
<label class="form-label" for="categoryTopicSort">[[admin/settings/post:sorting.topic-default]]</label>
<select id="categoryTopicSort" class="form-select" data-field="categoryTopicSort">
<option value="oldest_to_newest">[[admin/settings/post:sorting.oldest-to-newest]]</option>
<option value="newest_to_oldest">[[admin/settings/post:sorting.newest-to-oldest]]</option>
<option value="recently_replied">[[admin/settings/post:sorting.recently-replied]]</option>
<option value="recently_created">[[admin/settings/post:sorting.recently-created]]</option>
<option value="most_posts">[[admin/settings/post:sorting.most-posts]]</option>
<option value="most_votes">[[admin/settings/post:sorting.most-votes]]</option>
<option value="most_views">[[admin/settings/post:sorting.most-views]]</option>
</select>
</div>