diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index 1fe343d929..9de817550c 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -183,7 +183,7 @@ if (chat.modalExists(data.fromuid)) { modal = chat.getModal(data.fromuid); chat.appendChatMessage(modal, data.message, data.timestamp); - + if (modal.is(":visible")) { chat.load(modal.attr('UUID')); } else { @@ -196,6 +196,24 @@ }); }); + function updateUnreadCount(count) { + var badge = $('#numUnreadBadge'); + badge.html(count > 20 ? '20+' : count); + + if (count > 0) { + badge + .removeClass('badge-inverse') + .addClass('badge-important'); + } else { + badge + .removeClass('badge-important') + .addClass('badge-inverse'); + } + } + + socket.on('event:unread.updateCount', updateUnreadCount); + socket.emit('api:unread.count', updateUnreadCount); + require(['mobileMenu'], function(mobileMenu) { mobileMenu.init(); }); diff --git a/public/src/utils.js b/public/src/utils.js index 3e890b3f73..a7afef8ef7 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -184,21 +184,6 @@ notificationIcon.className = 'fa fa-circle active'; } }); - - jQuery.getJSON(RELATIVE_PATH + '/api/unread/total', function(data) { - var badge = jQuery('#numUnreadBadge'); - badge.html(data.count > 20 ? '20+' : data.count); - - if (data.count > 0) { - badge - .removeClass('badge-inverse') - .addClass('badge-important'); - } else { - badge - .removeClass('badge-important') - .addClass('badge-inverse'); - } - }); }, isRelativeUrl: function(url) { diff --git a/src/feed.js b/src/feed.js index d270e4b3cc..57818a6cca 100644 --- a/src/feed.js +++ b/src/feed.js @@ -28,7 +28,7 @@ } Feed.updateTopic = function (tid, callback) { - topics.getTopicWithPosts(tid, 0, 0, -1, function (err, topicData) { + topics.getTopicWithPosts(tid, 0, 0, -1, true, function (err, topicData) { if (err) { if(callback) { return callback(new Error('topic-invalid')); diff --git a/src/posts.js b/src/posts.js index f6375d2401..255707902a 100644 --- a/src/posts.js +++ b/src/posts.js @@ -147,6 +147,9 @@ var db = require('./database'), next(); }); }, + function(next) { + topics.pushUnreadCount(null, next); + }, function(next) { Posts.getCidByPid(postData.pid, function(err, cid) { if(err) { diff --git a/src/routes/api.js b/src/routes/api.js index 3510f5bdec..b92a29e1f1 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -115,7 +115,7 @@ var path = require('path'), app.get('/topic/:id/:slug?', function (req, res, next) { var uid = (req.user) ? req.user.uid : 0; - topics.getTopicWithPosts(req.params.id, uid, 0, 10, function (err, data) { + topics.getTopicWithPosts(req.params.id, uid, 0, 10, false, function (err, data) { if (!err) { if (parseInt(data.deleted, 10) === 1 && parseInt(data.expose_tools, 10) === 0) { return res.json(404, {}); diff --git a/src/routes/debug.js b/src/routes/debug.js index 3f251d63ae..dd1f147229 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -79,7 +79,6 @@ var DebugRoute = function(app) { }); }); - app.get('/mongo', function(req, res) { var db = require('./../database'); @@ -390,6 +389,12 @@ var DebugRoute = function(app) { } }); }); + + app.get('/test', function(req, res) { + topics.pushUnreadCount(); + res.send(); + }); + }); }; diff --git a/src/topics.js b/src/topics.js index fc80cc965b..93af6cda31 100644 --- a/src/topics.js +++ b/src/topics.js @@ -17,7 +17,9 @@ var async = require('async'), notifications = require('./notifications'), feed = require('./feed'), favourites = require('./favourites'), - meta = require('./meta'); + meta = require('./meta') + + websockets = require('./websockets'); (function(Topics) { @@ -91,7 +93,6 @@ var async = require('async'), Topics.markAsRead(tid, uid); }); - // in future it may be possible to add topics to several categories, so leaving the door open here. db.sortedSetAdd('categories:' + cid + ':tid', timestamp, tid); db.incrObjectField('category:' + cid, 'topic_count'); @@ -109,6 +110,8 @@ var async = require('async'), // Auto-subscribe the post creator to the newly created topic threadTools.toggleFollow(tid, uid); + Topics.pushUnreadCount(); + Topics.getTopicForCategoryView(tid, uid, function(topicData) { topicData.unreplied = 1; @@ -303,8 +306,52 @@ var async = require('async'), ); }; - Topics.getUnreadTopics = function(uid, start, stop, callback) { + Topics.getUnreadTids = function(uid, start, stop, callback) { + var unreadTids = [], + done = false; + function continueCondition() { + return unreadTids.length < 20 && !done; + } + + async.whilst(continueCondition, function(callback) { + RDB.zrevrange('topics:recent', start, stop, function(err, tids) { + if (err) { + return callback(err); + } + + if (tids && !tids.length) { + done = true; + return callback(null); + } + + if (uid === 0) { + unreadTids.push.apply(unreadTids, tids); + callback(null); + } else { + Topics.hasReadTopics(tids, uid, function(read) { + + var newtids = tids.filter(function(tid, index, self) { + return parseInt(read[index], 10) === 0; + }); + + unreadTids.push.apply(unreadTids, newtids); + + if(continueCondition()) { + start = stop + 1; + stop = start + 19; + } + + callback(null); + }); + } + }); + }, function(err) { + callback(err, unreadTids); + }); + }; + + Topics.getUnreadTopics = function(uid, start, stop, callback) { var unreadTopics = { 'category_name': 'Unread', 'show_sidebar': 'hidden', @@ -325,66 +372,54 @@ var async = require('async'), Topics.getTopicsByTids(topicIds, uid, function(topicData) { unreadTopics.topics = topicData; unreadTopics.nextStart = start + topicIds.length; - if (!topicData || topicData.length === 0) + if (!topicData || topicData.length === 0) { unreadTopics.no_topics_message = 'show'; - if (uid === 0 || topicData.length === 0) + } + if (uid === 0 || topicData.length === 0) { unreadTopics.show_markallread_button = 'hidden'; + } + callback(unreadTopics); }); } - var unreadTids = [], - done = false; - - function continueCondition() { - return unreadTids.length < 20 && !done; - } - - async.whilst( - continueCondition, - function(callback) { - db.getSortedSetRevRange('topics:recent', start, stop, function(err, tids) { - if (err) - return callback(err); - - if (tids && !tids.length) { - done = true; - return callback(null); - } - - if (uid === 0) { - unreadTids.push.apply(unreadTids, tids); - callback(null); - } else { - Topics.hasReadTopics(tids, uid, function(read) { - - var newtids = tids.filter(function(tid, index, self) { - return parseInt(read[index], 10) === 0; - }); + Topics.getUnreadTids(uid, start, stop, function(err, unreadTids) { + if (err) { + return callback([]); + } - unreadTids.push.apply(unreadTids, newtids); + if (unreadTids.length) { + sendUnreadTopics(unreadTids); + } else { + noUnreadTopics(); + } + }); + }; - if(continueCondition()) { - start = stop + 1; - stop = start + 19; - } + Topics.pushUnreadCount = function(uids, callback) { + if (uids == 0) throw new Error(); + if (!uids) { + clients = websockets.getConnectedClients(); + uids = Object.keys(clients); + } else if (!Array.isArray(uids)) { + uids = [uids]; + } - callback(null); - }); - } - }); - }, - function(err) { - if (err) - return callback([]); - if (unreadTids.length) - sendUnreadTopics(unreadTids); - else - noUnreadTopics(); + async.each(uids, function(uid, next) { + Topics.getUnreadTids(uid, 0, 19, function(err, tids) { + websockets.in('uid_' + uid).emit('event:unread.updateCount', tids.length); + next(); + }); + }, function(err) { + if (err) { + winston.error(err.message); + } + if (callback) { + callback(); } - ); - } + }); + }; Topics.getTopicsByTids = function(tids, current_user, callback, category_id) { @@ -489,14 +524,18 @@ var async = require('async'), } - Topics.getTopicWithPosts = function(tid, current_user, start, end, callback) { + Topics.getTopicWithPosts = function(tid, current_user, start, end, quiet, callback) { threadTools.exists(tid, function(exists) { if (!exists) { return callback(new Error('Topic tid \'' + tid + '\' not found')); } - Topics.markAsRead(tid, current_user); - Topics.increaseViewCount(tid); + // "quiet" is used for things like RSS feed updating, HTML parsing for non-js users, etc + if (!quiet) { + Topics.markAsRead(tid, current_user); + Topics.pushUnreadCount(current_user); + Topics.increaseViewCount(tid); + } function getTopicData(next) { Topics.getTopicData(tid, next); diff --git a/src/webserver.js b/src/webserver.js index 0c224f6569..eac6204901 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -463,7 +463,7 @@ var path = require('path'), async.waterfall([ function (next) { - topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), 0, -1, function (err, topicData) { + topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), 0, -1, true, function (err, topicData) { if (topicData) { if (parseInt(topicData.deleted, 10) === 1 && parseInt(topicData.expose_tools, 10) === 0) { return next(new Error('Topic deleted'), null); diff --git a/src/websockets.js b/src/websockets.js index 43d9840de0..2eec708119 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -61,7 +61,6 @@ websockets.init = function(io) { var hs = socket.handshake, sessionID, uid, lastPostTime = 0; - // Validate the session, if present socketCookieParser(hs, {}, function(err) { sessionID = socket.handshake.signedCookies["express.sid"]; @@ -104,8 +103,6 @@ websockets.init = function(io) { }); }); - - socket.on('disconnect', function() { var index = userSockets[uid].indexOf(socket); @@ -868,6 +865,12 @@ websockets.init = function(io) { }); }); + socket.on('api:unread.count', function(callback) { + topics.getUnreadTids(uid, 0, 19, function(err, tids) { + socket.emit('event:unread.updateCount', tids.length); + }); + }); + socket.on('api:category.loadMore', function(data, callback) { var start = data.after, end = start + 9; @@ -1137,6 +1140,10 @@ websockets.init = function(io) { websockets.in = function(room) { return io.sockets.in(room); }; + + websockets.getConnectedClients = function() { + return userSockets; + } } })(module.exports);