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-alltime": "All time popular topics",
|
||||
"recent": "Recent Topics",
|
||||
"top": "Top Voted Topics",
|
||||
"moderator-tools": "Moderator Tools",
|
||||
"flagged-content": "Flagged Content",
|
||||
"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',
|
||||
name: 'Recent',
|
||||
},
|
||||
{
|
||||
route: 'top',
|
||||
name: 'Top',
|
||||
},
|
||||
{
|
||||
route: 'popular',
|
||||
name: 'Popular',
|
||||
@@ -292,6 +296,3 @@ function getHomePageRoutes(userData, callback) {
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
|
||||
module.exports = settingsController;
|
||||
|
||||
@@ -37,6 +37,10 @@ homePageController.get = function (req, res, next) {
|
||||
route: 'recent',
|
||||
name: 'Recent',
|
||||
},
|
||||
{
|
||||
route: 'top',
|
||||
name: 'Top',
|
||||
},
|
||||
{
|
||||
route: 'popular',
|
||||
name: 'Popular',
|
||||
|
||||
@@ -19,6 +19,7 @@ Controllers.category = require('./category');
|
||||
Controllers.unread = require('./unread');
|
||||
Controllers.recent = require('./recent');
|
||||
Controllers.popular = require('./popular');
|
||||
Controllers.top = require('./top');
|
||||
Controllers.tags = require('./tags');
|
||||
Controllers.search = require('./search');
|
||||
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) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
topics.getTopicField(postData.tid, 'mainPid', next);
|
||||
topics.getTopicFields(postData.tid, ['mainPid', 'cid'], next);
|
||||
},
|
||||
function (mainPid, next) {
|
||||
if (parseInt(mainPid, 10) === parseInt(postData.pid, 10)) {
|
||||
return next();
|
||||
function (topicData, next) {
|
||||
if (parseInt(topicData.mainPid, 10) === parseInt(postData.pid, 10)) {
|
||||
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);
|
||||
},
|
||||
@@ -270,7 +286,10 @@ Posts.updatePostVoteCount = function (postData, callback) {
|
||||
db.sortedSetAdd('posts:votes', postData.votes, postData.pid, 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) {
|
||||
callback(err);
|
||||
|
||||
@@ -65,6 +65,7 @@ function categoryRoutes(app, middleware, controllers) {
|
||||
setupPageRoute(app, '/categories', middleware, [], controllers.categories.list);
|
||||
setupPageRoute(app, '/popular/:term?', middleware, [], controllers.popular.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, '/category/:category_id/:slug/:topic_index', middleware, [], controllers.category.get);
|
||||
|
||||
@@ -21,6 +21,7 @@ require('./topics/delete')(Topics);
|
||||
require('./topics/unread')(Topics);
|
||||
require('./topics/recent')(Topics);
|
||||
require('./topics/popular')(Topics);
|
||||
require('./topics/top')(Topics);
|
||||
require('./topics/user')(Topics);
|
||||
require('./topics/fork')(Topics);
|
||||
require('./topics/posts')(Topics);
|
||||
@@ -165,6 +166,9 @@ Topics.getTopicsByTids = function (tids, uid, callback) {
|
||||
topics[i].bookmark = results.bookmarks[i];
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
@@ -226,6 +230,10 @@ Topics.getTopicWithPosts = function (topicData, set, uid, start, stop, reverse,
|
||||
topicData.locked = parseInt(topicData.locked, 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 = [];
|
||||
|
||||
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) {
|
||||
meta.configs.set('homePageRoute', 'popular', function (err) {
|
||||
assert.ifError(err);
|
||||
|
||||
Reference in New Issue
Block a user