mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: add new sorting option to categories
add new zset for category topics fix sorting names
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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]);
|
||||
|
||||
31
src/upgrades/3.7.0/category-tid-created-zset.js
Normal file
31
src/upgrades/3.7.0/category-tid-created-zset.js
Normal 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,
|
||||
});
|
||||
},
|
||||
};
|
||||
37
src/upgrades/3.7.0/change-category-sort-settings.js
Normal file
37
src/upgrades/3.7.0/change-category-sort-settings.js
Normal 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,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user