From b685194dae6f59cd48d428b9ecaedcdcaa907793 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 7 Jan 2015 16:38:23 -0500 Subject: [PATCH 1/6] updated helper loading so it loads all helpers in helpers local var --- public/src/modules/helpers.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 1d13f93bf7..838ad7a4da 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -43,8 +43,9 @@ templates = window.templates; } - templates.registerHelper('displayUsersLink', helpers.displayUsersLink); - templates.registerHelper('buildMetaTag', helpers.buildMetaTag); + Object.keys(helpers).forEach(function(helperName) { + templates.registerHelper(helperName, helpers[helperName]); + }); }; // Use the define() function if we're in AMD land From ce9c17f8e540bf4f0295aabf61eecd3986f437cc Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Jan 2015 17:27:09 -0500 Subject: [PATCH 2/6] ability to search posts by user --- public/language/en_GB/search.json | 4 +- public/src/app.js | 2 +- public/src/client/search.js | 69 +++++++++++++++++++++++++------ public/src/client/users.js | 7 ++-- public/src/modules/search.js | 13 ++++-- src/controllers/search.js | 7 +++- src/search.js | 27 ++++++++++-- src/socket.io/user.js | 7 +++- 8 files changed, 108 insertions(+), 28 deletions(-) diff --git a/public/language/en_GB/search.json b/public/language/en_GB/search.json index 945e58c852..23519b038b 100644 --- a/public/language/en_GB/search.json +++ b/public/language/en_GB/search.json @@ -1,5 +1,7 @@ { "results_matching": "%1 result(s) matching \"%2\", (%3 seconds)", "no-matches": "No matches found", - "in": "In" + "in": "In", + "by": "By", + "posted-by": "Posted by" } diff --git a/public/src/app.js b/public/src/app.js index d894551064..a77200fdd9 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -437,7 +437,7 @@ app.uid = null; e.preventDefault(); var input = $(this).find('input'); - search.query(input.val(), 'posts', function() { + search.query({term: input.val(), in: 'posts'}, function() { input.val(''); }); }); diff --git a/public/src/client/search.js b/public/src/client/search.js index e08a5dc410..e536aaa160 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -1,18 +1,51 @@ +'use strict'; + +/* globals app, define, utils, socket*/ + define('forum/search', ['search'], function(searchModule) { var Search = {}; Search.init = function() { var searchQuery = $('#results').attr('data-search-query'); - var regexes = []; - var searchTerms = searchQuery.split(' '); - $('#advanced-search input').val(searchQuery); + $('#advanced-search #search-input').val(searchQuery); var params = utils.params(); + var select = $('#advanced-search select'); if (params && params.in) { - $('#advanced-search select').val(params.in); + select.val(params.in); } + if (params && params.by) { + $('.by-container #posted-by-input').val(params.by); + } + + select.on('change', function() { + $('.by-container').toggleClass('hide', select.val() !== 'posts'); + }); + + highlightMatches(searchQuery); + $('#advanced-search').off('submit').on('submit', function(e) { + e.preventDefault(); + var input = $(this).find('#search-input'); + var searchIn = $(this).find('select'); + var postedBy = $(this).find('#posted-by-input'); + + searchModule.query({ + term: input.val(), + in: searchIn.val(), + by: postedBy.val() + }, function() { + input.val(''); + }); + }); + + enableAutoComplete(); + }; + + function highlightMatches(searchQuery) { + var searchTerms = searchQuery.split(' '); + var regexes = []; for (var i=0; i'); - socket.emit('user.search', username, function(err, data) { + socket.emit('user.search', {query: username}, function(err, data) { if (err) { reset(); return app.alertError(err.message); } if (!data) { - reset(); - return; + return reset(); } templates.parse('users', 'users', data, function(html) { translator.translate(html, function(translated) { $('#users-container').html(translated); - + if (!data.users.length) { translator.translate('[[error:no-user]]', function(translated) { notify.html(translated); diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 7dd7abddff..73ff828673 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -7,7 +7,11 @@ define('search', ['navigator'], function(nav) { current: {} }; - Search.query = function(term, searchIn, callback) { + Search.query = function(data, callback) { + var term = data.term; + var searchIn = data.in || 'posts'; + var postedBy = data.by || ''; + // Detect if a tid was specified var topicSearch = term.match(/in:topic-([\d]+)/); @@ -19,8 +23,11 @@ define('search', ['navigator'], function(nav) { } catch(e) { return app.alertError('[[error:invalid-search-term]]'); } - - ajaxify.go('search/' + term + (searchIn ? '?in=' + searchIn : '')); + var query = {in: searchIn}; + if (postedBy && searchIn === 'posts') { + query.by = postedBy; + } + ajaxify.go('search/' + term + '?' + decodeURIComponent($.param(query))); callback(); } else { var cleanedTerm = term.replace(topicSearch[0], ''), diff --git a/src/controllers/search.js b/src/controllers/search.js index 1d4aff306c..1a5690a6e7 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -28,7 +28,12 @@ searchController.search = function(req, res, next) { req.params.term = validator.escape(req.params.term); - search.search(req.params.term, req.query.in, uid, function(err, results) { + search.search({ + query: req.params.term, + searchIn: req.query.in, + postedBy: req.query.by, + uid: uid + }, function(err, results) { if (err) { return next(err); } diff --git a/src/search.js b/src/search.js index 869455b3de..c4a830f41a 100644 --- a/src/search.js +++ b/src/search.js @@ -11,7 +11,7 @@ var search = {}; module.exports = search; -search.search = function(query, searchIn, uid, callback) { +search.search = function(data, callback) { function done(err, data) { if (err) { return callback(err); @@ -20,12 +20,16 @@ search.search = function(query, searchIn, uid, callback) { result.search_query = query; result[searchIn] = data; result.matchCount = data.length; + result.hidePostedBy = searchIn !== 'posts'; result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2); callback(null, result); } var start = process.hrtime(); - searchIn = searchIn || 'posts'; + + var query = data.query; + var searchIn = data.searchIn || 'posts'; + var uid = data.uid || 0; var result = { posts: [], @@ -34,7 +38,7 @@ search.search = function(query, searchIn, uid, callback) { }; if (searchIn === 'posts') { - searchInPosts(query, uid, done); + searchInPosts(query, data.postedBy, uid, done); } else if (searchIn === 'users') { searchInUsers(query, done); } else if (searchIn === 'tags') { @@ -44,13 +48,20 @@ search.search = function(query, searchIn, uid, callback) { } }; -function searchInPosts(query, uid, callback) { +function searchInPosts(query, postedBy, uid, callback) { async.parallel({ pids: function(next) { searchQuery('post', query, next); }, tids: function(next) { searchQuery('topic', query, next); + }, + postedByUid: function(next) { + if (postedBy) { + user.getUidByUsername(postedBy, next); + } else { + next(null, 0); + } } }, function (err, results) { if (err) { @@ -75,6 +86,14 @@ function searchInPosts(query, uid, callback) { }, function(pids, next) { posts.getPostSummaryByPids(pids, uid, {stripTags: true, parse: false}, next); + }, + function(posts, next) { + if (postedBy) { + posts = posts.filter(function(post) { + return post && parseInt(post.uid, 10) === parseInt(results.postedByUid, 10); + }); + } + next(null, posts); } ], callback); }); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index e20ff65bbb..392327c93f 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -62,11 +62,14 @@ SocketUser.increaseViewCount = function(socket, uid, callback) { } }; -SocketUser.search = function(socket, username, callback) { +SocketUser.search = function(socket, data, callback) { + if (!data) { + return callback(new Error('[[error:invalid-data]]')) + } if (!socket.uid) { return callback(new Error('[[error:not-logged-in]]')); } - user.search({query: username}, callback); + user.search({query: data.query}, callback); }; // Password Reset From 1a13f2a54aef4338ed39f72359dc65d2d6b448cb Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 7 Jan 2015 17:23:39 -0500 Subject: [PATCH 3/6] no need to crash nodebb on missing template directory warning --- src/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins.js b/src/plugins.js index 62cf8c8695..bd00bb8b0a 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -156,7 +156,7 @@ var fs = require('fs'), winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.'); } - next(err); + next(false); }); } else { next(false); From 5ee0ed401f72ddd822e0093d19a9779cf2039a64 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 7 Jan 2015 17:45:01 -0500 Subject: [PATCH 4/6] fixed ajaxify's redirecting to external URLs.. well fixed redirecting in general --- public/src/ajaxify.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 32f5a8bfbd..4896c58195 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -28,15 +28,19 @@ $(document).ready(function() { textStatus = err.textStatus; if (data) { - if (data.status === 403 || data.status === 404 || data.status === 500) { + var status = parseInt(data.status, 10); + + if (status === 403 || status === 404 || status === 500) { $('#footer, #content').removeClass('hide').addClass('ajaxifying'); - return renderTemplate(url, data.status.toString(), data.responseJSON, (new Date()).getTime(), callback); - } else if (data.status === 401) { + return renderTemplate(url, status.toString(), data.responseJSON, (new Date()).getTime(), callback); + } else if (status === 401) { app.alertError('[[global:please_log_in]]'); app.previousUrl = url; return ajaxify.go('login'); - } else if (data.status === 302) { - return ajaxify.go(data.responseJSON.slice(1), callback, quiet); + } else if (status === 302) { + if (!ajaxify.go(data.responseJSON.path, callback, quiet)) { + window.location.href = data.responseJSON.path; + } } } else if (textStatus !== "abort") { app.alertError(data.responseJSON.error); From 52cf224e5a21d0b0b7a764ea6863dd76859e49f3 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 7 Jan 2015 18:31:32 -0500 Subject: [PATCH 5/6] translator.getTranslations & bug fix --- public/src/translator.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/src/translator.js b/public/src/translator.js index 2fd8b79d1a..07794f360c 100644 --- a/public/src/translator.js +++ b/public/src/translator.js @@ -21,6 +21,16 @@ languages[language].loading = languages[language].loading || {}; }; + translator.getTranslations = function(language, filename, callback) { + if (languages[language] && languages[language].loaded[filename]) { + callback(languages[language].loaded[filename]); + } else { + translator.load(language, filename, function() { + callback(languages[language].loaded[filename]); + }); + } + } + translator.getLanguage = function() { return config.defaultLang; }; @@ -180,7 +190,7 @@ callback(translations); } - while (languages[language].callbacks[filename] && languages[language].callbacks[filename].length) { + while (languages[language].callbacks && languages[language].callbacks[filename] && languages[language].callbacks[filename].length) { languages[language].callbacks[filename].pop()(translations); } From a77a761433d52cfcb624bf3aa5a78d270b75c343 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Jan 2015 13:47:15 -0500 Subject: [PATCH 6/6] closes #1917 --- public/language/en_GB/topic.json | 3 ++- public/src/client/category.js | 12 ++++++++- public/src/client/topic.js | 22 +++------------- public/src/modules/sort.js | 26 +++++++++++++++++++ src/categories.js | 5 ++-- src/categories/delete.js | 1 + src/categories/topics.js | 13 +++++++--- src/controllers/api.js | 2 ++ src/controllers/categories.js | 15 +++++++++++ src/posts/delete.js | 5 +++- src/routes/feeds.js | 2 ++ src/socket.io/categories.js | 19 +++++++++++++- src/socket.io/topics.js | 2 +- src/socket.io/user.js | 6 +++++ src/threadTools.js | 17 ++++++++++--- src/topics/delete.js | 1 + src/upgrade.js | 42 ++++++++++++++++++++++++++++++- src/user/settings.js | 1 + src/views/admin/settings/post.tpl | 8 ++++++ tests/categories.js | 6 +++++ 20 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 public/src/modules/sort.js diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index 1320e21923..5fc5863225 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -112,5 +112,6 @@ "sort_by": "Sort by", "oldest_to_newest": "Oldest to Newest", "newest_to_oldest": "Newest to Oldest", - "most_votes": "Most votes" + "most_votes": "Most votes", + "most_posts": "Most posts" } diff --git a/public/src/client/category.js b/public/src/client/category.js index 073d3568aa..2f33a0d33c 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -1,7 +1,15 @@ "use strict"; /* global define, config, templates, app, utils, ajaxify, socket, translator */ -define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll', 'share', 'navigator', 'forum/categoryTools'], function(composer, pagination, infinitescroll, share, navigator, categoryTools) { +define('forum/category', [ + 'composer', + 'forum/pagination', + 'forum/infinitescroll', + 'share', + 'navigator', + 'forum/categoryTools', + 'sort' +], function(composer, pagination, infinitescroll, share, navigator, categoryTools, sort) { var Category = {}; $(window).on('action:ajaxify.start', function(ev, data) { @@ -33,6 +41,8 @@ define('forum/category', ['composer', 'forum/pagination', 'forum/infinitescroll' categoryTools.init(cid); + sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.variables.get('category_slug')); + enableInfiniteLoadingOrPagination(); if (!config.usePagination) { diff --git a/public/src/client/topic.js b/public/src/client/topic.js index bf328d869e..e1d3a1a153 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -11,8 +11,9 @@ define('forum/topic', [ 'forum/topic/events', 'forum/topic/browsing', 'forum/topic/posts', - 'navigator' -], function(pagination, infinitescroll, threadTools, postTools, events, browsing, posts, navigator) { + 'navigator', + 'sort' +], function(pagination, infinitescroll, threadTools, postTools, events, browsing, posts, navigator, sort) { var Topic = {}, currentUrl = ''; @@ -44,7 +45,7 @@ define('forum/topic', [ threadTools.init(tid, thread_state); events.init(); - handleSorting(); + sort.handleSort('topicPostSort', 'user.setTopicSort', 'topic/' + ajaxify.variables.get('topic_slug')); enableInfiniteLoadingOrPagination(); @@ -107,21 +108,6 @@ define('forum/topic', [ return parts[parts.length - 1] ? parseInt(parts[parts.length - 1], 10) : 0; } - 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 addBlockQuoteHandler() { $('#post-container').on('click', 'blockquote .toggle', function() { var blockQuote = $(this).parent('blockquote'); diff --git a/public/src/modules/sort.js b/public/src/modules/sort.js new file mode 100644 index 0000000000..8d1559d4da --- /dev/null +++ b/public/src/modules/sort.js @@ -0,0 +1,26 @@ +'use strict'; +/* globals define, translator, templates */ + +define('sort', function() { + var module = {}; + + module.handleSort = function (field, method, gotoOnSave) { + var threadSort = $('.thread-sort'); + threadSort.find('i').removeClass('fa-check'); + var currentSetting = threadSort.find('a[data-sort="' + config[field] + '"]'); + currentSetting.find('i').addClass('fa-check'); + + $('.thread-sort').on('click', 'a', function() { + var newSetting = $(this).attr('data-sort'); + socket.emit(method, newSetting, function(err) { + if (err) { + return app.alertError(err.message); + } + config[field] = newSetting; + ajaxify.go(gotoOnSave); + }); + }); + }; + + return module; +}); diff --git a/src/categories.js b/src/categories.js index c5078d711a..675eb4a80b 100644 --- a/src/categories.js +++ b/src/categories.js @@ -40,10 +40,11 @@ var async = require('async'), topics: function(next) { Categories.getCategoryTopics({ cid: data.cid, + set: data.set, + reverse: data.reverse, start: data.start, stop: data.end, - uid: data.uid, - targetUid: data.targetUid + uid: data.uid }, next); }, pageCount: function(next) { diff --git a/src/categories/delete.js b/src/categories/delete.js index 98c485ceed..d01dd13317 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -29,6 +29,7 @@ module.exports = function(Categories) { function(next) { db.deleteAll([ 'cid:' + cid + ':tids', + 'cid:' + cid + ':tids:posts', 'cid:' + cid + ':pids', 'category:' + cid ], next); diff --git a/src/categories/topics.js b/src/categories/topics.js index 392bed66c4..50dba67bbd 100644 --- a/src/categories/topics.js +++ b/src/categories/topics.js @@ -22,7 +22,7 @@ module.exports = function(Categories) { plugins.fireHook('filter:category.topics.prepare', data, next); }, function(data, next) { - Categories.getTopicIds(data.targetUid ? 'cid:' + data.cid + ':uid:' + data.targetUid + ':tids' : 'cid:' + data.cid + ':tids', data.start, data.stop, next); + Categories.getTopicIds(data.set, data.reverse, data.start, data.stop, next); }, function(tids, next) { topics.getTopicsByTids(tids, data.uid, next); @@ -56,8 +56,12 @@ module.exports = function(Categories) { }); }; - Categories.getTopicIds = function(set, start, stop, callback) { - db.getSortedSetRevRange(set, start, stop, callback); + Categories.getTopicIds = function(set, reverse, start, stop, callback) { + if (reverse) { + db.getSortedSetRevRange(set, start, stop, callback); + } else { + db.getSortedSetRange(set, start, stop, callback); + } }; Categories.getTopicIndex = function(tid, callback) { @@ -95,6 +99,9 @@ module.exports = function(Categories) { } else { db.sortedSetAdd('cid:' + cid + ':tids', postData.timestamp, postData.tid, next); } + }, + function(next) { + db.sortedSetIncrBy('cid:' + cid + ':tids:posts', 1, postData.tid, next); } ], callback); }); diff --git a/src/controllers/api.js b/src/controllers/api.js index 75e4d558dd..be4d8b2347 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -55,6 +55,7 @@ apiController.getConfig = function(req, res, next) { config['css-buster'] = meta.css.hash; config.requireEmailConfirmation = parseInt(meta.config.requireEmailConfirmation, 10) === 1; config.topicPostSort = meta.config.topicPostSort || 'oldest_to_newest'; + config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest'; config.csrf_token = req.csrfToken(); config.searchEnabled = plugins.hasListeners('filter:search.query'); @@ -79,6 +80,7 @@ apiController.getConfig = function(req, res, next) { config.userLang = settings.language || config.defaultLang; config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab; config.topicPostSort = settings.topicPostSort || config.topicPostSort; + config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort; config.topicSearchEnabled = settings.topicSearchEnabled || false; if (res.locals.isAPI) { diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 14abb6f260..75b80a17d0 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -158,11 +158,23 @@ categoriesController.get = function(req, res, next) { topicIndex = 0; } + var set = 'cid:' + cid + ':tids', + reverse = false; + + if (settings.categoryTopicSort === 'newest_to_oldest') { + reverse = true; + } else if (settings.categoryTopicSort === 'most_posts') { + reverse = true; + set = 'cid:' + cid + ':tids:posts'; + } + var start = (page - 1) * settings.topicsPerPage + topicIndex, end = start + settings.topicsPerPage - 1; next(null, { cid: cid, + set: set, + reverse: reverse, start: start, end: end, uid: uid @@ -171,6 +183,9 @@ categoriesController.get = function(req, res, next) { function(payload, next) { user.getUidByUserslug(req.query.author, function(err, uid) { payload.targetUid = uid; + if (uid) { + payload.set = 'cid:' + cid + ':uid:' + uid + ':tids'; + } next(err, payload); }); }, diff --git a/src/posts/delete.js b/src/posts/delete.js index 28db54101b..cc1a8c8b5b 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -171,8 +171,11 @@ module.exports = function(Posts) { topics.updateTeaser(postData.tid, next); }, function(next) { - user.incrementUserPostCountBy(postData.uid, -1, next); + db.sortedSetIncrBy('cid:' + topicData.cid + ':tids:posts', -1, postData.tid, next); }, + function(next) { + user.incrementUserPostCountBy(postData.uid, -1, next); + } ], callback); }); }); diff --git a/src/routes/feeds.js b/src/routes/feeds.js index 77571e1fe8..12b0f6a0e1 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -125,6 +125,8 @@ function generateForCategory(req, res, next) { var uid = req.user ? req.user.uid : 0; categories.getCategoryById({ cid: cid, + set: 'cid:' + cid + ':tids', + reverse: true, start: 0, end: 25, uid: uid diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index 0aeee0482a..9f20697ef9 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -19,7 +19,7 @@ SocketCategories.get = function(socket, data, callback) { }; SocketCategories.loadMore = function(socket, data, callback) { - if(!data) { + if (!data) { return callback(new Error('[[error:invalid-data]]')); } @@ -46,11 +46,28 @@ SocketCategories.loadMore = function(socket, data, callback) { return callback(new Error('[[error:no-privileges]]')); } + + var set = 'cid:' + data.cid + ':tids', + reverse = false; + + if (results.settings.categoryTopicSort === 'newest_to_oldest') { + reverse = true; + } else if (results.settings.categoryTopicSort === 'most_posts') { + reverse = true; + set = 'cid:' + data.cid + ':tids:posts'; + } + var start = parseInt(data.after, 10), end = start + results.settings.topicsPerPage - 1; + if (results.targetUid) { + set = 'cid:' + data.cid + ':uid:' + results.targetUid + ':tids'; + } + categories.getCategoryTopics({ cid: data.cid, + set: set, + reverse: reverse, start: start, stop: end, uid: socket.uid, diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index ccd063444f..5edda3f51e 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -392,7 +392,7 @@ SocketTopics.moveAll = function(socket, data, callback) { return callback(err || new Error('[[error:no-privileges]]')); } - categories.getTopicIds('cid:' + data.currentCid + ':tids', 0, -1, function(err, tids) { + categories.getTopicIds('cid:' + data.currentCid + ':tids', true, 0, -1, function(err, tids) { if (err) { return callback(err); } diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 392327c93f..aadd177e9a 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -298,6 +298,12 @@ SocketUser.setTopicSort = function(socket, sort, callback) { } }; +SocketUser.setCategorySort = function(socket, sort, callback) { + if (socket.uid) { + user.setSetting(socket.uid, 'categoryTopicSort', sort, callback); + } +}; + SocketUser.getOnlineAnonCount = function(socket, data, callback) { callback(null, module.parent.exports.getOnlineAnonCount()); }; diff --git a/src/threadTools.js b/src/threadTools.js index 09db8cd436..50cd3def95 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -185,15 +185,26 @@ var winston = require('winston'), var topic; async.waterfall([ function(next) { - topics.getTopicFields(tid, ['cid', 'lastposttime', 'pinned', 'deleted'], next); + topics.getTopicFields(tid, ['cid', 'lastposttime', 'pinned', 'deleted', 'postcount'], next); }, function(topicData, next) { topic = topicData; - db.sortedSetRemove('cid:' + topicData.cid + ':tids', tid, next); + db.sortedSetsRemove([ + 'cid:' + topicData.cid + ':tids', + 'cid:' + topicData.cid + ':tids:posts' + ], tid, next); }, function(next) { var timestamp = parseInt(topic.pinned, 10) ? Math.pow(2, 53) : topic.lastposttime; - db.sortedSetAdd('cid:' + cid + ':tids', timestamp, tid, next); + async.parallel([ + function(next) { + db.sortedSetAdd('cid:' + cid + ':tids', timestamp, tid, next); + }, + function(next) { + topic.postcount = topic.postcount || 0; + db.sortedSetAdd('cid:' + cid + ':tids:posts', topic.postcount, tid, next); + } + ], next); } ], function(err) { if (err) { diff --git a/src/topics/delete.js b/src/topics/delete.js index 629e411aec..eb4c6c43a0 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -76,6 +76,7 @@ module.exports = function(Topics) { db.sortedSetsRemove([ 'cid:' + topicData.cid + ':tids', + 'cid:' + topicData.cid + ':tids:posts', 'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids', 'uid:' + topicData.uid + ':topics' ], tid, callback); diff --git a/src/upgrade.js b/src/upgrade.js index d574995318..1564b5453c 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -21,7 +21,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2014, 11, 20); + latestSchema = Date.UTC(2015, 0, 8); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -451,8 +451,48 @@ Upgrade.upgrade = function(callback) { winston.info('[2014/12/20] Updating digest settings skipped'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2015, 0, 8); + if (schemaDate < thisSchemaDate) { + winston.info('[2015/01/08] Updating category topics sorted sets'); + + db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { + if (err) { + winston.error('[2014/12/20] Error encountered while updating digest settings'); + return next(err); + } + + var now = Date.now(); + + async.eachLimit(tids, 50, function(tid, next) { + db.getObjectFields('topic:' + tid, ['cid', 'postcount'], function(err, topicData) { + if (err) { + return next(err); + } + + if (Utils.isNumber(topicData.postcount) && topicData.cid) { + db.sortedSetAdd('cid:' + topicData.cid + ':tids:posts', topicData.postcount, tid, next); + } else { + next(); + } + }); + }, function(err) { + if (err) { + winston.error('[2015/01/08] Error encountered while Updating category topics sorted sets'); + return next(err); + } + winston.info('[2015/01/08] Updating category topics sorted sets done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2015/01/08] Updating category topics sorted sets skipped'); + next(); + } } + // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 22!!! ], function(err) { diff --git a/src/user/settings.js b/src/user/settings.js index 87b6b713ee..9ac714ea6e 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -65,6 +65,7 @@ module.exports = function(User) { settings.notificationSounds = parseInt(settings.notificationSounds, 10) === 1; settings.language = settings.language || meta.config.defaultLang || 'en_GB'; settings.topicPostSort = settings.topicPostSort || meta.config.topicPostSort || 'oldest_to_newest'; + settings.categoryTopicSort = settings.categoryTopicSort || meta.config.categoryTopicSort || 'newest_to_oldest'; settings.followTopicsOnCreate = (settings.followTopicsOnCreate === null || settings.followTopicsOnCreate === undefined) ? true : parseInt(settings.followTopicsOnCreate, 10) === 1; settings.followTopicsOnReply = parseInt(settings.followTopicsOnReply, 10) === 1; settings.sendChatNotifications = parseInt(settings.sendChatNotifications, 10) === 1; diff --git a/src/views/admin/settings/post.tpl b/src/views/admin/settings/post.tpl index 122b2cdf0c..15f65ef531 100644 --- a/src/views/admin/settings/post.tpl +++ b/src/views/admin/settings/post.tpl @@ -12,6 +12,14 @@ +
+ + +
diff --git a/tests/categories.js b/tests/categories.js index 95c7a0f14b..7115b88597 100644 --- a/tests/categories.js +++ b/tests/categories.js @@ -35,6 +35,8 @@ describe('Categories', function() { it('should retrieve a newly created category by its ID', function(done) { Categories.getCategoryById({ cid: categoryObj.cid, + set: 'cid:' + categoryObj.cid + ':tids', + reverse: true, start: 0, end: -1, uid: 0 @@ -52,6 +54,8 @@ describe('Categories', function() { it('should return a list of topics', function(done) { Categories.getCategoryTopics({ cid: categoryObj.cid, + set: 'cid:' + categoryObj.cid + ':tids', + reverse: true, start: 0, stop: 10, uid: 0 @@ -68,6 +72,8 @@ describe('Categories', function() { it('should return a list of topics by a specific user', function(done) { Categories.getCategoryTopics({ cid: categoryObj.cid, + set: 'cid:' + categoryObj.cid + ':uid:' + 1 + ':tids', + reverse: true, start: 0, stop: 10, uid: 0,