mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-29 10:06:13 +01:00
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
"popular-month": "Popular topics this month",
|
"popular-month": "Popular topics this month",
|
||||||
"popular-alltime": "All time popular topics",
|
"popular-alltime": "All time popular topics",
|
||||||
"recent": "Recent Topics",
|
"recent": "Recent Topics",
|
||||||
|
"top": "Top Voted Topics",
|
||||||
"moderator-tools": "Moderator Tools",
|
"moderator-tools": "Moderator Tools",
|
||||||
"flagged-content": "Flagged Content",
|
"flagged-content": "Flagged Content",
|
||||||
"ip-blacklist": "IP Blacklist",
|
"ip-blacklist": "IP Blacklist",
|
||||||
|
|||||||
4
public/language/en-GB/top.json
Normal file
4
public/language/en-GB/top.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "Top",
|
||||||
|
"no_top_topics": "No top topics"
|
||||||
|
}
|
||||||
52
public/src/client/top.js
Normal file
52
public/src/client/top.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
define('forum/top', ['forum/recent', 'forum/infinitescroll'], function (recent, infinitescroll) {
|
||||||
|
var Top = {};
|
||||||
|
|
||||||
|
$(window).on('action:ajaxify.start', function (ev, data) {
|
||||||
|
if (ajaxify.currentPage !== data.url) {
|
||||||
|
recent.removeListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Top.init = function () {
|
||||||
|
app.enterRoom('top_topics');
|
||||||
|
|
||||||
|
recent.watchForNewPosts();
|
||||||
|
|
||||||
|
recent.handleCategorySelection();
|
||||||
|
|
||||||
|
$('#new-topics-alert').on('click', function () {
|
||||||
|
$(this).addClass('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!config.usePagination) {
|
||||||
|
infinitescroll.init(loadMoreTopics);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).trigger('action:topics.loaded', { topics: ajaxify.data.topics });
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadMoreTopics(direction) {
|
||||||
|
if (direction < 0 || !$('[component="category"]').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infinitescroll.loadMore('topics.loadMoreTopTopics', {
|
||||||
|
after: $('[component="category"]').attr('data-nextstart'),
|
||||||
|
count: config.topicsPerPage,
|
||||||
|
cid: utils.params().cid,
|
||||||
|
filter: ajaxify.data.selectedFilter.filter,
|
||||||
|
}, function (data, done) {
|
||||||
|
if (data.topics && data.topics.length) {
|
||||||
|
recent.onTopicsLoaded('top', data.topics, true, done);
|
||||||
|
$('[component="category"]').attr('data-nextstart', data.nextStart);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
$('#load-more-btn').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Top;
|
||||||
|
});
|
||||||
@@ -254,6 +254,10 @@ function getHomePageRoutes(userData, callback) {
|
|||||||
route: 'recent',
|
route: 'recent',
|
||||||
name: 'Recent',
|
name: 'Recent',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
route: 'top',
|
||||||
|
name: 'Top',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
route: 'popular',
|
route: 'popular',
|
||||||
name: 'Popular',
|
name: 'Popular',
|
||||||
@@ -292,6 +296,3 @@ function getHomePageRoutes(userData, callback) {
|
|||||||
},
|
},
|
||||||
], callback);
|
], callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = settingsController;
|
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ homePageController.get = function (req, res, next) {
|
|||||||
route: 'recent',
|
route: 'recent',
|
||||||
name: 'Recent',
|
name: 'Recent',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
route: 'top',
|
||||||
|
name: 'Top',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
route: 'popular',
|
route: 'popular',
|
||||||
name: 'Popular',
|
name: 'Popular',
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Controllers.category = require('./category');
|
|||||||
Controllers.unread = require('./unread');
|
Controllers.unread = require('./unread');
|
||||||
Controllers.recent = require('./recent');
|
Controllers.recent = require('./recent');
|
||||||
Controllers.popular = require('./popular');
|
Controllers.popular = require('./popular');
|
||||||
|
Controllers.top = require('./top');
|
||||||
Controllers.tags = require('./tags');
|
Controllers.tags = require('./tags');
|
||||||
Controllers.search = require('./search');
|
Controllers.search = require('./search');
|
||||||
Controllers.user = require('./user');
|
Controllers.user = require('./user');
|
||||||
|
|||||||
84
src/controllers/top.js
Normal file
84
src/controllers/top.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
var nconf = require('nconf');
|
||||||
|
var querystring = require('querystring');
|
||||||
|
|
||||||
|
var user = require('../user');
|
||||||
|
var topics = require('../topics');
|
||||||
|
var meta = require('../meta');
|
||||||
|
var helpers = require('./helpers');
|
||||||
|
var pagination = require('../pagination');
|
||||||
|
|
||||||
|
var topController = module.exports;
|
||||||
|
|
||||||
|
topController.get = function (req, res, next) {
|
||||||
|
var page = parseInt(req.query.page, 10) || 1;
|
||||||
|
var stop = 0;
|
||||||
|
var settings;
|
||||||
|
var cid = req.query.cid;
|
||||||
|
var filter = req.params.filter || '';
|
||||||
|
var categoryData;
|
||||||
|
var rssToken;
|
||||||
|
|
||||||
|
if (!helpers.validFilters[filter]) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
async.parallel({
|
||||||
|
settings: function (next) {
|
||||||
|
user.getSettings(req.uid, next);
|
||||||
|
},
|
||||||
|
watchedCategories: function (next) {
|
||||||
|
helpers.getWatchedCategories(req.uid, cid, next);
|
||||||
|
},
|
||||||
|
rssToken: function (next) {
|
||||||
|
user.auth.getFeedToken(req.uid, next);
|
||||||
|
},
|
||||||
|
}, next);
|
||||||
|
},
|
||||||
|
function (results, next) {
|
||||||
|
rssToken = results.rssToken;
|
||||||
|
settings = results.settings;
|
||||||
|
categoryData = results.watchedCategories;
|
||||||
|
|
||||||
|
var start = Math.max(0, (page - 1) * settings.topicsPerPage);
|
||||||
|
stop = start + settings.topicsPerPage - 1;
|
||||||
|
|
||||||
|
topics.getTopTopics(cid, req.uid, start, stop, filter, next);
|
||||||
|
},
|
||||||
|
function (data) {
|
||||||
|
data.categories = categoryData.categories;
|
||||||
|
data.selectedCategory = categoryData.selectedCategory;
|
||||||
|
data.selectedCids = categoryData.selectedCids;
|
||||||
|
data.nextStart = stop + 1;
|
||||||
|
data.set = 'topics:votes';
|
||||||
|
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
|
||||||
|
data.rssFeedUrl = nconf.get('relative_path') + '/top.rss';
|
||||||
|
if (req.uid) {
|
||||||
|
data.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
|
||||||
|
}
|
||||||
|
data.title = meta.config.homePageTitle || '[[pages:home]]';
|
||||||
|
data.filters = helpers.buildFilters('top', filter);
|
||||||
|
|
||||||
|
data.selectedFilter = data.filters.find(function (filter) {
|
||||||
|
return filter && filter.selected;
|
||||||
|
});
|
||||||
|
|
||||||
|
var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
|
||||||
|
data.pagination = pagination.create(page, pageCount, req.query);
|
||||||
|
|
||||||
|
if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/top') || req.originalUrl.startsWith(nconf.get('relative_path') + '/top')) {
|
||||||
|
data.title = '[[pages:top]]';
|
||||||
|
data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[top:title]]' }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.querystring = cid ? '?' + querystring.stringify({ cid: cid }) : '';
|
||||||
|
|
||||||
|
res.render('top', data);
|
||||||
|
},
|
||||||
|
], next);
|
||||||
|
};
|
||||||
29
src/posts.js
29
src/posts.js
@@ -256,11 +256,27 @@ Posts.updatePostVoteCount = function (postData, callback) {
|
|||||||
function (next) {
|
function (next) {
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function (next) {
|
function (next) {
|
||||||
topics.getTopicField(postData.tid, 'mainPid', next);
|
topics.getTopicFields(postData.tid, ['mainPid', 'cid'], next);
|
||||||
},
|
},
|
||||||
function (mainPid, next) {
|
function (topicData, next) {
|
||||||
if (parseInt(mainPid, 10) === parseInt(postData.pid, 10)) {
|
if (parseInt(topicData.mainPid, 10) === parseInt(postData.pid, 10)) {
|
||||||
return next();
|
async.parallel([
|
||||||
|
function (next) {
|
||||||
|
topics.setTopicFields(postData.tid, {
|
||||||
|
upvotes: postData.upvotes,
|
||||||
|
downvotes: postData.downvotes,
|
||||||
|
}, next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
db.sortedSetAdd('topics:votes', postData.votes, postData.tid, next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', postData.votes, postData.tid, next);
|
||||||
|
},
|
||||||
|
], function (err) {
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
db.sortedSetAdd('tid:' + postData.tid + ':posts:votes', postData.votes, postData.pid, next);
|
db.sortedSetAdd('tid:' + postData.tid + ':posts:votes', postData.votes, postData.pid, next);
|
||||||
},
|
},
|
||||||
@@ -270,7 +286,10 @@ Posts.updatePostVoteCount = function (postData, callback) {
|
|||||||
db.sortedSetAdd('posts:votes', postData.votes, postData.pid, next);
|
db.sortedSetAdd('posts:votes', postData.votes, postData.pid, next);
|
||||||
},
|
},
|
||||||
function (next) {
|
function (next) {
|
||||||
Posts.setPostFields(postData.pid, { upvotes: postData.upvotes, downvotes: postData.downvotes }, next);
|
Posts.setPostFields(postData.pid, {
|
||||||
|
upvotes: postData.upvotes,
|
||||||
|
downvotes: postData.downvotes,
|
||||||
|
}, next);
|
||||||
},
|
},
|
||||||
], function (err) {
|
], function (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ function categoryRoutes(app, middleware, controllers) {
|
|||||||
setupPageRoute(app, '/categories', middleware, [], controllers.categories.list);
|
setupPageRoute(app, '/categories', middleware, [], controllers.categories.list);
|
||||||
setupPageRoute(app, '/popular/:term?', middleware, [], controllers.popular.get);
|
setupPageRoute(app, '/popular/:term?', middleware, [], controllers.popular.get);
|
||||||
setupPageRoute(app, '/recent/:filter?', middleware, [], controllers.recent.get);
|
setupPageRoute(app, '/recent/:filter?', middleware, [], controllers.recent.get);
|
||||||
|
setupPageRoute(app, '/top/:filter?', middleware, [], controllers.top.get);
|
||||||
setupPageRoute(app, '/unread/:filter?', middleware, [middleware.authenticate], controllers.unread.get);
|
setupPageRoute(app, '/unread/:filter?', middleware, [middleware.authenticate], controllers.unread.get);
|
||||||
|
|
||||||
setupPageRoute(app, '/category/:category_id/:slug/:topic_index', middleware, [], controllers.category.get);
|
setupPageRoute(app, '/category/:category_id/:slug/:topic_index', middleware, [], controllers.category.get);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ require('./topics/delete')(Topics);
|
|||||||
require('./topics/unread')(Topics);
|
require('./topics/unread')(Topics);
|
||||||
require('./topics/recent')(Topics);
|
require('./topics/recent')(Topics);
|
||||||
require('./topics/popular')(Topics);
|
require('./topics/popular')(Topics);
|
||||||
|
require('./topics/top')(Topics);
|
||||||
require('./topics/user')(Topics);
|
require('./topics/user')(Topics);
|
||||||
require('./topics/fork')(Topics);
|
require('./topics/fork')(Topics);
|
||||||
require('./topics/posts')(Topics);
|
require('./topics/posts')(Topics);
|
||||||
@@ -165,6 +166,9 @@ Topics.getTopicsByTids = function (tids, uid, callback) {
|
|||||||
topics[i].bookmark = results.bookmarks[i];
|
topics[i].bookmark = results.bookmarks[i];
|
||||||
topics[i].unreplied = !topics[i].teaser;
|
topics[i].unreplied = !topics[i].teaser;
|
||||||
|
|
||||||
|
topics[i].upvotes = parseInt(topics[i].upvotes, 10) || 0;
|
||||||
|
topics[i].downvotes = parseInt(topics[i].downvotes, 10) || 0;
|
||||||
|
topics[i].votes = topics[i].upvotes - topics[i].downvotes;
|
||||||
topics[i].icons = [];
|
topics[i].icons = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,6 +230,10 @@ Topics.getTopicWithPosts = function (topicData, set, uid, start, stop, reverse,
|
|||||||
topicData.locked = parseInt(topicData.locked, 10) === 1;
|
topicData.locked = parseInt(topicData.locked, 10) === 1;
|
||||||
topicData.pinned = parseInt(topicData.pinned, 10) === 1;
|
topicData.pinned = parseInt(topicData.pinned, 10) === 1;
|
||||||
|
|
||||||
|
topicData.upvotes = parseInt(topicData.upvotes, 10) || 0;
|
||||||
|
topicData.downvotes = parseInt(topicData.downvotes, 10) || 0;
|
||||||
|
topicData.votes = topicData.upvotes - topicData.downvotes;
|
||||||
|
|
||||||
topicData.icons = [];
|
topicData.icons = [];
|
||||||
|
|
||||||
plugins.fireHook('filter:topic.get', { topic: topicData, uid: uid }, next);
|
plugins.fireHook('filter:topic.get', { topic: topicData, uid: uid }, next);
|
||||||
|
|||||||
90
src/topics/top.js
Normal file
90
src/topics/top.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
var db = require('../database');
|
||||||
|
var privileges = require('../privileges');
|
||||||
|
var user = require('../user');
|
||||||
|
var meta = require('../meta');
|
||||||
|
|
||||||
|
module.exports = function (Topics) {
|
||||||
|
Topics.getTopTopics = function (cid, uid, start, stop, filter, callback) {
|
||||||
|
var topTopics = {
|
||||||
|
nextStart: 0,
|
||||||
|
topics: [],
|
||||||
|
};
|
||||||
|
if (cid && !Array.isArray(cid)) {
|
||||||
|
cid = [cid];
|
||||||
|
}
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
var key = 'topics:votes';
|
||||||
|
if (cid) {
|
||||||
|
key = cid.map(function (cid) {
|
||||||
|
return 'cid:' + cid + ':tids:votes';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
db.getSortedSetRevRange(key, 0, 199, next);
|
||||||
|
},
|
||||||
|
function (tids, next) {
|
||||||
|
filterTids(tids, uid, filter, cid, next);
|
||||||
|
},
|
||||||
|
function (tids, next) {
|
||||||
|
topTopics.topicCount = tids.length;
|
||||||
|
tids = tids.slice(start, stop + 1);
|
||||||
|
Topics.getTopicsByTids(tids, uid, next);
|
||||||
|
},
|
||||||
|
function (topicData, next) {
|
||||||
|
topTopics.topics = topicData;
|
||||||
|
topTopics.nextStart = stop + 1;
|
||||||
|
next(null, topTopics);
|
||||||
|
},
|
||||||
|
], callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
function filterTids(tids, uid, filter, cid, callback) {
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
if (filter === 'watched') {
|
||||||
|
Topics.filterWatchedTids(tids, uid, next);
|
||||||
|
} else if (filter === 'new') {
|
||||||
|
Topics.filterNewTids(tids, uid, next);
|
||||||
|
} else if (filter === 'unreplied') {
|
||||||
|
Topics.filterUnrepliedTids(tids, next);
|
||||||
|
} else {
|
||||||
|
Topics.filterNotIgnoredTids(tids, uid, next);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (tids, next) {
|
||||||
|
privileges.topics.filterTids('read', tids, uid, next);
|
||||||
|
},
|
||||||
|
function (tids, next) {
|
||||||
|
async.parallel({
|
||||||
|
ignoredCids: function (next) {
|
||||||
|
if (filter === 'watched' || parseInt(meta.config.disableRecentCategoryFilter, 10) === 1) {
|
||||||
|
return next(null, []);
|
||||||
|
}
|
||||||
|
user.getIgnoredCategories(uid, next);
|
||||||
|
},
|
||||||
|
topicData: function (next) {
|
||||||
|
Topics.getTopicsFields(tids, ['tid', 'cid'], next);
|
||||||
|
},
|
||||||
|
}, next);
|
||||||
|
},
|
||||||
|
function (results, next) {
|
||||||
|
cid = cid && cid.map(String);
|
||||||
|
tids = results.topicData.filter(function (topic) {
|
||||||
|
if (topic && topic.cid) {
|
||||||
|
return results.ignoredCids.indexOf(topic.cid.toString()) === -1 && (!cid || (cid.length && cid.indexOf(topic.cid.toString()) !== -1));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).map(function (topic) {
|
||||||
|
return topic.tid;
|
||||||
|
});
|
||||||
|
next(null, tids);
|
||||||
|
},
|
||||||
|
], callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
60
src/upgrades/1.7.3/topic_votes.js
Normal file
60
src/upgrades/1.7.3/topic_votes.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
var batch = require('../../batch');
|
||||||
|
var db = require('../../database');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'Add votes to topics',
|
||||||
|
timestamp: Date.UTC(2017, 11, 8),
|
||||||
|
method: function (callback) {
|
||||||
|
var progress = this.progress;
|
||||||
|
|
||||||
|
batch.processSortedSet('topics:tid', function (tids, next) {
|
||||||
|
async.eachLimit(tids, 500, function (tid, _next) {
|
||||||
|
progress.incr();
|
||||||
|
var topicData;
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
db.getObjectFields('topic:' + tid, ['mainPid', 'cid'], next);
|
||||||
|
},
|
||||||
|
function (_topicData, next) {
|
||||||
|
topicData = _topicData;
|
||||||
|
if (!topicData.mainPid || !topicData.cid) {
|
||||||
|
return _next();
|
||||||
|
}
|
||||||
|
db.getObject('post:' + topicData.mainPid, next);
|
||||||
|
},
|
||||||
|
function (postData, next) {
|
||||||
|
if (!postData) {
|
||||||
|
return _next();
|
||||||
|
}
|
||||||
|
var upvotes = parseInt(postData.upvotes, 10) || 0;
|
||||||
|
var downvotes = parseInt(postData.downvotes, 10) || 0;
|
||||||
|
var data = {
|
||||||
|
upvotes: upvotes,
|
||||||
|
downvotes: downvotes,
|
||||||
|
};
|
||||||
|
var votes = upvotes - downvotes;
|
||||||
|
async.parallel([
|
||||||
|
function (next) {
|
||||||
|
db.setObject('topic:' + tid, data, next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
db.sortedSetAdd('topics:votes', votes, tid, next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', votes, tid, next);
|
||||||
|
},
|
||||||
|
], function (err) {
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
], _next);
|
||||||
|
}, next);
|
||||||
|
}, {
|
||||||
|
progress: progress,
|
||||||
|
batch: 500,
|
||||||
|
}, callback);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -118,6 +118,19 @@ describe('Controllers', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should load top', function (done) {
|
||||||
|
meta.configs.set('homePageRoute', 'top', function (err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
request(nconf.get('url'), function (err, res, body) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(res.statusCode, 200);
|
||||||
|
assert(body);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should load popular', function (done) {
|
it('should load popular', function (done) {
|
||||||
meta.configs.set('homePageRoute', 'popular', function (err) {
|
meta.configs.set('homePageRoute', 'popular', function (err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user