From 7610c11cd1b9de53ce185227ab80f86d55d3bd69 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 6 Jun 2014 22:12:14 -0400 Subject: [PATCH] closes #450 backup database before upgrade! upgrade script will take the first post of each topic and set the `mainPid` property on the topic. then it will remove that pid from the sorted sets for that topic, this was done to make alternative sorting work. added a new sorted set called `tid::posts:votes` that is used to sort topic posts by vote count, the original sorted set `tid::posts` is used to sort by oldest first or newest first. the main post is added to the returned posts array on topic load and is always at the top. theme changes are minimal just a few new data properties on the posts and the sorting dropdown. hopefully didn't miss anything too critical. --- public/language/en_GB/topic.json | 7 +- public/src/forum/infinitescroll.js | 6 +- public/src/forum/topic.js | 66 ++++++++++++------ src/controllers/api.js | 2 + src/controllers/topics.js | 12 +++- src/favourites.js | 3 +- src/middleware/middleware.js | 3 +- src/posts.js | 33 +++++++-- src/routes/feeds.js | 2 +- src/socket.io/topics.js | 14 +++- src/socket.io/user.js | 6 ++ src/topics.js | 26 ++++++- src/topics/create.js | 1 + src/topics/fork.js | 4 +- src/topics/posts.js | 106 +++++++++++++++++------------ src/upgrade.js | 70 ++++++++++++++++++- src/user/delete.js | 9 ++- src/user/settings.js | 5 ++ 18 files changed, 291 insertions(+), 84 deletions(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index 8958beb1dc..6bbb8ffe3e 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -111,5 +111,10 @@ "more_users_and_guests": "%1 more user(s) and %2 guest(s)", "more_users": "%1 more user(s)", - "more_guests": "%1 more guest(s)" + "more_guests": "%1 more guest(s)", + + "sort_by": "Sort by", + "oldest_to_newest": "Oldest to Newest", + "newest_to_oldest": "Newest to Oldest", + "most_votes": "Most votes" } diff --git a/public/src/forum/infinitescroll.js b/public/src/forum/infinitescroll.js index a496ff02e9..053635f312 100644 --- a/public/src/forum/infinitescroll.js +++ b/public/src/forum/infinitescroll.js @@ -8,14 +8,16 @@ define('forum/infinitescroll', function() { var callback; var previousScrollTop = 0; var loadingMore = false; + var topOffset = 0; - scroll.init = function(cb) { + scroll.init = function(cb, _topOffest) { callback = cb; + topOffset = _topOffest || 0; $(window).off('scroll', onScroll).on('scroll', onScroll); }; function onScroll() { - var top = $(window).height() * 0.1; + var top = $(window).height() * 0.1 + topOffset; var bottom = ($(document).height() - $(window).height()) * 0.9; var currentScrollTop = $(window).scrollTop(); diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 319feef24f..7b75f00371 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -40,6 +40,8 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ threadTools.init(tid, thread_state); events.init(); + handleSorting(); + hidePostToolsForDeletedPosts(); enableInfiniteLoadingOrPagination(); @@ -77,6 +79,21 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ socket.emit('topics.increaseViewCount', tid); }; + function handleSorting() { + var threadSort = $('.thread-sort'); + threadSort.find('i').removeClass('fa-check'); + var currentSetting = threadSort.find('a[data-sort="' + config.topicPostSort + '"]'); + currentSetting.find('i').addClass('fa-check'); + + $('.thread-sort').on('click', 'a', function() { + var newSetting = $(this).attr('data-sort'); + socket.emit('user.setTopicSort', newSetting, function(err) { + config.topicPostSort = newSetting; + ajaxify.go('topic/' + ajaxify.variables.get('topic_slug')); + }); + }); + } + function getPostIndex() { var parts = window.location.pathname.split('/'); return parts[4] ? (parseInt(parts[4], 10) - 1) : ''; @@ -122,7 +139,7 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ function enableInfiniteLoadingOrPagination() { if(!config.usePagination) { - infinitescroll.init(loadMorePosts); + infinitescroll.init(loadMorePosts, $('#post-container .post-row[data-index="0"]').height()); } else { navigator.hide(); @@ -283,25 +300,36 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ before = null; function findInsertionPoint() { - var firstPid = parseInt(data.posts[0].pid, 10); + var firstPostTimestamp = parseInt(data.posts[0].timestamp, 10); + var firstPostVotes = parseInt(data.posts[0].votes, 10); + var firstPostPid = data.posts[0].pid; - $('#post-container li[data-pid]').each(function() { - var $this = $(this); + var firstReply = $('#post-container li.post-row[data-index!="0"]').first(); + var lastReply = $('#post-container li.post-row[data-index!="0"]').last(); - if(firstPid > parseInt($this.attr('data-pid'), 10)) { - after = $this; - if(after.next().length && after.next().hasClass('post-bar')) { - after = after.next(); - } - } else { - return false; + if (config.topicPostSort === 'oldest_to_newest') { + if (firstPostTimestamp < parseInt(firstReply.attr('data-timestamp'), 10)) { + before = firstReply; + } else if(firstPostTimestamp >= parseInt(lastReply.attr('data-timestamp'), 10)) { + after = lastReply; } - }); - - if (!after) { - var firstPost = $('#post-container .post-row').first(); - if(firstPid < parseInt(firstPost.attr('data-pid'), 10)) { - before = firstPost; + } else if(config.topicPostSort === 'newest_to_oldest') { + if (firstPostTimestamp > parseInt(firstReply.attr('data-timestamp'), 10)) { + before = firstReply; + } else if(firstPostTimestamp <= parseInt(lastReply.attr('data-timestamp'), 10)) { + after = lastReply; + } + } else if(config.topicPostSort === 'most_votes') { + if (firstPostVotes > parseInt(firstReply.attr('data-votes'), 10)) { + before = firstReply; + } else if(firstPostVotes < parseInt(firstReply.attr('data-votes'), 10)) { + after = lastReply; + } else { + if (firstPostPid > firstReply.attr('data-pid')) { + before = firstReply; + } else if(firstPostPid <= firstReply.attr('data-pid')) { + after = lastReply; + } } } } @@ -373,7 +401,7 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ return; } - infinitescroll.calculateAfter(direction, '#post-container .post-row', config.postsPerPage, function(after, offset, el) { + infinitescroll.calculateAfter(direction, '#post-container .post-row[data-index!="0"]', config.postsPerPage, function(after, offset, el) { loadPostsAfter(after, function() { if (direction < 0 && el) { Topic.scrollToPost(el.attr('data-index'), false, 0, offset); @@ -384,7 +412,7 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ function loadPostsAfter(after, callback) { var tid = ajaxify.variables.get('topic_id'); - if (!utils.isNumber(tid) || !utils.isNumber(after) || (after === 0 && $('#post-container li.post-row[data-index="0"]').length)) { + if (!utils.isNumber(tid) || !utils.isNumber(after) || (after === 0 && $('#post-container li.post-row[data-index="1"]').length)) { return; } diff --git a/src/controllers/api.js b/src/controllers/api.js index d83cdab13d..ef355cbd51 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -42,6 +42,7 @@ apiController.getConfig = function(req, res, next) { config.isLoggedIn = !!req.user; config['cache-buster'] = meta.config['cache-buster'] || ''; config.requireEmailConfirmation = parseInt(meta.config.requireEmailConfirmation, 10) === 1; + config.topicPostSort = meta.config.topicPostSort || 'oldest_to_newest'; config.version = pkg.version; if (!req.user) { @@ -64,6 +65,7 @@ apiController.getConfig = function(req, res, next) { config.notificationSounds = settings.notificationSounds; config.defaultLang = settings.language || config.defaultLang; config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab; + config.topicPostSort = settings.topicPostSort || config.topicPostSort; if (res.locals.isAPI) { res.json(200, config); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index bed49056be..a73968ef5c 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -44,7 +44,17 @@ topicsController.get = function(req, res, next) { var start = (page - 1) * settings.postsPerPage + postIndex, end = start + settings.postsPerPage - 1; - topics.getTopicWithPosts(tid, uid, start, end, function (err, topicData) { + var set = 'tid:' + tid + ':posts', + reverse = false; + + if (settings.topicPostSort === 'newest_to_oldest') { + reverse = true; + } else if (settings.topicPostSort === 'most_votes') { + reverse = true; + set = 'tid:' + tid + ':posts:votes'; + } + + topics.getTopicWithPosts(tid, set, uid, start, end, reverse, function (err, topicData) { if (topicData) { if (parseInt(topicData.deleted, 10) === 1 && !userPrivileges.view_deleted) { return next(new Error('[[error:no-topic]]')); diff --git a/src/favourites.js b/src/favourites.js index 0398614b9f..9e76887faa 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -88,7 +88,8 @@ var async = require('async'), return callback(err); } var voteCount = parseInt(results.upvotes, 10) - parseInt(results.downvotes, 10); - posts.setPostField(pid, 'votes', voteCount, function(err) { + + posts.updatePostVoteCount(pid, voteCount, function(err) { callback(err, voteCount); }); }); diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 6d0a172414..4c8b6a9e5f 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -85,9 +85,10 @@ middleware.checkPostIndex = function(req, res, next) { return next(err); } var postIndex = parseInt(req.params.post_index, 10); + postCount = parseInt(postCount, 10) + 1; if (postIndex > postCount) { return res.locals.isAPI ? res.json(302, '/topic/' + req.params.topic_id + '/' + req.params.slug + '/' + postCount) : res.redirect('/topic/' + req.params.topic_id + '/' + req.params.slug + '/' + postCount); - } else if (postIndex < 1) { + } else if (postIndex <= 1) { return res.locals.isAPI ? res.json(302, '/topic/' + req.params.topic_id + '/' + req.params.slug) : res.redirect('/topic/' + req.params.topic_id + '/' + req.params.slug); } next(); diff --git a/src/posts.js b/src/posts.js index 69f555a05c..51de423df0 100644 --- a/src/posts.js +++ b/src/posts.js @@ -90,8 +90,8 @@ var db = require('./database'), ], callback); }; - Posts.getPostsByTid = function(tid, start, end, reverse, callback) { - db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange']('tid:' + tid + ':posts', start, end, function(err, pids) { + Posts.getPostsByTid = function(tid, set, start, end, reverse, callback) { + db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, end, function(err, pids) { if(err) { return callback(err); } @@ -157,8 +157,6 @@ var db = require('./database'), }); }; - - Posts.getRecentPosts = function(uid, start, stop, term, callback) { var terms = { day: 86400000, @@ -469,7 +467,9 @@ var db = require('./database'), return callback(err); } - db.sortedSetRank('tid:' + tid + ':posts', pid, callback); + db.sortedSetRank('tid:' + tid + ':posts', pid, function(err, index) { + callback(err, parseInt(index, 10) + 1); + }); }); }; @@ -482,5 +482,28 @@ var db = require('./database'), }); }; + Posts.updatePostVoteCount = function(pid, voteCount, callback) { + async.parallel([ + function(next) { + Posts.getPostField(pid, 'tid', function(err, tid) { + if (err) { + return next(err); + } + topics.getTopicField(tid, 'mainPid', function(err, mainPid) { + if (err) { + return next(err); + } + if (parseInt(mainPid, 10) === parseInt(pid, 10)) { + return next(); + } + db.sortedSetAdd('tid:' + tid + ':posts:votes', voteCount, pid, next); + }); + }); + }, + function(next) { + Posts.setPostField(pid, 'votes', voteCount, next); + } + ], callback); + }; }(exports)); diff --git a/src/routes/feeds.js b/src/routes/feeds.js index 624c06e9a1..30b3cb6c4c 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -40,7 +40,7 @@ function hasPrivileges(method, id, req, res, next) { function generateForTopic(req, res, next) { var tid = req.params.topic_id; var uid = req.user ? req.user.uid : 0; - topics.getTopicWithPosts(tid, uid, 0, 25, function (err, topicData) { + topics.getTopicWithPosts(tid, 'tid:' + tid + ':posts', uid, 0, 25, false, function (err, topicData) { if (err) { return next(err); } diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index b7c84ff127..466859038a 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -314,12 +314,22 @@ SocketTopics.loadMore = function(socket, data, callback) { return callback(err); } - var start = parseInt(data.after, 10), + var start = Math.max(parseInt(data.after, 10) - 1, 0), end = start + settings.postsPerPage - 1; + var set = 'tid:' + data.tid + ':posts', + reverse = false; + + if (settings.topicPostSort === 'newest_to_oldest') { + reverse = true; + } else if (settings.topicPostSort === 'most_votes') { + reverse = true; + set = 'tid:' + data.tid + ':posts:votes'; + } + async.parallel({ posts: function(next) { - topics.getTopicPosts(data.tid, start, end, socket.uid, false, next); + topics.getTopicPosts(data.tid, set, start, end, socket.uid, reverse, next); }, privileges: function(next) { privileges.topics.get(data.tid, socket.uid, next); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 4747c24d2f..5cf805362b 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -178,6 +178,12 @@ SocketUser.saveSettings = function(socket, data, callback) { } }; +SocketUser.setTopicSort = function(socket, sort, callback) { + if(socket.uid) { + user.setSetting(socket.uid, 'topicPostSort', sort, callback); + } +}; + SocketUser.getOnlineUsers = function(socket, data, callback) { var returnData = {}; if(!data) { diff --git a/src/topics.js b/src/topics.js index 05b57f5d33..0b225092d4 100644 --- a/src/topics.js +++ b/src/topics.js @@ -262,7 +262,7 @@ var async = require('async'), }); }; - Topics.getTopicWithPosts = function(tid, uid, start, end, callback) { + Topics.getTopicWithPosts = function(tid, set, uid, start, end, reverse, callback) { Topics.getTopicData(tid, function(err, topicData) { if (err || !topicData) { return callback(err || new Error('[[error:no-topic]]')); @@ -270,7 +270,7 @@ var async = require('async'), async.parallel({ posts: function(next) { - Topics.getTopicPosts(tid, start, end, uid, false, next); + Topics.getTopicPosts(tid, set, start, end, uid, reverse, next); }, category: function(next) { Topics.getCategoryData(tid, next); @@ -283,6 +283,26 @@ var async = require('async'), }, tags: function(next) { Topics.getTopicTagsObjects(tid, next); + }, + mainPost: function(next) { + Topics.getTopicField(tid, 'mainPid', function(err, mainPid) { + if (err) { + return next(err); + } + if (!parseInt(mainPid, 10)) { + return next(null, []); + } + posts.getPostsByPids([mainPid], function(err, postData) { + if (err) { + return next(err); + } + if (!Array.isArray(postData) || !postData.length) { + return next(null, []); + } + postData[0].index = 0; + Topics.addPostData(postData, uid, next); + }); + }); } }, function(err, results) { if (err) { @@ -290,7 +310,7 @@ var async = require('async'), } topicData.category = results.category; - topicData.posts = results.posts; + topicData.posts = results.mainPost.concat(results.posts); topicData.tags = results.tags; topicData.thread_tools = results.threadTools; topicData.pageCount = results.pageCount; diff --git a/src/topics/create.js b/src/topics/create.js index 7bf23b4bed..a6b3ef353a 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -37,6 +37,7 @@ module.exports = function(Topics) { 'tid': tid, 'uid': uid, 'cid': cid, + 'mainPid': 0, 'title': title, 'slug': slug, 'timestamp': timestamp, diff --git a/src/topics/fork.js b/src/topics/fork.js index 35880cc2ea..bd9218f875 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -71,7 +71,7 @@ module.exports = function(Topics) { return callback(err || new Error('[[error:no-topic]]')); } - posts.getPostFields(pid, ['deleted', 'tid', 'timestamp'], function(err, postData) { + posts.getPostFields(pid, ['deleted', 'tid', 'timestamp', 'votes'], function(err, postData) { if(err) { return callback(err); } @@ -91,7 +91,7 @@ module.exports = function(Topics) { } posts.setPostField(pid, 'tid', tid); - Topics.addPostToTopic(tid, pid, postData.timestamp, callback); + Topics.addPostToTopic(tid, pid, postData.timestamp, postData.votes, callback); }); }); }); diff --git a/src/topics/posts.js b/src/topics/posts.js index 13cd8ca37c..ca1b0ba4c7 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -15,13 +15,13 @@ module.exports = function(Topics) { Topics.onNewPostMade = function(postData) { Topics.increasePostCount(postData.tid); Topics.updateTimestamp(postData.tid, postData.timestamp); - Topics.addPostToTopic(postData.tid, postData.pid, postData.timestamp); + Topics.addPostToTopic(postData.tid, postData.pid, postData.timestamp, 0); }; emitter.on('event:newpost', Topics.onNewPostMade); - Topics.getTopicPosts = function(tid, start, end, uid, reverse, callback) { - posts.getPostsByTid(tid, start, end, reverse, function(err, postData) { + Topics.getTopicPosts = function(tid, set, start, end, uid, reverse, callback) { + posts.getPostsByTid(tid, set, start, end, reverse, function(err, postData) { if(err) { return callback(err); } @@ -29,52 +29,57 @@ module.exports = function(Topics) { if (Array.isArray(postData) && !postData.length) { return callback(null, []); } + start = parseInt(start, 10); for(var i=0; i