diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 211d6907ab..9ee1bbfefc 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -20,6 +20,7 @@ "profile_views": "Profile views", "reputation": "Reputation", "favourites":"Favourites", + "watched": "Watched", "followers": "Followers", "following": "Following", "signature": "Signature", @@ -68,6 +69,7 @@ "follows_no_one": "This user isn't following anyone :(", "has_no_posts": "This user didn't post anything yet.", "has_no_topics": "This user didn't post any topics yet.", + "has_no_watched_topics": "This user didn't watch any topics yet.", "email_hidden": "Email Hidden", "hidden": "hidden", diff --git a/public/src/client/account/followers.js b/public/src/client/account/followers.js index 6dfb3bf83c..71b3dd38da 100644 --- a/public/src/client/account/followers.js +++ b/public/src/client/account/followers.js @@ -3,16 +3,6 @@ define('forum/account/followers', ['forum/account/header'], function(header) { Followers.init = function() { header.init(); - - var yourid = ajaxify.variables.get('yourid'), - theirid = ajaxify.variables.get('theirid'), - followersCount = ajaxify.variables.get('followersCount'); - - - if (parseInt(followersCount, 10) === 0) { - $('#no-followers-notice').removeClass('hide'); - } - }; return Followers; diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index f552bf4056..f0a0c112fa 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -7,17 +7,10 @@ define('forum/account/header', function() { }; function displayAccountMenus() { - var yourid = ajaxify.variables.get('yourid'), - theirid = ajaxify.variables.get('theirid'); - - if (parseInt(yourid, 10) !== 0 && parseInt(yourid, 10) === parseInt(theirid, 10)) { - $('#editLink, #settingsLink, #favouritesLink').removeClass('hide'); - } else { - $('.account-sub-links .plugin-link').each(function() { - var $this = $(this); - $this.toggleClass('hide', $this.hasClass('private')); - }); - } + $('.account-sub-links .plugin-link').each(function() { + var $this = $(this); + $this.toggleClass('hide', $this.hasClass('private')); + }); } function selectActivePill() { diff --git a/public/src/client/account/watched.js b/public/src/client/account/watched.js new file mode 100644 index 0000000000..7a71699b50 --- /dev/null +++ b/public/src/client/account/watched.js @@ -0,0 +1,45 @@ +'use strict'; + +/* globals define, app, socket, utils */ +define('forum/account/watched', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) { + var AccountTopics = {}; + + AccountTopics.init = function() { + header.init(); + + infinitescroll.init(loadMore); + }; + + function loadMore(direction) { + console.log(direction); + if (direction < 0) { + return; + } + + infinitescroll.loadMore('topics.loadMoreFromSet', { + set: 'uid:' + $('.account-username-box').attr('data-uid') + ':followed_tids', + after: $('.user-topics').attr('data-nextstart') + }, function(data, done) { +console.log(data); + if (data.topics && data.topics.length) { + onTopicsLoaded(data.topics, done); + $('.user-topics').attr('data-nextstart', data.nextStart); + } else { + done(); + } + }); + } + + function onTopicsLoaded(topics, callback) { + infinitescroll.parseAndTranslate('account/watched', 'topics', {topics: topics}, function(html) { + $('#topics-container').append(html); + html.find('span.timeago').timeago(); + app.createUserTooltips(); + utils.makeNumbersHumanReadable(html.find('.human-readable-number')); + $(window).trigger('action:topics.loaded'); + callback(); + }); + } + + return AccountTopics; +}); diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index fd972ccd3b..2732c593dc 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -89,7 +89,7 @@ function getUserDataByUserSlug(userslug, callerUID, callback) { userData.yourid = callerUID; userData.theirid = userData.uid; userData.isSelf = self; - userData.showSettings = self || isAdmin; + userData.showHidden = self || isAdmin; userData.groups = Array.isArray(results.groups) && results.groups.length ? results.groups[0] : []; userData.disableSignatures = meta.config.disableSignatures !== undefined && parseInt(meta.config.disableSignatures, 10) === 1; userData['email:confirmed'] = !!parseInt(userData['email:confirmed'], 10); @@ -192,7 +192,7 @@ accountsController.getFollowers = function(req, res, next) { getFollow('account/followers', 'followers', req, res, next); }; -function getFollow(route, name, req, res, next) { +function getFollow(tpl, name, req, res, next) { var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; var userData; @@ -215,40 +215,28 @@ function getFollow(route, name, req, res, next) { userData[name] = users; userData[name + 'Count'] = users.length; - res.render(route, userData); + res.render(tpl, userData); }); } accountsController.getFavourites = function(req, res, next) { - var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; - - getBaseUser(req.params.userslug, callerUID, function(err, userData) { - if (err) { - return next(err); - } - - if (!userData) { - return helpers.notFound(req, res); - } - - if (parseInt(userData.uid, 10) !== callerUID) { - return helpers.notAllowed(req, res); - } - - posts.getPostsFromSet('uid:' + userData.uid + ':favourites', callerUID, 0, 9, function(err, favourites) { - if (err) { - return next(err); - } - - userData.posts = favourites.posts; - userData.nextStart = favourites.nextStart; - - res.render('account/favourites', userData); - }); - }); + getFromUserSet('account/favourites', 'favourites', posts.getPostsFromSet, 'posts', req, res, next); }; accountsController.getPosts = function(req, res, next) { + getFromUserSet('account/posts', 'posts', posts.getPostsFromSet, 'posts', req, res, next); +}; + +accountsController.getWatchedTopics = function(req, res, next) { + getFromUserSet('account/watched', 'followed_tids', topics.getTopicsFromSet, 'topics', req, res, next); +}; + +accountsController.getTopics = function(req, res, next) { + getFromUserSet('account/topics', 'topics', topics.getTopicsFromSet, 'topics', req, res, next); +}; + + +function getFromUserSet(tpl, set, method, type, req, res, next) { var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; getBaseUser(req.params.userslug, callerUID, function(err, userData) { @@ -259,44 +247,19 @@ accountsController.getPosts = function(req, res, next) { if (!userData) { return helpers.notFound(req, res); } - posts.getPostsFromSet('uid:' + userData.uid + ':posts', callerUID, 0, 19, function(err, userPosts) { + + method('uid:' + userData.uid + ':' + set, callerUID, 0, 19, function(err, data) { if (err) { return next(err); } - userData.posts = userPosts.posts; - userData.nextStart = userPosts.nextStart; + userData[type] = data[type]; + userData.nextStart = data.nextStart; - res.render('account/posts', userData); + res.render(tpl, userData); }); }); -}; - -accountsController.getTopics = function(req, res, next) { - var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; - - getBaseUser(req.params.userslug, callerUID, function(err, userData) { - if (err) { - return next(err); - } - - if (!userData) { - return helpers.notFound(req, res); - } - - var set = 'uid:' + userData.uid + ':topics'; - topics.getTopicsFromSet(set, callerUID, 0, 19, function(err, userTopics) { - if(err) { - return next(err); - } - - userData.topics = userTopics.topics; - userData.nextStart = userTopics.nextStart; - - res.render('account/topics', userData); - }); - }); -}; +} function getBaseUser(userslug, callerUID, callback) { user.getUidByUserslug(userslug, function (err, uid) { @@ -326,7 +289,7 @@ function getBaseUser(userslug, callerUID, callback) { results.user.yourid = callerUID; results.user.theirid = uid; results.user.isSelf = parseInt(callerUID, 10) === parseInt(uid, 10); - results.user.showSettings = results.user.isSelf || results.isAdmin; + results.user.showHidden = results.user.isSelf || results.isAdmin; results.user.profile_links = results.profile_links; callback(null, results.user); }); diff --git a/src/routes/index.js b/src/routes/index.js index eb35860928..c189404e5f 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -70,6 +70,7 @@ function accountRoutes(app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/topics', middleware, middlewares, controllers.accounts.getTopics); setupPageRoute(app, '/user/:userslug/favourites', middleware, accountMiddlewares, controllers.accounts.getFavourites); + setupPageRoute(app, '/user/:userslug/watched', middleware, accountMiddlewares, controllers.accounts.getWatchedTopics); setupPageRoute(app, '/user/:userslug/edit', middleware, accountMiddlewares, controllers.accounts.accountEdit); setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.accountSettings); diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 072b7d6514..08dab140e6 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -214,7 +214,7 @@ function addRedisAdapter(io) { } function callMethod(method, socket, params, callback) { - method(socket, params || {}, function(err, result) { + method(socket, params, function(err, result) { callback(err ? {message: err.message} : null, result); }); } diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 71a44f36bd..8fd517e9bd 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -83,6 +83,7 @@ SocketModules.composer.editCheck = function(socket, pid, callback) { }; SocketModules.composer.renderPreview = function(socket, content, callback) { + console.log(content); plugins.fireHook('filter:parse.raw', content, callback); }; diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 5edda3f51e..52d2c2cb01 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -172,7 +172,7 @@ SocketTopics.markAsUnreadForAll = function(socket, tids, callback) { async.each(tids, function(tid, next) { async.waterfall([ function(next) { - threadTools.exists(tid, next); + topics.exists(tid, next); }, function(exists, next) { if (!exists) { @@ -409,7 +409,7 @@ SocketTopics.follow = function(socket, tid, callback) { return callback(new Error('[[error:not-logged-in]]')); } - threadTools.toggleFollow(tid, socket.uid, callback); + topics.toggleFollow(tid, socket.uid, callback); }; SocketTopics.loadMore = function(socket, data, callback) { diff --git a/src/threadTools.js b/src/threadTools.js index 74b316f987..1fe1369d35 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -19,10 +19,6 @@ var winston = require('winston'), (function(ThreadTools) { - ThreadTools.exists = function(tid, callback) { - db.isSortedSetMember('topics:tid', tid, callback); - }; - ThreadTools.delete = function(tid, uid, callback) { toggleDelete(tid, uid, true, callback); }; @@ -75,7 +71,7 @@ var winston = require('winston'), ThreadTools.purge = function(tid, uid, callback) { async.waterfall([ function(next) { - ThreadTools.exists(tid, next); + topics.exists(tid, next); }, function(exists, next) { if (!exists) { @@ -229,39 +225,5 @@ var winston = require('winston'), }); }; - ThreadTools.toggleFollow = function(tid, uid, callback) { - callback = callback || function() {}; - async.waterfall([ - function (next) { - ThreadTools.exists(tid, next); - }, - function (exists, next) { - if (!exists) { - return next(new Error('[[error:no-topic]]')); - } - topics.isFollowing([tid], uid, next); - }, - function (isFollowing, next) { - db[isFollowing[0] ? 'setRemove' : 'setAdd']('tid:' + tid + ':followers', uid, function(err) { - next(err, !isFollowing[0]); - }); - } - ], callback); - }; - - ThreadTools.follow = function(tid, uid, callback) { - callback = callback || function() {}; - async.waterfall([ - function (next) { - ThreadTools.exists(tid, next); - }, - function (exists, next) { - if (!exists) { - return next(new Error('[[error:no-topic]]')); - } - db.setAdd('tid:' + tid + ':followers', uid, next); - } - ], callback); - }; }(exports)); diff --git a/src/topics.js b/src/topics.js index d7b1b04c7d..1e9df9958f 100644 --- a/src/topics.js +++ b/src/topics.js @@ -26,6 +26,10 @@ var async = require('async'), require('./topics/tags')(Topics); require('./topics/teaser')(Topics); + Topics.exists = function(tid, callback) { + db.isSortedSetMember('topics:tid', tid, callback); + }; + Topics.getTopicData = function(tid, callback) { db.getObject('topic:' + tid, function(err, topic) { if (err || !topic) { diff --git a/src/topics/create.js b/src/topics/create.js index 415c3b955a..b3038911c1 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -148,7 +148,7 @@ module.exports = function(Topics) { return next(err); } if (settings.followTopicsOnCreate) { - threadTools.follow(postData.tid, uid, next); + Topics.follow(postData.tid, uid, next); } else { next(); } @@ -193,7 +193,7 @@ module.exports = function(Topics) { function(next) { async.parallel({ exists: function(next) { - threadTools.exists(tid, next); + Topics.exists(tid, next); }, locked: function(next) { Topics.isLocked(tid, next); @@ -266,7 +266,7 @@ module.exports = function(Topics) { } if (results.settings.followTopicsOnReply) { - threadTools.follow(postData.tid, uid); + Topics.follow(postData.tid, uid); } postData.index = results.postIndex - 1; postData.favourited = false; diff --git a/src/topics/follow.js b/src/topics/follow.js index b79e513c07..00785ffd77 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -13,6 +13,68 @@ var async = require('async'), module.exports = function(Topics) { + Topics.toggleFollow = function(tid, uid, callback) { + callback = callback || function() {}; + var isFollowing; + async.waterfall([ + function (next) { + Topics.exists(tid, next); + }, + function (exists, next) { + if (!exists) { + return next(new Error('[[error:no-topic]]')); + } + Topics.isFollowing([tid], uid, next); + }, + function (_isFollowing, next) { + isFollowing = _isFollowing[0]; + if (isFollowing) { + Topics.unfollow(tid, uid, next); + } else { + Topics.follow(tid, uid, next); + } + }, + function(next) { + next(null, !isFollowing); + } + ], callback); + }; + + Topics.follow = function(tid, uid, callback) { + callback = callback || function() {}; + async.waterfall([ + function (next) { + Topics.exists(tid, next); + }, + function (exists, next) { + if (!exists) { + return next(new Error('[[error:no-topic]]')); + } + db.setAdd('tid:' + tid + ':followers', uid, next); + }, + function(next) { + db.sortedSetAdd('uid:' + uid + ':followed_tids', Date.now(), tid, next); + } + ], callback); + }; + + Topics.unfollow = function(tid, uid, callback) { + callback = callback || function() {}; + async.waterfall([ + function (next) { + Topics.exists(tid, next); + }, + function (exists, next) { + if (!exists) { + return next(new Error('[[error:no-topic]]')); + } + db.setRemove('tid:' + tid + ':followers', uid, next); + }, + function(next) { + db.sortedSetRemove('uid:' + uid + ':followed_tids', tid, next); + } + ], callback); + }; Topics.isFollowing = function(tids, uid, callback) { if (!Array.isArray(tids)) { @@ -23,7 +85,7 @@ module.exports = function(Topics) { } var keys = tids.map(function(tid) { return 'tid:' + tid + ':followers'; - }) + }); db.isMemberOfSets(keys, uid, callback); }; diff --git a/src/topics/fork.js b/src/topics/fork.js index 4eb1274b2a..4cd5e0c53e 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -76,7 +76,7 @@ module.exports = function(Topics) { var postData; async.waterfall([ function(next) { - threadTools.exists(tid, next); + Topics.exists(tid, next); }, function(exists, next) { if (!exists) { diff --git a/src/upgrade.js b/src/upgrade.js index f5e548e614..23194cddf9 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(2015, 0, 9); + latestSchema = Date.UTC(2015, 0, 13); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -525,8 +525,48 @@ Upgrade.upgrade = function(callback) { winston.info('[2015/01/09] Creating fullname:uid hash skipped'); next(); } - } + }, + function(next) { + thisSchemaDate = Date.UTC(2015, 0, 13); + if (schemaDate < thisSchemaDate) { + winston.info('[2015/01/13] Creating uid:followed_tids sorted set'); + db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { + if (err) { + winston.error('[2014/01/13] Error encountered while Creating uid:followed_tids sorted set'); + return next(err); + } + + var now = Date.now(); + + async.eachLimit(tids, 50, function(tid, next) { + db.getSetMembers('tid:' + tid + ':followers', function(err, uids) { + if (err) { + return next(err); + } + + async.eachLimit(uids, 50, function(uid, next) { + if (parseInt(uid, 10)) { + db.sortedSetAdd('uid:' + uid + ':followed_tids', now, tid, next); + } else { + next(); + } + }, next); + }); + }, function(err) { + if (err) { + winston.error('[2015/01/13] Error encountered while Creating uid:followed_tids sorted set'); + return next(err); + } + winston.info('[2015/01/13] Creating uid:followed_tids sorted set done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2015/01/13] Creating uid:followed_tids sorted set skipped'); + next(); + } + } // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 22!!! diff --git a/src/user/delete.js b/src/user/delete.js index ce7f70a30f..c115c727f1 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -76,7 +76,7 @@ module.exports = function(User) { function(next) { var keys = [ 'uid:' + uid + ':notifications:read', 'uid:' + uid + ':notifications:unread', - 'uid:' + uid + ':favourites', 'user:' + uid + ':settings', + 'uid:' + uid + ':favourites', 'uid:' + uid + ':followed_tids', 'user:' + uid + ':settings', 'uid:' + uid + ':topics', 'uid:' + uid + ':posts', 'uid:' + uid + ':chats', 'uid:' + uid + ':chats:unread', 'uid:' + uid + ':ip', 'uid:' + uid + ':upvote', 'uid:' + uid + ':downvote', diff --git a/src/views/config.json b/src/views/config.json index 9fc65e5781..70997cf4a8 100644 --- a/src/views/config.json +++ b/src/views/config.json @@ -11,6 +11,7 @@ "^user/.*/followers": "account/followers", "^user/.*/settings": "account/settings", "^user/.*/favourites": "account/favourites", + "^user/.*/watched": "account/watched", "^user/.*/posts": "account/posts", "^user/.*/topics": "account/topics", "^user/[^\/]+": "account/profile",