From 9bbdacc79a89667e0d6f1ed30c415e5350ad468b Mon Sep 17 00:00:00 2001 From: Liming Xie Date: Tue, 5 Jan 2016 23:26:02 +0800 Subject: Add notification, allow watch boards / lists / cards --- models/activities.js | 73 ++++++++++++++++++++++++++++++++++++++++++ models/boards.js | 2 +- models/cards.js | 6 +--- models/users.js | 81 ++++++++++++++++++++++++++++++++++++++++++----- models/watchable.js | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 models/watchable.js (limited to 'models') diff --git a/models/activities.js b/models/activities.js index 5de07ee5..0aa4fa54 100644 --- a/models/activities.js +++ b/models/activities.js @@ -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); + }); + }); } diff --git a/models/boards.js b/models/boards.js index e20ca8ce..64d6df62 100644 --- a/models/boards.js +++ b/models/boards.js @@ -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() { diff --git a/models/cards.js b/models/cards.js index 1895fc69..09c86191 100644 --- a/models/cards.js +++ b/models/cards.js @@ -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({ diff --git a/models/users.js b/models/users.js index 5d9c218a..3bb7324f 100644 --- a/models/users.js +++ b/models/users.js @@ -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); diff --git a/models/watchable.js b/models/watchable.js new file mode 100644 index 00000000..6821f847 --- /dev/null +++ b/models/watchable.js @@ -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); -- cgit v1.2.3-1-g7c22