Skip to content

Commit

Permalink
feat: add new sorting option to categories
Browse files Browse the repository at this point in the history
add new zset for category topics
fix sorting names
  • Loading branch information
barisusakli committed Mar 4, 2024
1 parent db2f7c0 commit 2a9b0a3
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 25 deletions.
4 changes: 2 additions & 2 deletions install/package.json
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions public/language/en-GB/admin/settings/post.json
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions public/language/en-GB/topic.json
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/api/categories.js
Expand Up @@ -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));

Expand Down
31 changes: 15 additions & 16 deletions src/categories/topics.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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';

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`;
}
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`,
};

let set = sortToSet.hasOwnProperty(sort) ? sortToSet[sort] : `cid:${cid}:tids`;

if (data.tag) {
if (Array.isArray(data.tag)) {
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/api.js
Expand Up @@ -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',
Expand Down
4 changes: 3 additions & 1 deletion src/controllers/category.js
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/topics/delete.js
Expand Up @@ -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`,
Expand Down
2 changes: 2 additions & 0 deletions src/topics/scheduled.js
Expand Up @@ -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`,
Expand Down Expand Up @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion src/topics/suggested.js
Expand Up @@ -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));
}
};
4 changes: 4 additions & 0 deletions src/topics/tools.js
Expand Up @@ -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`,
Expand All @@ -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],
Expand Down Expand Up @@ -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`,
Expand All @@ -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]);
Expand Down
31 changes: 31 additions & 0 deletions src/upgrades/3.7.0/category-tid-created-zset.js
@@ -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,
});
},
};
37 changes: 37 additions & 0 deletions src/upgrades/3.7.0/change-category-sort-settings.js
@@ -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,
});
},
};
2 changes: 1 addition & 1 deletion src/user/settings.js
Expand Up @@ -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');
Expand Down
6 changes: 4 additions & 2 deletions src/views/admin/settings/post.tpl
Expand Up @@ -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>

Expand Down

0 comments on commit 2a9b0a3

Please sign in to comment.