Making notifications into their own package

This commit is contained in:
Sacha Greif 2014-09-20 09:57:09 +09:00
parent 075f861e8d
commit 2a911217e9
24 changed files with 461 additions and 72 deletions

View file

@ -66,3 +66,5 @@ matb33:collection-hooks
djedi:sanitize-html
rajit:bootstrap3-datepicker
telescope-update-prompt
telescope-notifications

View file

@ -101,6 +101,7 @@ telescope-lib@0.2.9
telescope-module-embedly@0.2.9
telescope-module-share@0.0.0
telescope-newsletter@0.1.0
telescope-notifications@0.1.0
telescope-rss@0.0.0
telescope-search@0.0.0
telescope-tags@0.0.0

View file

@ -28,16 +28,6 @@ adminNav = adminNav.concat([
}
]);
// Notifications - only load if user is logged in
// Not mandatory, because server won't publish anything even if we try to load.
// Remember about Deps.autorun - user can log in and log out several times
Deps.autorun(function() {
// userId() can be changed before user(), because loading profile takes time
if(Meteor.userId()) {
Meteor.subscribe('notifications');
}
});
// Sort postModules array position using modulePositions as index
postModules = _.sortBy(postModules, function(module){return _.indexOf(modulePositions, module.position)});

View file

@ -137,6 +137,16 @@ Meteor.methods({
if(parentCommentId)
comment.parentCommentId = parentCommentId;
// ------------------------------ Callbacks ------------------------------ //
// run all post submit server callbacks on comment object successively
comment = commentSubmitMethodCallbacks.reduce(function(result, currentFunction) {
return currentFunction(result);
}, comment);
// -------------------------------- Insert ------------------------------- //
var newCommentId=Comments.insert(comment);
// increment comment count
@ -155,36 +165,6 @@ Meteor.methods({
Meteor.call('upvoteComment', comment);
var notificationProperties = {
comment: _.pick(comment, '_id', 'userId', 'author', 'body'),
post: _.pick(post, '_id', 'title', 'url')
};
if(!this.isSimulation){
if(parentCommentId){
// child comment
var parentComment = Comments.findOne(parentCommentId);
var parentUser = Meteor.users.findOne(parentComment.userId);
notificationProperties.parentComment = _.pick(parentComment, '_id', 'userId', 'author');
// reply notification
// do not notify users of their own actions (i.e. they're replying to themselves)
if(parentUser._id != user._id)
createNotification('newReply', notificationProperties, parentUser);
// 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)
createNotification('newComment', notificationProperties, postUser);
}else{
// root comment
// don't notify users of their own comments
if(postUser._id != user._id)
createNotification('newComment', notificationProperties, postUser);
}
}
return comment;
},
removeComment: function(commentId){

View file

@ -256,7 +256,7 @@ Meteor.methods({
// ------------------------------ Callbacks ------------------------------ //
// run all post submit server callbacks on post object successively
post = postSubmitServerCallbacks.reduce(function(result, currentFunction) {
post = postSubmitMethodCallbacks.reduce(function(result, currentFunction) {
return currentFunction(result);
}, post);
@ -274,14 +274,6 @@ Meteor.methods({
Meteor.call('upvotePost', post, postAuthor);
// notify users of new posts
if(Meteor.isServer && !!getSetting('emailNotifications', false)){
// we don't want emails to hold up the post submission, so we make the whole thing async with setTimeout
Meteor.setTimeout(function () {
newPostNotification(post, [userId])
}, 1);
}
return post;
},
setPostedAt: function(post, customPostedAt){

View file

@ -0,0 +1,103 @@
can = {};
// Permissions
// user: Defaults to Meteor.user()
// returnError: If there's an error, should we return what the problem is?
//
// return true if all is well, false || an error string if not
can.view = function(user){
// console.log('canView', 'user:', user, 'returnError:', returnError, getSetting('requireViewInvite'));
if(getSetting('requireViewInvite', false)){
if(Meteor.isClient){
// on client only, default to the current user
var user=(typeof user === 'undefined') ? Meteor.user() : user;
}
if(user && (isAdmin(user) || isInvited(user))){
// if logged in AND either admin or invited
return true;
}else{
return false;
}
}
return true;
};
can.viewById = function(userId, returnError){
// if an invite is required to view, run permission check, else return true
if(getSetting('requireViewInvite', false)){
// if user is logged in, then run canView, else return false
return userId ? canView(Meteor.users.findOne(userId), returnError) : false;
}
return true;
};
can.post = function(user, returnError){
var user=(typeof user === 'undefined') ? Meteor.user() : user;
// console.log('canPost', user, action, getSetting('requirePostInvite'));
if(!user){
return returnError ? "no_account" : false;
} else if (isAdmin(user)) {
return true;
} else if (getSetting('requirePostInvite')) {
if (user.isInvited) {
return true;
} else {
return returnError ? "no_invite" : false;
}
} else {
return true;
}
};
can.postById = function(userId, returnError){
var user = Meteor.users.findOne(userId);
return canPost(user, returnError);
};
can.comment = function(user, returnError){
return canPost(user, returnError);
};
can.commentById = function(userId, returnError){
var user = Meteor.users.findOne(userId);
return canComment(user, returnError);
};
can.upvote = function(user, collection, returnError){
return canPost(user, returnError);
};
can.upvoteById = function(userId, returnError){
var user = Meteor.users.findOne(userId);
return canUpvote(user, returnError);
};
can.downvote = function(user, collection, returnError){
return canPost(user, returnError);
};
can.downvoteById = function(userId, returnError){
var user = Meteor.users.findOne(userId);
return canDownvote(user, returnError);
};
can.edit = function(user, item, returnError){
var user=(typeof user === 'undefined') ? Meteor.user() : user;
if (!user || !item){
return returnError ? "no_rights" : false;
} else if (isAdmin(user)) {
return true;
} else if (user._id!==item.userId) {
return returnError ? "no_rights" : false;
}else {
return true;
}
};
can.editById = function(userId, item){
var user = Meteor.users.findOne(userId);
return canEdit(user, item);
};
can.currentUserEdit = function(item) {
return canEdit(Meteor.user(), item);
};
can.invite = function(user){
return isInvited(user) || isAdmin(user);
};

View file

@ -14,7 +14,12 @@ Package.onUse(function (api) {
'jquery'
], 'client');
api.add_files(['lib/lib.js', 'lib/deep_extend.js', 'lib/autolink.js'], ['client', 'server']);
api.add_files([
'lib/lib.js',
'lib/deep_extend.js',
'lib/autolink.js',
'lib/permissions.js'
], ['client', 'server']);
api.add_files(['lib/client/jquery.exists.js'], ['client']);
@ -26,6 +31,7 @@ Package.onUse(function (api) {
'getSetting',
'getThemeSetting',
'getSiteUrl',
'trimWords'
'trimWords',
'can'
]);
});

View file

@ -62,4 +62,4 @@ var extendPost = function (post) {
return post;
}
postSubmitServerCallbacks.push(extendPost);
postSubmitMethodCallbacks.push(extendPost);

View file

@ -0,0 +1 @@
.build*

View file

@ -0,0 +1,9 @@
// Notifications - only load if user is logged in
// Not mandatory, because server won't publish anything even if we try to load.
// Remember about Deps.autorun - user can log in and log out several times
Tracker.autorun(function() {
// userId() can be changed before user(), because loading profile takes time
if(Meteor.userId()) {
Meteor.subscribe('notifications');
}
});

View file

@ -1,6 +1,6 @@
<template name="notificationItem">
<li class="notification-item {{#if read}}read{{/if}}">
<span class="notification-timestamp">{{nice_time}}</span>
<span class="notification-timestamp">{{niceTime}}</span>
<div class="notification-html">
{{{notificationHTML}}}
</div>

View file

@ -1,5 +1,5 @@
Template[getTemplate('notificationItem')].helpers({
nice_time: function(){
niceTime: function(){
return moment(this.timestamp).fromNow();
},
properties: function(){

View file

@ -1,4 +1,4 @@
<template name="notification_new_comment">
<template name="notificationNewComment">
<p>
<a href="{{profileUrl}}">{{author}}</a>
left a new comment on

View file

@ -1,4 +1,4 @@
<template name="notification_new_reply">
<template name="notificationNewReply">
<p>
<a href="{{profileUrl}}">{{author}}</a>
has replied to your comment on

View file

@ -21,12 +21,12 @@ Notifications = new Meteor.Collection('notifications');
// });
Notifications.allow({
insert: function(userId, doc){
// new notifications can only be created via a Meteor method
return false;
}
, update: canEditById
, remove: canEditById
insert: function(userId, doc){
// new notifications can only be created via a Meteor method
return false;
},
update: can.editById,
remove: can.editById
});
createNotification = function(event, properties, userToNotify) {
@ -67,11 +67,11 @@ buildSiteNotification = function (notification) {
switch(event){
case 'newReply':
template = 'notification_new_reply';
template = 'notificationNewReply';
break;
case 'newComment':
template = 'notification_new_comment';
template = 'notificationNewComment';
break;
default:
@ -97,4 +97,53 @@ Meteor.methods({
{multi: true}
);
}
});
// add new post notification callback on post submit
postSubmitMethodCallbacks.push(function (post) {
if(Meteor.isServer && !!getSetting('emailNotifications', false)){
// we don't want emails to hold up the post submission, so we make the whole thing async with setTimeout
Meteor.setTimeout(function () {
newPostNotification(post, [post.userId])
}, 1);
}
return post;
});
// add new comment notification callback on comment submit
commentSubmitMethodCallbacks.push(function (comment) {
if(Meteor.isServer){
var post = Posts.findOne(comment.postId);
var parentCommentId = comment.parentCommentId;
var notificationProperties = {
comment: _.pick(comment, '_id', 'userId', 'author', 'body'),
post: _.pick(post, '_id', 'title', 'url')
};
if(parentCommentId){
// child comment
var parentComment = Comments.findOne(parentCommentId);
var parentUser = Meteor.users.findOne(parentComment.userId);
notificationProperties.parentComment = _.pick(parentComment, '_id', 'userId', 'author');
// reply notification
// do not notify users of their own actions (i.e. they're replying to themselves)
if(parentUser._id != user._id)
createNotification('newReply', notificationProperties, parentUser);
// 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)
createNotification('newComment', notificationProperties, postUser);
}else{
// root comment
// don't notify users of their own comments
if(postUser._id != user._id)
createNotification('newComment', notificationProperties, postUser);
}
}
});

View file

@ -0,0 +1,7 @@
Meteor.publish('notifications', function() {
// only publish notifications belonging to the current user
if(canViewById(this.userId)){
return Notifications.find({userId:this.userId});
}
return [];
});

View file

@ -0,0 +1,58 @@
Package.describe({
summary: "Telescope notifications package",
version: '0.1.0',
name: "telescope-notifications"
});
Package.onUse(function (api) {
api.use([
'telescope-lib',
'telescope-base',
'aldeed:simple-schema'
], ['client', 'server']);
api.use([
'jquery',
'underscore',
'iron:router',
'templating',
'tracker'
], 'client');
api.use([
'cmather:handlebars-server'
], ['server']);
api.add_files([
'lib/notifications.js',
], ['client', 'server']);
api.add_files([
'lib/client/notifications-client.js',
'lib/client/templates/notification_item.html',
'lib/client/templates/notification_item.js',
'lib/client/templates/notification_new_comment.html',
'lib/client/templates/notification_new_reply.html',
'lib/client/templates/notifications_menu.html',
'lib/client/templates/notifications_menu.js',
'lib/client/templates/unsubscribe.html',
'lib/client/templates/unsubscribe.js',
], ['client']);
api.add_files([
'lib/server/notifications-server.js',
'lib/server/publication.js'
], ['server']);
api.export([
'createNotification',
'buildSiteNotification',
'newPostNotification',
'buildEmailNotification',
'getUnsubscribeLink',
'postSubmitMethodCallbacks'
]);
});
// TODO: once user profile edit form is generated dynamically, add notification options from this package as well.

View file

@ -0,0 +1,199 @@
{
"dependencies": [
[
"aldeed:simple-schema",
"1.0.3"
],
[
"application-configuration",
"1.0.2"
],
[
"base64",
"1.0.0"
],
[
"binary-heap",
"1.0.0"
],
[
"blaze",
"2.0.0"
],
[
"blaze-tools",
"1.0.0"
],
[
"boilerplate-generator",
"1.0.0"
],
[
"callback-hook",
"1.0.0"
],
[
"check",
"1.0.0"
],
[
"cmather:handlebars-server",
"0.2.0"
],
[
"ddp",
"1.0.8"
],
[
"deps",
"1.0.3"
],
[
"ejson",
"1.0.2"
],
[
"follower-livedata",
"1.0.1"
],
[
"geojson-utils",
"1.0.0"
],
[
"handlebars",
"1.0.0"
],
[
"html-tools",
"1.0.0"
],
[
"htmljs",
"1.0.1"
],
[
"id-map",
"1.0.0"
],
[
"iron:core",
"0.3.4"
],
[
"iron:dynamic-template",
"0.4.1"
],
[
"iron:layout",
"0.4.1"
],
[
"iron:router",
"0.9.3"
],
[
"jquery",
"1.0.0"
],
[
"json",
"1.0.0"
],
[
"logging",
"1.0.3"
],
[
"meteor",
"1.1.0"
],
[
"minifiers",
"1.1.0"
],
[
"minimongo",
"1.0.3"
],
[
"mongo",
"1.0.5"
],
[
"observe-sequence",
"1.0.2"
],
[
"ordered-dict",
"1.0.0"
],
[
"random",
"1.0.0"
],
[
"reactive-dict",
"1.0.2"
],
[
"reactive-var",
"1.0.1"
],
[
"retry",
"1.0.0"
],
[
"routepolicy",
"1.0.1"
],
[
"spacebars",
"1.0.1"
],
[
"spacebars-compiler",
"1.0.2"
],
[
"telescope-base",
"0.0.0"
],
[
"telescope-i18n",
"0.0.0"
],
[
"telescope-lib",
"0.2.9"
],
[
"templating",
"1.0.6"
],
[
"tracker",
"1.0.2"
],
[
"ui",
"1.0.2"
],
[
"underscore",
"1.0.0"
],
[
"webapp",
"1.1.1"
],
[
"webapp-hashing",
"1.0.0"
]
],
"pluginDependencies": [],
"toolVersion": "meteor-tool@1.0.30",
"format": "1.0"
}

View file

@ -11,14 +11,6 @@ Meteor.publish('settings', function() {
return Settings.find({}, options);
});
Meteor.publish('notifications', function() {
// only publish notifications belonging to the current user
if(canViewById(this.userId)){
return Notifications.find({userId:this.userId});
}
return [];
});
Meteor.publish('invites', function(){
if(canViewById(this.userId)){
return Invites.find({invitingUserId:this.userId});