mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +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