diff --git a/packages/rocketchat-api/server/v1/chat.js b/packages/rocketchat-api/server/v1/chat.js index 6e3486ca34ab4..c53f25f8b251c 100644 --- a/packages/rocketchat-api/server/v1/chat.js +++ b/packages/rocketchat-api/server/v1/chat.js @@ -317,3 +317,24 @@ RocketChat.API.v1.addRoute('chat.reportMessage', { authRequired: true }, { return RocketChat.API.v1.success(); } }); + +RocketChat.API.v1.addRoute('chat.ignoreUser', { authRequired: true }, { + get() { + const { rid, userId } = this.queryParams; + let { ignore = true } = this.queryParams; + + ignore = typeof ignore === 'string' ? /true|1/.test(ignore) : ignore; + + if (!rid || !rid.trim()) { + throw new Meteor.Error('error-room-id-param-not-provided', 'The required "rid" param is missing.'); + } + + if (!userId || !userId.trim()) { + throw new Meteor.Error('error-user-id-param-not-provided', 'The required "userId" param is missing.'); + } + + Meteor.runAsUser(this.userId, () => Meteor.call('ignoreUser', { rid, userId, ignore })); + + return RocketChat.API.v1.success(); + } +}); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 8aab13363db55..60161c16aef3c 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -889,6 +889,8 @@ "Iframe_Integration_send_enable_Description": "Send events to parent window", "Iframe_Integration_send_target_origin": "Send Target Origin", "Iframe_Integration_send_target_origin_Description": "Origin with protocol prefix, which commands are sent to e.g. 'https://localhost', or * to allow sending to anywhere.", + "Ignore": "Ignore", + "Ignored": "Ignored", "IMAP_intercepter_already_running": "IMAP intercepter already running", "IMAP_intercepter_Not_running": "IMAP intercepter Not running", "Impersonate_next_agent_from_queue": "Impersonate next agent from queue", @@ -1317,6 +1319,7 @@ "Message_HideType_ru": "Hide \"User Removed\" messages", "Message_HideType_uj": "Hide \"User Join\" messages", "Message_HideType_ul": "Hide \"User Leave\" messages", + "Message_Ignored": "This message was ignored", "Message_info": "Message info", "Message_KeepHistory": "Keep Per Message Editing History", "Message_MaxAll": "Maximum Channel Size for ALL Message", @@ -2035,6 +2038,7 @@ "unarchive-room_description": "Permission to unarchive channels", "Unblock_User": "Unblock User", "Uninstall": "Uninstall", + "Unignore": "Unignore", "Unmute_someone_in_room": "Unmute someone in the room", "Unmute_user": "Unmute user", "Unnamed": "Unnamed", @@ -2086,8 +2090,10 @@ "User_has_been_activated": "User has been activated", "User_has_been_deactivated": "User has been deactivated", "User_has_been_deleted": "User has been deleted", + "User_has_been_ignored": "User has been ignored", "User_has_been_muted_in_s": "User has been muted in %s", "User_has_been_removed_from_s": "User has been removed from %s", + "User_has_been_unignored": "User is no longer ignored", "User_Info": "User Info", "User_Interface": "User Interface", "User_is_blocked": "User is blocked", diff --git a/packages/rocketchat-lib/client/MessageAction.js b/packages/rocketchat-lib/client/MessageAction.js index dc6afdb31636a..225baa5f4eb17 100644 --- a/packages/rocketchat-lib/client/MessageAction.js +++ b/packages/rocketchat-lib/client/MessageAction.js @@ -4,6 +4,17 @@ import _ from 'underscore'; import moment from 'moment'; import toastr from 'toastr'; +const success = function success(fn) { + return function(error, result) { + if (error) { + return handleError(error); + } + if (result) { + fn.call(this, result); + } + }; +}; + RocketChat.MessageAction = new class { /* config expects the following keys (only id is mandatory): @@ -279,4 +290,45 @@ Meteor.startup(function() { order: 6, group: 'menu' }); + + + + RocketChat.MessageAction.addButton({ + id: 'ignore-user', + icon: 'ban', + label: t('Ignore'), + context: ['message', 'message-mobile'], + action() { + const [, {rid, u: {_id}}] = this._arguments; + Meteor.call('ignoreUser', { rid, userId:_id, ignore: true}, success(() => toastr.success(t('User_has_been_ignored')))); + }, + condition(message) { + const subscription = RocketChat.models.Subscriptions.findOne({rid: message.rid}); + + return Meteor.userId() !== message.u._id && !(subscription.ignored && subscription.ignored.indexOf(message.u._id) > -1); + }, + order: 20, + group: 'menu' + }); + + RocketChat.MessageAction.addButton({ + id: 'unignore-user', + icon: 'ban', + label: t('Unignore'), + context: ['message', 'message-mobile'], + action() { + const [, {rid, u: {_id}}] = this._arguments; + Meteor.call('ignoreUser', { rid, userId:_id, ignore: false}, success(() => toastr.success(t('User_has_been_unignored')))); + + }, + condition(message) { + const subscription = RocketChat.models.Subscriptions.findOne({rid: message.rid}); + return Meteor.userId() !== message.u._id && subscription.ignored && subscription.ignored.indexOf(message.u._id) > -1; + }, + order: 20, + group: 'menu' + }); + + + }); diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 16c425a8a6dd6..33532cb036a9c 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -205,12 +205,11 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room, userId) { // Don't fetch all users if room exceeds max members const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); const disableAllMessageNotifications = room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; - const subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id, disableAllMessageNotifications); + const subscriptions = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id, disableAllMessageNotifications) || []; const userIds = []; - subscriptions.forEach((s) => { - userIds.push(s.u._id); - }); + subscriptions.forEach(s => userIds.push(s.u._id)); const users = {}; + RocketChat.models.Users.findUsersByIds(userIds, { fields: { 'settings.preferences': 1 } }).forEach((user) => { users[user._id] = user; }); @@ -223,6 +222,10 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room, userId) { return; } + if (Array.isArray(subscription.ignored) && subscription.ignored.find(message.u._id)) { + return; + } + const { audioNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'audioNotifications'), desktopNotifications = RocketChat.getUserPreference(users[subscription.u._id], 'desktopNotifications'), diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index 9e585150bf012..116751db301d8 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -454,6 +454,21 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.update(query, update, { multi: true }); } + ignoreUser({_id, ignoredUser : ignored, ignore = true}) { + const query = { + _id + }; + const update = { + }; + if (ignore) { + update.$addToSet = { ignored }; + } else { + update.$pull = { ignored }; + } + + return this.update(query, update); + } + setAlertForRoomIdExcludingUserId(roomId, userId) { const query = { rid: roomId, diff --git a/packages/rocketchat-theme/client/imports/components/messages.css b/packages/rocketchat-theme/client/imports/components/messages.css index 16e1b2986988a..1c76d40f33656 100644 --- a/packages/rocketchat-theme/client/imports/components/messages.css +++ b/packages/rocketchat-theme/client/imports/components/messages.css @@ -41,6 +41,23 @@ } .message { + + & .toggle-hidden { + display: none; + } + + &--ignored { + & .body { + display: none; + } + & .toggle-hidden { + display: block; + } + & + .message--ignored.sequential { + display: none; + } + } + &.active { & .message-actions__label { color: var(--rc-color-button-primary); diff --git a/packages/rocketchat-theme/client/imports/general/base_old.css b/packages/rocketchat-theme/client/imports/general/base_old.css index 638e7da8c6fb7..b0b9f26cb94e6 100644 --- a/packages/rocketchat-theme/client/imports/general/base_old.css +++ b/packages/rocketchat-theme/client/imports/general/base_old.css @@ -5384,6 +5384,11 @@ body:not(.is-cordova) { cursor: pointer; } +.toggle-hidden { + cursor: pointer; + font-style: italic; +} + /* kinda hacky, needed in oembedFrageWidget.html */ .rc-old br.only-after-a { diff --git a/packages/rocketchat-ui-flextab/client/tabs/membersList.html b/packages/rocketchat-ui-flextab/client/tabs/membersList.html index 651b3e73f1a5f..d9c415a730356 100644 --- a/packages/rocketchat-ui-flextab/client/tabs/membersList.html +++ b/packages/rocketchat-ui-flextab/client/tabs/membersList.html @@ -37,7 +37,7 @@ {{> avatar username=user.username}}
- {{displayName}} {{utcOffset}} + {{ignored}} {{displayName}} {{utcOffset}}
{{> icon user=. block="rc-member-list__menu js-action" icon="menu" }} diff --git a/packages/rocketchat-ui-flextab/client/tabs/membersList.js b/packages/rocketchat-ui-flextab/client/tabs/membersList.js index 21f27367f5216..1a9f8ec571d71 100644 --- a/packages/rocketchat-ui-flextab/client/tabs/membersList.js +++ b/packages/rocketchat-ui-flextab/client/tabs/membersList.js @@ -1,8 +1,13 @@ -/* globals WebRTC popover */ +/* globals WebRTC popover isRtl */ import _ from 'underscore'; import {getActions} from './userActions'; Template.membersList.helpers({ + ignored() { + const {user} = this; + const sub = RocketChat.models.Subscriptions.findOne({rid: Session.get('openedRoom')}); + return sub && sub.ignored && sub.ignored.indexOf(user._id) > -1 ? `(${ t('Ignored') })` : ''; + }, tAddUsers() { return t('Add_users'); }, @@ -63,7 +68,7 @@ Template.membersList.helpers({ return { user, - status: (onlineUsers[user.username] != null ? onlineUsers[user.username].status : undefined), + status: (onlineUsers[user.username] != null ? onlineUsers[user.username].status : 'offline'), muted: Array.from(roomMuted).includes(user.username), utcOffset }; @@ -76,7 +81,7 @@ Template.membersList.helpers({ } // show online users first. // sortBy is stable, so we can do this - users = _.sortBy(users, u => u.status == null); + users = _.sortBy(users, u => u.status === 'offline'); let hasMore = undefined; const usersLimit = Template.instance().usersLimit.get(); @@ -216,11 +221,21 @@ Template.membersList.events({ e.preventDefault(); const config = { columns, + mousePosition: () => ({ + x: e.currentTarget.getBoundingClientRect().right + 10, + y: e.currentTarget.getBoundingClientRect().bottom + 100 + }), + customCSSProperties: () => ({ + top: `${ e.currentTarget.getBoundingClientRect().bottom + 10 }px`, + left: isRtl() ? `${ e.currentTarget.getBoundingClientRect().left - 10 }px` : undefined + }), data: { rid: this._id, username: instance.data.username, instance }, + offsetHorizontal: 15, + activeElement: e.currentTarget, currentTarget: e.currentTarget, onDestroyed:() => { e.currentTarget.parentElement.classList.remove('active'); @@ -256,6 +271,7 @@ Template.membersList.onCreated(function() { this.showDetail = new ReactiveVar(false); this.filter = new ReactiveVar(''); + this.users = new ReactiveVar([]); this.total = new ReactiveVar; this.loading = new ReactiveVar(true); @@ -264,7 +280,6 @@ Template.membersList.onCreated(function() { Tracker.autorun(() => { if (this.data.rid == null) { return; } - this.loading.set(true); return Meteor.call('getUsersOfRoom', this.data.rid, this.showAllUsers.get(), (error, users) => { this.users.set(users.records); diff --git a/packages/rocketchat-ui-flextab/client/tabs/userActions.js b/packages/rocketchat-ui-flextab/client/tabs/userActions.js index 6f79c1c68d102..3b9a2096949e7 100644 --- a/packages/rocketchat-ui-flextab/client/tabs/userActions.js +++ b/packages/rocketchat-ui-flextab/client/tabs/userActions.js @@ -6,7 +6,10 @@ import toastr from 'toastr'; export const getActions = function({ user, directActions, hideAdminControls }) { const hasPermission = RocketChat.authz.hasAllPermission; - + const isIgnored = () => { + const sub = RocketChat.models.Subscriptions.findOne({rid : Session.get('openedRoom')}); + return sub && sub.ignored && sub.ignored.indexOf(user._id) > -1; + }; const canSetLeader= () => { return RocketChat.authz.hasAllPermission('set-leader', Session.get('openedRoom')); }; @@ -302,6 +305,24 @@ export const getActions = function({ user, directActions, hideAdminControls }) { })); }) }; + }, () => { + if (!directActions || user._id === Meteor.userId()) { + return; + } + if (isIgnored()) { + return { + group: 'channel', + icon : 'ban', + name: t('Unignore'), + action: prevent(getUser, ({_id}) => Meteor.call('ignoreUser', { rid: Session.get('openedRoom'), userId:_id, ignore: false}, success(() => toastr.success(t('User_has_been_unignored'))))) + }; + } + return { + group: 'channel', + icon : 'ban', + name: t('Ignore'), + action: prevent(getUser, ({_id}) => Meteor.call('ignoreUser', { rid: Session.get('openedRoom'), userId:_id, ignore: true}, success(() => toastr.success(t('User_has_been_ignored'))))) + }; }, () => { if (!directActions || !canMuteUser()) { return; diff --git a/packages/rocketchat-ui-master/public/icons.svg b/packages/rocketchat-ui-master/public/icons.svg index f1b1d85eebce3..7b2b722e07e73 100644 --- a/packages/rocketchat-ui-master/public/icons.svg +++ b/packages/rocketchat-ui-master/public/icons.svg @@ -105,4 +105,5 @@ + diff --git a/packages/rocketchat-ui-message/client/message.html b/packages/rocketchat-ui-message/client/message.html index fe107febd65f4..bc14740efc012 100644 --- a/packages/rocketchat-ui-message/client/message.html +++ b/packages/rocketchat-ui-message/client/message.html @@ -1,5 +1,5 @@