diff --git a/common/dist/scripts/habitrpg-shared.js b/common/dist/scripts/habitrpg-shared.js index 3fee72d973..1b0b86a2b0 100644 --- a/common/dist/scripts/habitrpg-shared.js +++ b/common/dist/scripts/habitrpg-shared.js @@ -7173,7 +7173,6 @@ process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues -process.versions = {}; function noop() {} diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json index f53105d6fe..d182b5940b 100644 --- a/common/locales/en/settings.json +++ b/common/locales/en/settings.json @@ -96,6 +96,7 @@ "inactivityEmails": "Your account is inactive", "questStarted": "Your Quest has Begun", "invitedQuest": "Invited to Quest", + "kickedGroup": "Kicked from group", "remindersToLogin": "Reminders to check in to HabitRPG", "unsubscribeAllEmails": "Check to Unsubscribe from Emails", "unsubscribeAllEmailsText": "By checking this box, I certify that I understand that by unsubscribing from all emails, HabitRPG will never be able to notify me via email about important changes to the site or my account.", diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index ab49091077..a87992f43d 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -21,88 +21,105 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' return ~(memberIds.indexOf(userid)); } - $scope.isMember = function(user, group){ - return ~(group.members.indexOf(user._id)); + $scope.isMember = function(user, group){ + return ~(group.members.indexOf(user._id)); + } + + $scope.Members = Members; + $scope._editing = {group:false}; + + $scope.save = function(group){ + if(group._newLeader && group._newLeader._id) group.leader = group._newLeader._id; + group.$save(); + group._editing = false; + } + + $scope.deleteAllMessages = function() { + if (confirm(window.env.t('confirmDeleteAllMessages'))) { + User.user.ops.clearPMs({}); } + } - $scope.Members = Members; - $scope._editing = {group:false}; + // ------ Modals ------ - $scope.save = function(group){ - if(group._newLeader && group._newLeader._id) group.leader = group._newLeader._id; - group.$save(); - group._editing = false; - } - - $scope.deleteAllMessages = function() { - if (confirm(window.env.t('confirmDeleteAllMessages'))) { - User.user.ops.clearPMs({}); - } - } - - // ------ Modals ------ - - $scope.clickMember = function(uid, forceShow) { - if (User.user._id == uid && !forceShow) { - if ($state.is('tasks')) { - $state.go('options.profile.avatar'); - } else { - $state.go('tasks'); - } + $scope.clickMember = function(uid, forceShow) { + if (User.user._id == uid && !forceShow) { + if ($state.is('tasks')) { + $state.go('options.profile.avatar'); } else { - // We need the member information up top here, but then we pass it down to the modal controller - // down below. Better way of handling this? - Members.selectMember(uid, function(){ - $rootScope.openModal('member', {controller:'MemberModalCtrl', windowClass:'profile-modal', size:'lg'}); - }); + $state.go('tasks'); } - } - - $scope.removeMember = function(group, member, isMember){ - $rootScope.openModal('remove-member'); - /*var yes = confirm(window.env.t('sureKick')) - if(yes){ - Groups.Group.removeMember({gid: group._id, uuid: member._id }, undefined, function(){ - if(isMember){ - _.pull(group.members, member); - }else{ - _.pull(group.invites, member); - } - }); - }*/ - } - - $scope.openInviteModal = function(group){ - $rootScope.openModal('invite-friends', {controller:'InviteToGroupCtrl', resolve: - {injectedGroup: function(){ - return group; - }}}); - }; - - //var serializeQs = function(obj, prefix){ - // var str = []; - // for(var p in obj) { - // if (obj.hasOwnProperty(p)) { - // var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p]; - // str.push(typeof v == "object" ? - // serializeQs(v, k) : - // encodeURIComponent(k) + "=" + encodeURIComponent(v)); - // } - // } - // return str.join("&"); - //} - // - //$scope.inviteLink = function(obj){ - // return window.env.BASE_URL + '?' + serializeQs({partyInvite: obj}); - //} - - $scope.quickReply = function(uid) { + } else { + // We need the member information up top here, but then we pass it down to the modal controller + // down below. Better way of handling this? Members.selectMember(uid, function(){ - $rootScope.openModal('private-message',{controller:'MemberModalCtrl'}); + $rootScope.openModal('member', {controller:'MemberModalCtrl', windowClass:'profile-modal', size:'lg'}); }); } } - ]) + + + $scope.removeMember = function(group, member, isMember){ + // TODO find a better way to do this (share data with remove member modal) + $scope.removeMemberData = { + group: group, + member: member, + isMember: isMember + }; + $rootScope.openModal('remove-member', {scope: $scope}); + } + + $scope.confirmRemoveMember = function(confirm){ + if(confirm){ + Groups.Group.removeMember({ + gid: $scope.removeMemberData.group._id, + uuid: $scope.removeMemberData.member._id, + message: $scope.removeMemberData.message, + }, undefined, function(){ + if($scope.removeMemberData.isMember){ + _.pull($scope.removeMemberData.group.members, $scope.removeMemberData.member); + }else{ + _.pull($scope.removeMemberData.group.invites, $scope.removeMemberData.member); + } + + $scope.removeMemberData = undefined; + }); + }else{ + $scope.removeMemberData = undefined; + } + } + + $scope.openInviteModal = function(group){ + $rootScope.openModal('invite-friends', {controller:'InviteToGroupCtrl', resolve: + {injectedGroup: function(){ + return group; + }}}); + }; + + //var serializeQs = function(obj, prefix){ + // var str = []; + // for(var p in obj) { + // if (obj.hasOwnProperty(p)) { + // var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p]; + // str.push(typeof v == "object" ? + // serializeQs(v, k) : + // encodeURIComponent(k) + "=" + encodeURIComponent(v)); + // } + // } + // return str.join("&"); + //} + // + //$scope.inviteLink = function(obj){ + // return window.env.BASE_URL + '?' + serializeQs({partyInvite: obj}); + //} + + $scope.quickReply = function(uid) { + Members.selectMember(uid, function(){ + $rootScope.openModal('private-message',{controller:'MemberModalCtrl'}); + }); + } + + }]) .controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification', function($scope, User, Groups, injectedGroup, $http, Notification){ $scope.group = injectedGroup; diff --git a/website/src/controllers/groups.js b/website/src/controllers/groups.js index 6883aebef0..8eda273a34 100644 --- a/website/src/controllers/groups.js +++ b/website/src/controllers/groups.js @@ -660,8 +660,21 @@ api.invite = function(req, res, next){ api.removeMember = function(req, res, next){ var group = res.locals.group; var uuid = req.query.uuid; + var message = req.query.message; var user = res.locals.user; + // Send an email to the removed user with an optional message from the leader + var sendMessage = function(removedUser){ + if(removedUser.preferences.emailNotifications.kickedGroup !== false){ + utils.txnEmail(removedUser, ('kicked-from-' + group.type), [ + {name: 'GROUP_NAME', content: group.name}, + {name: 'MESSAGE', content: message}, + {name: 'GUILDS_LINK', content: nconf.get('BASE_URL') + '/#/options/groups/guilds/public'}, + {name: 'PARTY_WANTED_GUILD', content: nconf.get('BASE_URL') + '/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'} + ]); + } + } + if(group.leader !== user._id){ return res.json(401, {err: "Only group leader can remove a member!"}); } @@ -678,12 +691,21 @@ api.removeMember = function(req, res, next){ Group.update({_id:group._id},update, function(err, saved){ if (err) return next(err); - // Sending an empty 204 because Group.update doesn't return the group - // see http://mongoosejs.com/docs/api.html#model_Model.update - return res.send(204); + User.findById(uuid, function(err, removedUser){ + if(err) return next(err); + + sendMessage(removedUser); + + // Sending an empty 204 because Group.update doesn't return the group + // see http://mongoosejs.com/docs/api.html#model_Model.update + group = uuid = null; + return res.send(204); + }); }); }else if(_.contains(group.invites, uuid)){ User.findById(uuid, function(err,invited){ + if(err) return next(err); + var invitations = invited.invitations; if(group.type === 'guild'){ invitations.guilds.splice(_.indexOf(invitations.guilds, group._id), 1); @@ -703,8 +725,9 @@ api.removeMember = function(req, res, next){ // Sending an empty 204 because Group.update doesn't return the group // see http://mongoosejs.com/docs/api.html#model_Model.update - return res.send(204); + sendMessage(invited); group = uuid = null; + return res.send(204); }); }); diff --git a/website/src/models/user.js b/website/src/models/user.js index 21a4330c8c..024bcedcc1 100644 --- a/website/src/models/user.js +++ b/website/src/models/user.js @@ -308,6 +308,7 @@ var UserSchema = new Schema({ emailNotifications: { unsubscribeFromAll: {type: Boolean, 'default': false}, newPM: {type: Boolean, 'default': true}, + kickedGroup: {type: Boolean, 'default': true}, wonChallenge: {type: Boolean, 'default': true}, giftedGems: {type: Boolean, 'default': true}, giftedSubscription: {type: Boolean, 'default': true}, diff --git a/website/src/utils.js b/website/src/utils.js index d03ee36b11..51b29a4356 100644 --- a/website/src/utils.js +++ b/website/src/utils.js @@ -69,7 +69,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables){ // When only one recipient send his info as variables if(mailingInfoArray.length === 1 && mailingInfoArray[0].name){ variables.push({name: 'RECIPIENT_NAME', content: mailingInfoArray[0].name}); - } + } if(isProd && mailingInfoArray.length > 0){ request({ diff --git a/website/views/options/settings.jade b/website/views/options/settings.jade index 643b4da4ae..43ba6ab88a 100644 --- a/website/views/options/settings.jade +++ b/website/views/options/settings.jade @@ -305,6 +305,11 @@ script(id='partials/options.settings.notifications.html', type="text/ng-template input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.invitedGuild', ng-change='set({"preferences.emailNotifications.invitedGuild": user.preferences.emailNotifications.invitedGuild ? true: false})') span=env.t('invitedGuild') + .checkbox + label + input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.kickedGroup', ng-change='set({"preferences.emailNotifications.kickedGroup": user.preferences.emailNotifications.kickedGroup ? true: false})') + span=env.t('kickedGroup') + .checkbox label input(type='checkbox', ng-disabled='user.preferences.emailNotifications.unsubscribeFromAll === true', ng-model='user.preferences.emailNotifications.questStarted', ng-change='set({"preferences.emailNotifications.questStarted": user.preferences.emailNotifications.questStarted ? true: false})') diff --git a/website/views/shared/modals/members.jade b/website/views/shared/modals/members.jade index 7cdeb13074..dc46f4bb43 100644 --- a/website/views/shared/modals/members.jade +++ b/website/views/shared/modals/members.jade @@ -106,7 +106,7 @@ script(type='text/ng-template', id='modals/remove-member.html') .modal-header h4=env.t('sureKick') .modal-body - textarea.form-control(type='text',rows='5', placeholder=env.t('optionalMessage')) + textarea.form-control(type='text',rows='5',placeholder=env.t('optionalMessage'),ng-model='removeMemberData.message') .modal-footer - button.pull-left.btn.btn-danger(ng-click='')=env.t('yesRemove') - button.btn.btn-default(ng-click='$close()')=env.t('cancel') \ No newline at end of file + button.pull-left.btn.btn-danger(ng-click='confirmRemoveMember(true); $close()')=env.t('yesRemove') + button.btn.btn-default(ng-click='confirmRemoveMember(false); $close()')=env.t('cancel') \ No newline at end of file