Add notification, allow watch boards / lists / cards

This commit is contained in:
Liming Xie 2016-01-05 23:26:02 +08:00
parent 9ef8ebaf09
commit 9bbdacc79a
24 changed files with 579 additions and 16 deletions

View file

@ -48,4 +48,77 @@ if (Meteor.isServer) {
createdAt: -1,
});
});
Activities.after.insert((userId, doc) => {
const activity = Activities.findOne(doc._id);
let participants = [];
let watchers = [];
let title = 'Wekan Notification';
let board = null;
const description = `act-${activity.activityType}`;
const params = {
activityId: activity._id,
};
if (activity.userId) {
// No need send notification to user of activity
// participants = _.union(participants, [activity.userId]);
params.user = activity.user().getName();
}
if (activity.boardId) {
board = activity.board();
params.board = board.title;
title = 'act-withBoardTitle';
params.url = board.absoluteUrl();
}
if (activity.memberId) {
participants = _.union(participants, [activity.memberId]);
params.member = activity.member().getName();
}
if (activity.listId) {
const list = activity.list();
watchers = _.union(watchers, list.watchers || []);
params.list = list.title;
}
if (activity.oldListId) {
const oldList = activity.oldList();
watchers = _.union(watchers, oldList.watchers || []);
params.oldList = oldList.title;
}
if (activity.cardId) {
const card = activity.card();
participants = _.union(participants, [card.userId], card.members || []);
watchers = _.union(watchers, card.watchers || []);
params.card = card.title;
title = 'act-withCardTitle';
params.url = card.absoluteUrl();
}
if (activity.commentId) {
const comment = activity.comment();
params.comment = comment.text;
}
if (activity.attachmentId) {
const attachment = activity.attachment();
params.attachment = attachment._id;
}
if (board) {
const boardWatching = _.pluck(_.where(board.watchers, {level: 'watching'}), 'userId');
const boardTracking = _.pluck(_.where(board.watchers, {level: 'tracking'}), 'userId');
const boardMuted = _.pluck(_.where(board.watchers, {level: 'muted'}), 'userId');
switch(board.getWatchDefault()) {
case 'muted':
participants = _.intersection(participants, boardTracking);
watchers = _.intersection(watchers, boardTracking);
break;
case 'tracking':
participants = _.difference(participants, boardMuted);
watchers = _.difference(watchers, boardMuted);
break;
}
watchers = _.union(watchers, boardWatching || []);
}
Notifications.getUsers(participants, watchers).forEach((user) => {
Notifications.notify(user, title, description, params);
});
});
}

View file

@ -151,7 +151,7 @@ Boards.helpers({
},
absoluteUrl() {
return FlowRouter.path('board', { id: this._id, slug: this.slug });
return FlowRouter.url('board', { id: this._id, slug: this.slug });
},
colorClass() {

View file

@ -116,16 +116,12 @@ Cards.helpers({
absoluteUrl() {
const board = this.board();
return FlowRouter.path('card', {
return FlowRouter.url('card', {
boardId: board._id,
slug: board.slug,
cardId: this._id,
});
},
rootUrl() {
return Meteor.absoluteUrl(this.absoluteUrl().replace('/', ''));
},
});
Cards.mutations({

View file

@ -47,6 +47,21 @@ Users.helpers({
return _.contains(invitedBoards, boardId);
},
hasTag(tag) {
const {tags = []} = this.profile;
return _.contains(tags, tag);
},
hasNotification(activityId) {
const {notifications = []} = this.profile;
return _.contains(notifications, activityId);
},
getEmailCache() {
const {emailCache = []} = this.profile;
return emailCache;
},
getInitials() {
const profile = this.profile || {};
if (profile.initials)
@ -99,6 +114,61 @@ Users.mutations({
};
},
addTag(tag) {
return {
$addToSet: {
'profile.tags': tag,
},
};
},
removeTag(tag) {
return {
$pull: {
'profile.tags': tag,
},
};
},
toggleTag(tag) {
if (this.hasTag(tag))
this.removeTag(tag);
else
this.addTag(tag);
},
addNotification(activityId) {
return {
$addToSet: {
'profile.notifications': activityId,
},
};
},
removeNotification(activityId) {
return {
$pull: {
'profile.notifications': activityId,
},
};
},
addEmailCache(text) {
return {
$addToSet: {
'profile.emailCache': text,
},
};
},
clearEmailCache() {
return {
$set: {
'profile.emailCache': [],
},
};
},
setAvatarUrl(avatarUrl) {
return { $set: { 'profile.avatarUrl': avatarUrl }};
},
@ -167,21 +237,18 @@ if (Meteor.isServer) {
user.addInvite(boardId);
try {
const { _id, slug } = board;
const boardUrl = FlowRouter.url('board', { id: _id, slug });
const vars = {
const params = {
user: user.username,
inviter: inviter.username,
board: board.title,
url: boardUrl,
url: board.absoluteUrl(),
};
const lang = user.getLanguage();
Email.send({
to: user.emails[0].address,
from: Accounts.emailTemplates.from,
subject: TAPi18n.__('email-invite-subject', vars, lang),
text: TAPi18n.__('email-invite-text', vars, lang),
subject: TAPi18n.__('email-invite-subject', params, lang),
text: TAPi18n.__('email-invite-text', params, lang),
});
} catch (e) {
throw new Meteor.Error('email-fail', e.message);

89
models/watchable.js Normal file
View file

@ -0,0 +1,89 @@
// simple version, only toggle watch / unwatch
const simpleWatchable = (collection) => {
collection.attachSchema({
watchers: {
type: [String],
optional: true,
},
});
collection.helpers({
getWatchLevels() {
return [true, false];
},
watcherIndex(userId) {
return this.watchers.indexOf(userId);
},
findWatcher(userId) {
return _.contains(this.watchers, userId);
},
});
collection.mutations({
setWatcher(userId, level) {
// if level undefined or null or false, then remove
if (!level) return { $pull: { watchers: userId }};
return { $addToSet: { watchers: userId }};
},
});
};
// more complex version of same interface, with 3 watching levels
const complexWatchOptions = ['watching', 'tracking', 'muted'];
const complexWatchDefault = 'muted';
const complexWatchable = (collection) => {
collection.attachSchema({
'watchers.$.userId': {
type: String,
},
'watchers.$.level': {
type: String,
allowedValues: complexWatchOptions,
},
});
collection.helpers({
getWatchOptions() {
return complexWatchOptions;
},
getWatchDefault() {
return complexWatchDefault;
},
watcherIndex(userId) {
return _.pluck(this.watchers, 'userId').indexOf(userId);
},
findWatcher(userId) {
return _.findWhere(this.watchers, { userId });
},
getWatchLevel(userId) {
const watcher = this.findWatcher(userId);
return watcher ? watcher.level : complexWatchDefault;
},
});
collection.mutations({
setWatcher(userId, level) {
// if level undefined or null or false, then remove
if (level === complexWatchDefault) level = null;
if (!level) return { $pull: { watchers: { userId }}};
const index = this.watcherIndex(userId);
if (index<0) return { $push: { watchers: { userId, level }}};
return {
$set: {
[`watchers.${index}.level`]: level,
},
};
},
});
};
complexWatchable(Boards);
simpleWatchable(Lists);
simpleWatchable(Cards);