From 882b9a917ff6907e7feb9f1ba9fafdaba5219b71 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 6 Jun 2013 20:39:45 -0400 Subject: [PATCH] ability for users to now follow/subscribe to threads --- public/src/forum/topic.js | 81 ++++++++++++++++++++++++++++++++++---- public/templates/topic.tpl | 1 + src/postTools.js | 1 + src/posts.js | 3 ++ src/threadTools.js | 63 ++++++++++++++++++++++++++++- src/webserver.js | 6 +-- src/websockets.js | 18 +++++++++ 7 files changed, 160 insertions(+), 13 deletions(-) diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index aacaae19ab..0cba8ce384 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -155,6 +155,46 @@ if (postEls[x].getAttribute('data-deleted') === '1') toggle_post_delete_state(postEls[x].getAttribute('data-pid')); postEls[x].removeAttribute('data-deleted'); } + + // Follow Thread State + var followEl = $('.main-post .follow'), + set_follow_state = function(state, quiet) { + if (state && !followEl.hasClass('btn-success')) { + followEl.addClass('btn-success'); + followEl[0].title = 'You are currently receiving updates to this topic'; + if (!quiet) { + app.alert({ + alert_id: 'topic_follow', + timeout: 2500, + title: 'Following Topic', + message: 'You will now be receiving notifications when somebody posts to this topic.', + type: 'success' + }); + } + } else if (!state && followEl.hasClass('btn-success')) { + followEl.removeClass('btn-success'); + followEl[0].title = 'Be notified of new replies in this topic'; + if (!quiet) { + app.alert({ + alert_id: 'topic_follow', + timeout: 2500, + title: 'Not Following Topic', + message: 'You will no longer receive notifications from this topic.', + type: 'success' + }); + } + } + }; + socket.on('api:topic.followCheck', function(state) { + set_follow_state(state, true); + }); + socket.on('api:topic.follow', function(data) { + set_follow_state(data.follow); + }); + socket.emit('api:topic.followCheck', tid); + followEl[0].addEventListener('click', function() { + socket.emit('api:topic.follow', tid); + }, false); }); @@ -589,21 +629,46 @@ } function toggle_post_delete_state(pid) { - var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')); + var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')); + + if (postEl[0]) { quoteEl = $(postEl[0].querySelector('.quote')), favEl = $(postEl[0].querySelector('.favourite')), replyEl = $(postEl[0].querySelector('.post_reply')); - if (!postEl.hasClass('deleted')) { - quoteEl.addClass('none'); - favEl.addClass('none'); - replyEl.addClass('none'); - } else { + socket.once('api:post.privileges', function(privileges) { + if (privileges.editable) { + if (!postEl.hasClass('deleted')) { + toggle_post_tools(pid, false); + } else { + toggle_post_tools(pid, true); + } + } + + if (privileges.view_deleted) { + postEl.toggleClass('deleted'); + } else { + postEl.toggleClass('none'); + } + }); + socket.emit('api:post.privileges', pid); + } + } + + function toggle_post_tools(pid, state) { + var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')), + quoteEl = $(postEl[0].querySelector('.quote')), + favEl = $(postEl[0].querySelector('.favourite')), + replyEl = $(postEl[0].querySelector('.post_reply')); + + if (state) { quoteEl.removeClass('none'); favEl.removeClass('none'); replyEl.removeClass('none'); + } else { + quoteEl.addClass('none'); + favEl.addClass('none'); + replyEl.addClass('none'); } - - postEl.toggleClass('deleted'); } })(); \ No newline at end of file diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl index 695366763e..7ad709958d 100644 --- a/public/templates/topic.tpl +++ b/public/templates/topic.tpl @@ -31,6 +31,7 @@
{main_posts.username} + diff --git a/src/postTools.js b/src/postTools.js index c8a975aad4..ac1499aa8e 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -1,5 +1,6 @@ var RDB = require('./redis.js'), posts = require('./posts.js'), + topics = require('./topics'), threadTools = require('./threadTools.js'), user = require('./user.js'), async = require('async'), diff --git a/src/posts.js b/src/posts.js index 0920d045af..206ea03fd1 100644 --- a/src/posts.js +++ b/src/posts.js @@ -199,6 +199,9 @@ marked.setOptions({ timeout: 2000 }); + // Send notifications to users who are following this topic + threadTools.notify_followers(tid, uid); + user.getUserFields(uid, ['username','reputation','picture','signature'], function(data) { var timestamp = new Date().getTime(); diff --git a/src/threadTools.js b/src/threadTools.js index 391aba631b..77b77aded5 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -2,8 +2,8 @@ var RDB = require('./redis.js'), topics = require('./topics.js'), categories = require('./categories.js'), user = require('./user.js'), - async = require('async'); - + async = require('async'), + notifications = require('./notifications.js'); (function(ThreadTools) { @@ -160,4 +160,63 @@ var RDB = require('./redis.js'), }); } + ThreadTools.isFollowing = function(tid, current_user, callback) { + RDB.sismember('tid:' + tid + ':followers', current_user, function(err, following) { + callback(following); + }); + } + + ThreadTools.toggleFollow = function(tid, current_user, callback) { + ThreadTools.isFollowing(tid, current_user, function(following) { + if (!following) { + RDB.sadd('tid:' + tid + ':followers', current_user, function(err, success) { + if (!err) { + callback({ + status: 'ok', + follow: true + }); + } else callback({ status: 'error' }); + }); + } else { + RDB.srem('tid:' + tid + ':followers', current_user, function(err, success) { + if (!err) { + callback({ + status: 'ok', + follow: false + }); + } else callback({ status: 'error' }); + }); + } + }); + } + + ThreadTools.get_followers = function(tid, callback) { + RDB.smembers('tid:' + tid + ':followers', function(err, followers) { + callback(err, followers); + }); + } + + ThreadTools.notify_followers = function(tid, exceptUid) { + async.parallel([ + function(next) { + topics.get_topic(tid, 0, function(threadData) { + // console.log(threadData); + notifications.create(threadData.teaser_username + ' has posted a reply to: "' + threadData.title + '"', null, '/topic/' + tid, 'topic:' + tid, function(nid) { + next(null, nid); + }); + }); + }, + function(next) { + ThreadTools.get_followers(tid, function(err, followers) { + if (followers.indexOf(exceptUid) !== -1) followers.splice(followers.indexOf(exceptUid), 1); + next(null, followers); + }); + } + ], function(err, results) { + if (!err) { + notifications.push(results[0], results[1]); + } + }); + } + }(exports)); \ No newline at end of file diff --git a/src/webserver.js b/src/webserver.js index e97779c534..68d8126acb 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -264,9 +264,9 @@ var express = require('express'), app.get('/api/:method/:id*', api_method); app.get('/test', function(req, res) { - meta.config.get(function(config) { - res.send(JSON.stringify(config, null, 4)); - }); + var ThreadTools = require('./threadTools.js'); + ThreadTools.notify_followers(3); + res.send(); }); diff --git a/src/websockets.js b/src/websockets.js index e59784f518..5c222d8787 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -364,6 +364,24 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }), }) }) }); + + socket.on('api:post.privileges', function(pid) { + postTools.privileges(pid, uid, function(privileges) { + socket.emit('api:post.privileges', privileges); + }); + }); + + socket.on('api:topic.followCheck', function(tid) { + threadTools.isFollowing(tid, uid, function(following) { + socket.emit('api:topic.followCheck', following); + }); + }); + + socket.on('api:topic.follow', function(tid) { + threadTools.toggleFollow(tid, uid, function(follow) { + if (follow.status === 'ok') socket.emit('api:topic.follow', follow); + }); + }); }); }(SocketIO));