From f6583aad5e21d8541b70da7cadf59d9aabf1c536 Mon Sep 17 00:00:00 2001 From: Delgermurun Date: Sun, 21 Dec 2014 16:02:36 +0800 Subject: [PATCH] subscribe post --- lib/publications.js | 3 +- .../telescope-notifications/i18n/en.i18n.json | 7 +- .../lib/client/templates/post_subscribe.html | 15 ++ .../lib/client/templates/post_subscribe.js | 46 ++++++ .../templates/user_subscribed_posts.html | 26 ++++ .../client/templates/user_subscribed_posts.js | 52 +++++++ .../telescope-notifications/lib/herald.js | 2 +- .../lib/notifications.js | 38 +++-- .../lib/server/notifications-server.js | 12 +- .../lib/server/publications.js | 5 + .../telescope-notifications/lib/subscribe.js | 135 ++++++++++++++++++ packages/telescope-notifications/package.js | 6 + 12 files changed, 326 insertions(+), 21 deletions(-) create mode 100644 packages/telescope-notifications/lib/client/templates/post_subscribe.html create mode 100644 packages/telescope-notifications/lib/client/templates/post_subscribe.js create mode 100644 packages/telescope-notifications/lib/client/templates/user_subscribed_posts.html create mode 100644 packages/telescope-notifications/lib/client/templates/user_subscribed_posts.js create mode 100644 packages/telescope-notifications/lib/server/publications.js create mode 100644 packages/telescope-notifications/lib/subscribe.js diff --git a/lib/publications.js b/lib/publications.js index a4cb5e03a..cbbe7c7b6 100644 --- a/lib/publications.js +++ b/lib/publications.js @@ -22,6 +22,7 @@ privacyOptions = { // true means exposed 'votes.downvotedPosts': true, 'votes.upvotedComments': true, 'votes.upvotedPosts': true, + 'subscribedItems.Posts': true }; // minimum required properties to display avatars @@ -37,4 +38,4 @@ avatarOptions = { 'services.facebook.id': true, 'services.twitter.screenName': true, 'services.github.screenName': true, // Github is not really used, but there are some mentions to it in the code -} \ No newline at end of file +} diff --git a/packages/telescope-notifications/i18n/en.i18n.json b/packages/telescope-notifications/i18n/en.i18n.json index bd3294d5a..d14c41cde 100644 --- a/packages/telescope-notifications/i18n/en.i18n.json +++ b/packages/telescope-notifications/i18n/en.i18n.json @@ -8,5 +8,8 @@ "1_notification": "1 notification", "notifications": "notifications", "notifications_fieldset": "Notifications", - "emailNotifications": "Email Notifications" -} \ No newline at end of file + "emailNotifications": "Email Notifications", + "subscribed_posts": "Subscribed Posts", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe" +} diff --git a/packages/telescope-notifications/lib/client/templates/post_subscribe.html b/packages/telescope-notifications/lib/client/templates/post_subscribe.html new file mode 100644 index 000000000..5749c3665 --- /dev/null +++ b/packages/telescope-notifications/lib/client/templates/post_subscribe.html @@ -0,0 +1,15 @@ + diff --git a/packages/telescope-notifications/lib/client/templates/post_subscribe.js b/packages/telescope-notifications/lib/client/templates/post_subscribe.js new file mode 100644 index 000000000..5c5e8462c --- /dev/null +++ b/packages/telescope-notifications/lib/client/templates/post_subscribe.js @@ -0,0 +1,46 @@ +Template[getTemplate('postSubcribe')].helpers({ + canSubscribe: function() { + return this.userId !== Meteor.userId(); + }, + subscribed: function() { + var user = Meteor.user(); + if (!user) return false; + + return _.include(this.subscribers, user._id); + } +}); + +Template[getTemplate('postSubcribe')].events({ + 'click .subscribe-link': function(e, instance) { + e.preventDefault(); + if (this.userId === Meteor.userId()) + return; + + var post = this; + + if (!Meteor.user()) { + Router.go('atSignIn'); + flashMessage(i18n.t("please_log_in_first"), "info"); + } + + Meteor.call('subscribePost', post._id, function(error, result) { + if (result) + trackEvent("post subscribed", {'_id': post._id}); + }); + }, + + 'click .unsubscribe-link': function(e, instance) { + e.preventDefault(); + var post = this; + + if (!Meteor.user()) { + Router.go('atSignIn'); + flashMessage(i18n.t("please_log_in_first"), "info"); + } + + Meteor.call('unsubscribePost', post._id, function(error, result) { + if (result) + trackEvent("post unsubscribed", {'_id': post._id}); + }); + } +}); diff --git a/packages/telescope-notifications/lib/client/templates/user_subscribed_posts.html b/packages/telescope-notifications/lib/client/templates/user_subscribed_posts.html new file mode 100644 index 000000000..a26e92a81 --- /dev/null +++ b/packages/telescope-notifications/lib/client/templates/user_subscribed_posts.html @@ -0,0 +1,26 @@ + diff --git a/packages/telescope-notifications/lib/client/templates/user_subscribed_posts.js b/packages/telescope-notifications/lib/client/templates/user_subscribed_posts.js new file mode 100644 index 000000000..b7989f218 --- /dev/null +++ b/packages/telescope-notifications/lib/client/templates/user_subscribed_posts.js @@ -0,0 +1,52 @@ +Template[getTemplate('userSubscribedPosts')].created = function () { + var user = this.data, + instance = this; + + // initialize the terms and posts local reactive variables + instance.terms = new ReactiveVar({ + view: 'userSubscribedPosts', + userId: user._id, + limit: 5 + }); + instance.posts = new ReactiveVar({}); + + // will re-run when the "terms" local reactive variable changes + this.autorun(function () { + + // get the new terms and generate new parameters from them + var terms = instance.terms.get(); + var parameters = getPostsParameters(terms); + + // subscribe to the userPosts publication + instance.subscription = Meteor.subscribe('userSubscribedPosts', terms); + + // update the instance's "posts" cursor + instance.posts.set(Posts.find(parameters.find, parameters.options)); + + }); +}; + +Template[getTemplate('userSubscribedPosts')].helpers({ + posts: function () { + var user = this, + posts = Template.instance().posts.get().fetch(); + posts = _.map(posts, function (post) { + var item = _.findWhere(user.subscribedItems.Posts, {itemId: post._id}); + post.subscribedAt = item.subscribedAt; + return post; + }); + return posts; + }, + hasMorePosts: function () { + return Template.instance().posts.get().count() >= Template.instance().terms.get().limit; + } +}); + +Template[getTemplate('userSubscribedPosts')].events({ + 'click .subscribedposts-more': function (e) { + e.preventDefault(); + var terms = Template.instance().terms.get(); + terms.limit += 5; + Template.instance().terms.set(terms) + } +}); diff --git a/packages/telescope-notifications/lib/herald.js b/packages/telescope-notifications/lib/herald.js index b2e998dca..e13f047ce 100644 --- a/packages/telescope-notifications/lib/herald.js +++ b/packages/telescope-notifications/lib/herald.js @@ -17,7 +17,7 @@ var commentEmail = function (userToNotify) { var notification = this; // put in setTimeout so it doesn't hold up the rest of the method Meteor.setTimeout(function () { - notificationEmail = buildEmailNotification(notification); + notificationEmail = buildEmailNotification(notification, userToNotify); sendEmail(getEmail(userToNotify), notificationEmail.subject, notificationEmail.html); }, 1); } diff --git a/packages/telescope-notifications/lib/notifications.js b/packages/telescope-notifications/lib/notifications.js index 460cd78cb..2828518d7 100644 --- a/packages/telescope-notifications/lib/notifications.js +++ b/packages/telescope-notifications/lib/notifications.js @@ -13,20 +13,31 @@ postAfterSubmitMethodCallbacks.push(function (post) { commentAfterSubmitMethodCallbacks.push(function (comment) { if(Meteor.isServer){ - var parentCommentId = comment.parentCommentId; - var user = Meteor.user(); - var post = Posts.findOne(comment.postId); - var postUser = Meteor.users.findOne(post.userId); + var parentCommentId = comment.parentCommentId, + user = Meteor.user(), + post = Posts.findOne(comment.postId), + postUser = Meteor.users.findOne(post.userId), + userIdsToNotify = post.subscribers && _.clone(post.subscribers) || []; var notificationData = { comment: _.pick(comment, '_id', 'userId', 'author', 'body'), - post: _.pick(post, '_id', 'title', 'url') + post: _.pick(post, '_id', 'userId', 'title', 'url') }; - if(parentCommentId){ + var i = userIdsToNotify.indexOf(user._id); + if (i != -1) + // do not notify author of comment + userIdsToNotify.splice(i, 1); + + i = userIdsToNotify.indexOf(postUser._id); + if (i != -1) + // remove author of post, decide whether to nofity or not below + userIdsToNotify.splice(i, 1); + + if (parentCommentId) { // child comment - var parentComment = Comments.findOne(parentCommentId); - var parentUser = Meteor.users.findOne(parentComment.userId); + var parentComment = Comments.findOne(parentCommentId), + parentUser = Meteor.users.findOne(parentComment.userId); notificationData.parentComment = _.pick(parentComment, '_id', 'userId', 'author'); @@ -38,14 +49,17 @@ commentAfterSubmitMethodCallbacks.push(function (comment) { // comment notification // if the original poster is different from the author of the parent comment, notify them too if(postUser._id != user._id && parentComment.userId != post.userId) - Herald.createNotification(postUser._id, {courier: 'newComment', data: notificationData}) + userIdsToNotify.push(postUser._id); - }else{ + } else { // root comment // don't notify users of their own comments if(postUser._id != user._id) - Herald.createNotification(postUser._id, {courier: 'newComment', data: notificationData}) + userIdsToNotify.push(postUser._id); } + + if (userIdsToNotify.length > 0) + Herald.createNotification(userIdsToNotify, {courier: 'newComment', data: notificationData}); } return comment; @@ -76,4 +90,4 @@ function setNotificationDefaults (user) { }; return user; } -userCreatedCallbacks.push(setNotificationDefaults); \ No newline at end of file +userCreatedCallbacks.push(setNotificationDefaults); diff --git a/packages/telescope-notifications/lib/server/notifications-server.js b/packages/telescope-notifications/lib/server/notifications-server.js index 3ad11cbfb..1077e0d3c 100644 --- a/packages/telescope-notifications/lib/server/notifications-server.js +++ b/packages/telescope-notifications/lib/server/notifications-server.js @@ -3,11 +3,13 @@ getUnsubscribeLink = function(user){ }; // given a notification, return the correct subject and html to send an email -buildEmailNotification = function (notification) { +buildEmailNotification = function (notification, user) { - var subject, template; - var post = notification.data.post; - var comment = notification.data.comment; + var subject, + template, + post = notification.data.post, + comment = notification.data.comment, + subscribeText = post.userId === user._id ? '' : 'subscribed '; switch(notification.courier){ case 'newReply': @@ -16,7 +18,7 @@ buildEmailNotification = function (notification) { break; case 'newComment': - subject = 'A new comment on your post "'+post.title+'"'; + subject = 'A new comment on your ' + subscribeText + 'post "' + post.title + '"'; template = 'emailNewComment'; break; diff --git a/packages/telescope-notifications/lib/server/publications.js b/packages/telescope-notifications/lib/server/publications.js new file mode 100644 index 000000000..c9c48990c --- /dev/null +++ b/packages/telescope-notifications/lib/server/publications.js @@ -0,0 +1,5 @@ +Meteor.publish('userSubscribedPosts', function(terms) { + var parameters = getPostsParameters(terms); + var posts = Posts.find(parameters.find, parameters.options); + return posts; +}); diff --git a/packages/telescope-notifications/lib/subscribe.js b/packages/telescope-notifications/lib/subscribe.js new file mode 100644 index 000000000..6f6a5f7d5 --- /dev/null +++ b/packages/telescope-notifications/lib/subscribe.js @@ -0,0 +1,135 @@ +postMeta.push( + { + template: 'postSubcribe', + order: 40 + } +); + +addToPostSchema.push( + { + propertyName: 'subscribers', + propertySchema: { + type: [String], + optional: true, + autoform: { + omit: true + } + } + } +); + +addToPostSchema.push( + { + propertyName: 'subscriberCount', + propertySchema: { + type: Number, + optional: true, + autoform: { + omit: true + } + } + } +); + +userProfileDisplay.push( + { + template: 'userSubscribedPosts', + order: 5 + } +); + +viewParameters.userSubscribedPosts = function (terms) { + var user = Meteor.users.findOne(terms.userId), + postsIds = []; + + if (user.subscribedItems && user.subscribedItems.Posts) + postsIds = _.pluck(user.subscribedItems.Posts, "itemId"); + + return { + find: {_id: {$in: postsIds}}, + options: {limit: 5, sort: {postedAt: -1}} + }; +} + +var hasSubscribedItem = function (item, user) { + return item.subscribers && item.subscribers.indexOf(user._id) != -1; +}; + +var addSubscribedItem = function (userId, item, collection) { + var field = 'subscribedItems.' + collection; + var add = {}; + add[field] = item; + Meteor.users.update({_id: userId}, { + $addToSet: add + }); +}; + +var removeSubscribedItem = function (userId, itemId, collection) { + var field = 'subscribedItems.' + collection; + var remove = {}; + remove[field] = {itemId: itemId}; + Meteor.users.update({_id: userId}, { + $pull: remove + }); +}; + +var subscribeItem = function (collection, itemId) { + var user = Meteor.user(), + item = collection.findOne(itemId), + collectionName = collection._name.slice(0,1).toUpperCase() + collection._name.slice(1); + + if (!user || !item || hasSubscribedItem(item, user)) + return false; + + // author can't subscribe item + if (item.userId && item.userId === user._id) + return false + + // Subscribe + var result = collection.update({_id: itemId, subscribers: { $ne: user._id }}, { + $addToSet: {subscribers: user._id}, + $inc: {subscriberCount: 1} + }); + + if (result > 0) { + // Add item to list of subscribed items + var obj = { + itemId: item._id, + subscribedAt: new Date() + }; + addSubscribedItem(user._id, obj, collectionName); + } + + return true; +}; + +var unsubscribeItem = function (collection, itemId) { + var user = Meteor.user(), + item = collection.findOne(itemId), + collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1); + + if (!user || !item || !hasSubscribedItem(item, user)) + return false; + + // Unsubscribe + var result = collection.update({_id: itemId, subscribers: user._id }, { + $pull: {subscribers: user._id}, + $inc: {subscriberCount: -1} + }); + + if (result > 0) { + // Remove item from list of subscribed items + removeSubscribedItem(user._id, itemId, collectionName); + } + return true; +}; + +Meteor.methods({ + subscribePost: function(postId) { + return subscribeItem.call(this, Posts, postId); + }, + + unsubscribePost: function(postId) { + return unsubscribeItem.call(this, Posts, postId); + } +}); diff --git a/packages/telescope-notifications/package.js b/packages/telescope-notifications/package.js index 053185394..148bd336b 100644 --- a/packages/telescope-notifications/package.js +++ b/packages/telescope-notifications/package.js @@ -30,6 +30,7 @@ Package.onUse(function (api) { api.add_files([ 'lib/notifications.js', 'lib/herald.js', + 'lib/subscribe.js', 'package-tap.i18n' ], ['client', 'server']); @@ -42,10 +43,15 @@ Package.onUse(function (api) { 'lib/client/templates/notifications_menu.js', 'lib/client/templates/unsubscribe.html', 'lib/client/templates/unsubscribe.js', + 'lib/client/templates/post_subscribe.html', + 'lib/client/templates/post_subscribe.js', + 'lib/client/templates/user_subscribed_posts.html', + 'lib/client/templates/user_subscribed_posts.js', ], ['client']); api.add_files([ 'lib/server/notifications-server.js', + 'lib/server/publications.js', 'lib/server/routes.js' ], ['server']);