mirror of
https://github.com/wekan/wekan.git
synced 2026-02-05 16:11:47 +01:00
Add notification, allow watch boards / lists / cards
This commit is contained in:
parent
9ef8ebaf09
commit
9bbdacc79a
24 changed files with 579 additions and 16 deletions
35
server/notifications/email.js
Normal file
35
server/notifications/email.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// cache the email text in a queue, and send them in a batch
|
||||
Meteor.startup(() => {
|
||||
Notifications.subscribe('cachedEmail', (user, title, description, params) => {
|
||||
// add quote to make titles easier to read in email text
|
||||
const quoteParams = _.clone(params);
|
||||
['card', 'list', 'oldList', 'board', 'comment'].forEach((key) => {
|
||||
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
|
||||
});
|
||||
|
||||
const text = `${params.user} ${TAPi18n.__(description, quoteParams, user.getLanguage())}\n${params.url}`;
|
||||
user.addEmailCache(text);
|
||||
|
||||
const userId = user._id;
|
||||
Meteor.setTimeout(() => {
|
||||
const user = Users.findOne(userId);
|
||||
|
||||
const emailCache = user.getEmailCache();
|
||||
if (emailCache.length === 0) return;
|
||||
|
||||
const text = emailCache.join('\n\n');
|
||||
user.clearEmailCache();
|
||||
|
||||
try {
|
||||
Email.send({
|
||||
to: user.emails[0].address,
|
||||
from: Accounts.emailTemplates.from,
|
||||
subject : TAPi18n.__('act-activity-notify', {}, user.getLanguage()),
|
||||
text,
|
||||
});
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}, 30000, user._id);
|
||||
});
|
||||
});
|
||||
48
server/notifications/notifications.js
Normal file
48
server/notifications/notifications.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// a map of notification service, like email, web, IM, qq, etc.
|
||||
|
||||
// serviceName -> callback(user, title, description, params)
|
||||
// expected arguments to callback:
|
||||
// - user: Meteor user object
|
||||
// - title: String, TAPi18n key
|
||||
// - description, String, TAPi18n key
|
||||
// - params: Object, values extracted from context, to used for above two TAPi18n keys
|
||||
// see example call to Notifications.notify() in models/activities.js
|
||||
const notifyServices = {};
|
||||
|
||||
Notifications = {
|
||||
subscribe: (serviceName, callback) => {
|
||||
notifyServices[serviceName] = callback;
|
||||
},
|
||||
|
||||
unsubscribe: (serviceName) => {
|
||||
if (typeof notifyServices[serviceName] === 'function')
|
||||
delete notifyServices[serviceName];
|
||||
},
|
||||
|
||||
// filter recipients according to user settings for notification
|
||||
getUsers: (participants, watchers) => {
|
||||
const userMap = {};
|
||||
participants.forEach((userId) => {
|
||||
if (userMap[userId]) return;
|
||||
const user = Users.findOne(userId);
|
||||
if (user && user.hasTag('notify-participate')) {
|
||||
userMap[userId] = user;
|
||||
}
|
||||
});
|
||||
watchers.forEach((userId) => {
|
||||
if (userMap[userId]) return;
|
||||
const user = Users.findOne(userId);
|
||||
if (user && user.hasTag('notify-watch')) {
|
||||
userMap[userId] = user;
|
||||
}
|
||||
});
|
||||
return _.map(userMap, (v) => v);
|
||||
},
|
||||
|
||||
notify: (user, title, description, params) => {
|
||||
for(const k in notifyServices) {
|
||||
const notifyImpl = notifyServices[k];
|
||||
if (notifyImpl && typeof notifyImpl === 'function') notifyImpl(user, title, description, params);
|
||||
}
|
||||
},
|
||||
};
|
||||
9
server/notifications/profile.js
Normal file
9
server/notifications/profile.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Meteor.startup(() => {
|
||||
// XXX: add activity id to profile.notifications,
|
||||
// it can be displayed and rendered on web or mobile UI
|
||||
// will uncomment the following code once UI implemented
|
||||
//
|
||||
// Notifications.subscribe('profile', (user, title, description, params) => {
|
||||
// user.addNotification(params.activityId);
|
||||
// });
|
||||
});
|
||||
36
server/notifications/watch.js
Normal file
36
server/notifications/watch.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
Meteor.methods({
|
||||
watch(watchableType, id, level) {
|
||||
check(watchableType, String);
|
||||
check(id, String);
|
||||
check(level, Match.OneOf(String, null));
|
||||
|
||||
const userId = Meteor.userId();
|
||||
|
||||
let watchableObj = null;
|
||||
let board = null;
|
||||
if (watchableType === 'board') {
|
||||
watchableObj = Boards.findOne(id);
|
||||
if (!watchableObj) throw new Meteor.Error('error-board-doesNotExist');
|
||||
board = watchableObj;
|
||||
|
||||
} else if (watchableType === 'list') {
|
||||
watchableObj = Lists.findOne(id);
|
||||
if (!watchableObj) throw new Meteor.Error('error-list-doesNotExist');
|
||||
board = watchableObj.board();
|
||||
|
||||
} else if (watchableType === 'card') {
|
||||
watchableObj = Cards.findOne(id);
|
||||
if (!watchableObj) throw new Meteor.Error('error-card-doesNotExist');
|
||||
board = watchableObj.board();
|
||||
|
||||
} else {
|
||||
throw new Meteor.Error('error-json-schema');
|
||||
}
|
||||
|
||||
if ((board.permission === 'private') && !board.hasMember(userId))
|
||||
throw new Meteor.Error('error-board-notAMember');
|
||||
|
||||
watchableObj.setWatcher(userId, level);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue