diff --git a/app/api/server/v1/channels.js b/app/api/server/v1/channels.js index b1d9552bca30..f92132a7d4e5 100644 --- a/app/api/server/v1/channels.js +++ b/app/api/server/v1/channels.js @@ -539,7 +539,7 @@ API.v1.addRoute('channels.members', { authRequired: true }, { const members = subscriptions.fetch().map((s) => s.u && s.u._id); const users = Users.find({ _id: { $in: members } }, { - fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, + fields: { _id: 1, username: 1, name: 1, status: 1, statusText: 1, utcOffset: 1 }, sort: { username: sort.username != null ? sort.username : 1 }, }).fetch(); diff --git a/app/api/server/v1/groups.js b/app/api/server/v1/groups.js index 32cd1609bd7f..b84c1d171a06 100644 --- a/app/api/server/v1/groups.js +++ b/app/api/server/v1/groups.js @@ -497,7 +497,7 @@ API.v1.addRoute('groups.members', { authRequired: true }, { const members = subscriptions.fetch().map((s) => s.u && s.u._id); const users = Users.find({ _id: { $in: members } }, { - fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, + fields: { _id: 1, username: 1, name: 1, status: 1, statusText: 1, utcOffset: 1 }, sort: { username: sort.username != null ? sort.username : 1 }, }).fetch(); diff --git a/app/api/server/v1/im.js b/app/api/server/v1/im.js index 13502c083a64..5afd41d05cb1 100644 --- a/app/api/server/v1/im.js +++ b/app/api/server/v1/im.js @@ -205,7 +205,7 @@ API.v1.addRoute(['dm.members', 'im.members'], { authRequired: true }, { const members = cursor.fetch().map((s) => s.u && s.u.username); const users = Users.find({ username: { $in: members } }, { - fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, + fields: { _id: 1, username: 1, name: 1, status: 1, statusText: 1, utcOffset: 1 }, sort: { username: sort && sort.username ? sort.username : 1 }, }).fetch(); diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 82596a4e46f6..8512a5032526 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -18,7 +18,7 @@ import { } from '../../../lib'; import { getFullUserData } from '../../../lib/server/functions/getFullUserData'; import { API } from '../api'; -import { setStatusMessage } from '../../../lib/server'; +import { setStatusText } from '../../../lib/server'; API.v1.addRoute('users.create', { authRequired: true }, { post() { @@ -370,7 +370,7 @@ API.v1.addRoute('users.setStatus', { authRequired: true }, { Meteor.runAsUser(user._id, () => { if (this.bodyParams.message) { - setStatusMessage(user._id, this.bodyParams.message); + setStatusText(user._id, this.bodyParams.message); } if (this.bodyParams.status) { const validStatus = ['online', 'away', 'offline', 'busy']; diff --git a/app/lib/lib/roomTypes/direct.js b/app/lib/lib/roomTypes/direct.js index 0f55c1388ee3..8499332c8918 100644 --- a/app/lib/lib/roomTypes/direct.js +++ b/app/lib/lib/roomTypes/direct.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; -import { ChatRoom, Subscriptions, Users } from '../../../models'; +import { ChatRoom, Subscriptions } from '../../../models'; import { openRoom } from '../../../ui-utils'; import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext } from '../../../utils'; import { hasPermission, hasAtLeastOnePermission } from '../../../authorization'; @@ -93,11 +93,12 @@ export class DirectMessageRoomType extends RoomTypeConfig { } getUserStatusText(roomId) { - const userId = roomId.replace(Meteor.userId(), ''); - const userData = Users.findOne({ _id: userId }); - if (userData && userData.statusText) { - return userData.statusText; + const subscription = Subscriptions.findOne({ rid: roomId }); + if (subscription == null) { + return; } + + return Session.get(`user_${ subscription.name }_status_text`); } getDisplayName(room) { diff --git a/app/lib/server/functions/getStatusText.js b/app/lib/server/functions/getStatusText.js new file mode 100644 index 000000000000..6695ecd92f93 --- /dev/null +++ b/app/lib/server/functions/getStatusText.js @@ -0,0 +1,19 @@ +import { Users } from '../../../models/server'; + +export const getStatusText = function(userId) { + if (!userId) { + return undefined; + } + + const fields = { + statusText: 1, + }; + + const options = { + fields, + limit: 1, + }; + + const data = Users.findOneById(userId, options); + return data && data.statusText; +}; diff --git a/app/lib/server/functions/index.js b/app/lib/server/functions/index.js index b5a307a4e148..7a94e02f36fc 100644 --- a/app/lib/server/functions/index.js +++ b/app/lib/server/functions/index.js @@ -23,7 +23,8 @@ export { saveUser } from './saveUser'; export { sendMessage } from './sendMessage'; export { setEmail } from './setEmail'; export { setRealName, _setRealName } from './setRealName'; -export { setStatusMessage, _setStatusMessage } from './setStatusMessage'; +export { setStatusText, _setStatusText } from './setStatusText'; +export { getStatusText } from './getStatusText'; export { setUserAvatar } from './setUserAvatar'; export { _setUsername, setUsername } from './setUsername'; export { unarchiveRoom } from './unarchiveRoom'; diff --git a/app/lib/server/functions/saveUser.js b/app/lib/server/functions/saveUser.js index ea9c8772b6bc..081dc7755996 100644 --- a/app/lib/server/functions/saveUser.js +++ b/app/lib/server/functions/saveUser.js @@ -10,7 +10,7 @@ import { settings } from '../../../settings'; import PasswordPolicy from '../lib/PasswordPolicyClass'; import { validateEmailDomain } from '../lib'; -import { checkEmailAvailability, checkUsernameAvailability, setUserAvatar, setEmail, setRealName, setUsername, setStatusMessage } from '.'; +import { checkEmailAvailability, checkUsernameAvailability, setUserAvatar, setEmail, setRealName, setUsername, setStatusText } from '.'; const passwordPolicy = new PasswordPolicy(); @@ -256,7 +256,7 @@ export const saveUser = function(userId, userData) { } if (typeof userData.statusText === 'string') { - setStatusMessage(userData._id, userData.statusText); + setStatusText(userData._id, userData.statusText); } if (userData.email) { diff --git a/app/lib/server/functions/setStatusMessage.js b/app/lib/server/functions/setStatusMessage.js deleted file mode 100644 index ceae68be66f8..000000000000 --- a/app/lib/server/functions/setStatusMessage.js +++ /dev/null @@ -1,45 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import s from 'underscore.string'; - -import { Users } from '../../../models'; -import { Notifications } from '../../../notifications'; -import { hasPermission } from '../../../authorization'; -import { RateLimiter } from '../lib'; - -export const _setStatusMessage = function(userId, statusMessage) { - statusMessage = s.trim(statusMessage); - if (statusMessage.length > 120) { - statusMessage = statusMessage.substr(0, 120); - } - - if (!userId) { - return false; - } - - const user = Users.findOneById(userId); - - // User already has desired statusMessage, return - if (user.statusText === statusMessage) { - return user; - } - - // Set new statusMessage - Users.updateStatusText(user._id, statusMessage); - user.statusText = statusMessage; - - Notifications.notifyLogged('Users:StatusMessageChanged', { - _id: user._id, - name: user.name, - username: user.username, - statusText: user.statusText, - }); - - return true; -}; - -export const setStatusMessage = RateLimiter.limitFunction(_setStatusMessage, 1, 60000, { - 0() { - // Administrators have permission to change others status, so don't limit those - return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); - }, -}); diff --git a/app/lib/server/functions/setStatusText.js b/app/lib/server/functions/setStatusText.js new file mode 100644 index 000000000000..c852f21a6ab0 --- /dev/null +++ b/app/lib/server/functions/setStatusText.js @@ -0,0 +1,53 @@ +import { Meteor } from 'meteor/meteor'; +import s from 'underscore.string'; + +import { Users } from '../../../models'; +import { Notifications } from '../../../notifications'; +import { hasPermission } from '../../../authorization'; +import { RateLimiter } from '../lib'; + +// mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated +const STATUS_MAP = { + offline: 0, + online: 1, + away: 2, + busy: 3, +}; + +export const _setStatusText = function(userId, statusText) { + statusText = s.trim(statusText); + if (statusText.length > 120) { + statusText = statusText.substr(0, 120); + } + + if (!userId) { + return false; + } + + const user = Users.findOneById(userId); + + // User already has desired statusText, return + if (user.statusText === statusText) { + return user; + } + + // Set new statusText + Users.updateStatusText(user._id, statusText); + user.statusText = statusText; + + Notifications.notifyLogged('user-status', [ + user._id, + user.username, + STATUS_MAP[user.status], + statusText, + ]); + + return true; +}; + +export const setStatusText = RateLimiter.limitFunction(_setStatusText, 1, 60000, { + 0() { + // Administrators have permission to change others status, so don't limit those + return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); + }, +}); diff --git a/app/slashcommands-status/lib/status.js b/app/slashcommands-status/lib/status.js index 8188ed41a598..a03a9e516117 100644 --- a/app/slashcommands-status/lib/status.js +++ b/app/slashcommands-status/lib/status.js @@ -3,40 +3,37 @@ import { TAPi18n } from 'meteor/tap:i18n'; import { Random } from 'meteor/random'; import { handleError, slashCommands } from '../../utils'; -import { hasPermission } from '../../authorization'; import { Notifications } from '../../notifications'; function Status(command, params, item) { if (command === 'status') { - if ((Meteor.isClient && hasPermission('edit-other-user-info')) || (Meteor.isServer && hasPermission(Meteor.userId(), 'edit-other-user-info'))) { - const user = Meteor.users.findOne(Meteor.userId()); + const user = Meteor.users.findOne(Meteor.userId()); - Meteor.call('setUserStatus', null, params, (err) => { - if (err) { - if (Meteor.isClient) { - return handleError(err); - } - - if (err.error === 'error-not-allowed') { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), - msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language), - }); - } + Meteor.call('setUserStatus', null, params, (err) => { + if (err) { + if (Meteor.isClient) { + return handleError(err); + } - throw err; - } else { + if (err.error === 'error-not-allowed') { Notifications.notifyUser(Meteor.userId(), 'message', { _id: Random.id(), rid: item.rid, ts: new Date(), - msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language), + msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language), }); } - }); - } + + throw err; + } else { + Notifications.notifyUser(Meteor.userId(), 'message', { + _id: Random.id(), + rid: item.rid, + ts: new Date(), + msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language), + }); + } + }); } } diff --git a/app/ui-flextab/client/tabs/userInfo.js b/app/ui-flextab/client/tabs/userInfo.js index 68a68e265cf4..1ee515ef3fe8 100644 --- a/app/ui-flextab/client/tabs/userInfo.js +++ b/app/ui-flextab/client/tabs/userInfo.js @@ -8,7 +8,7 @@ import moment from 'moment'; import { DateFormat } from '../../../lib'; import { popover } from '../../../ui-utils'; -import { t, templateVarHandler } from '../../../utils'; +import { templateVarHandler } from '../../../utils'; import { RoomRoles, UserRoles, Roles } from '../../../models'; import { settings } from '../../../settings'; import FullUser from '../../../models/client/models/FullUser'; @@ -83,7 +83,7 @@ Template.userInfo.helpers({ userStatus() { const user = Template.instance().user.get(); const userStatus = Session.get(`user_${ user.username }_status`); - return userStatus || 'offline'; + return userStatus || TAPi18n.__('offline'); }, userStatusText() { @@ -92,7 +92,8 @@ Template.userInfo.helpers({ } const user = Template.instance().user.get(); - return t(Session.get(`user_${ user.username }_status`)); + const userStatus = Session.get(`user_${ user.username }_status`); + return userStatus || TAPi18n.__('offline'); }, email() { diff --git a/app/ui-sidenav/client/sidebarHeader.js b/app/ui-sidenav/client/sidebarHeader.js index ea81eb21761b..7fc2eb4a343f 100644 --- a/app/ui-sidenav/client/sidebarHeader.js +++ b/app/ui-sidenav/client/sidebarHeader.js @@ -295,7 +295,7 @@ Template.sidebarHeader.helpers({ }; } return id && Meteor.users.findOne(id, { fields: { - username: 1, status: 1, + username: 1, status: 1, statusText: 1, } }); }, toolbarButtons() { @@ -316,18 +316,24 @@ Template.sidebarHeader.events({ 'click .sidebar__header .avatar'(e) { if (!(Meteor.userId() == null && settings.get('Accounts_AllowAnonymousRead'))) { const user = Meteor.user(); - + const STATUS_MAP = [ + 'offline', + 'online', + 'away', + 'busy', + ]; const userStatusList = Object.keys(userStatus.list).map((key) => { const status = userStatus.list[key]; - const customName = status.localizeName ? null : status.name; const name = status.localizeName ? t(status.name) : status.name; const modifier = status.statusType || user.status; + const defaultStatus = STATUS_MAP.includes(status.id); + const statusText = defaultStatus ? null : name; return { icon: 'circle', name, modifier, - action: () => setStatus(status.statusType, customName), + action: () => setStatus(status.statusType, statusText), }; }); diff --git a/app/ui/client/components/header/headerRoom.js b/app/ui/client/components/header/headerRoom.js index 7e78dae45b8b..1bfe1ef5f104 100644 --- a/app/ui/client/components/header/headerRoom.js +++ b/app/ui/client/components/header/headerRoom.js @@ -4,7 +4,6 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Session } from 'meteor/session'; import { Template } from 'meteor/templating'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import s from 'underscore.string'; import { t, roomTypes, handleError } from '../../../../utils'; import { TabBar, fireGlobalEvent, call } from '../../../../ui-utils'; @@ -25,7 +24,12 @@ const isDiscussion = ({ _id }) => { const getUserStatus = (id) => { const roomData = Session.get(`roomData${ id }`); - return roomTypes.getUserStatus(roomData.t, id) || 'offline'; + return roomTypes.getUserStatus(roomData.t, id); +}; + +const getUserStatusText = (id) => { + const roomData = Session.get(`roomData${ id }`); + return roomTypes.getUserStatusText(roomData.t, id); }; Template.headerRoom.helpers({ @@ -111,18 +115,26 @@ Template.headerRoom.helpers({ }, userStatus() { - return getUserStatus(this._id); + return getUserStatus(this._id) || 'offline'; }, userStatusText() { - const roomData = Session.get(`roomData${ this._id }`); - const statusText = roomTypes.getUserStatusText(roomData.t, this._id); - - if (s.trim(statusText)) { + const statusText = getUserStatusText(this._id); + if (statusText) { return statusText; } - return t(getUserStatus(this._id)); + const presence = getUserStatus(this._id); + if (presence) { + return t(presence); + } + + const oldStatusText = Template.instance().userOldStatusText.get(); + if (oldStatusText) { + return oldStatusText; + } + + return t('offline'); }, showToggleFavorite() { @@ -183,10 +195,39 @@ Template.headerRoom.events({ }, }); +const loadUserStatusText = () => { + const instance = Template.instance(); + + if (!instance || !instance.data || !instance.data._id) { + return; + } + + const id = instance.data._id; + + if (Rooms.findOne(id).t !== 'd') { + return; + } + + const userId = id.replace(Meteor.userId(), ''); + + // If the user is already on the local collection, the method call is not necessary + const found = Meteor.users.findOne(userId, { fields: { _id: 1 } }); + if (found) { + return; + } + + Meteor.call('getUserStatusText', userId, (error, result) => { + if (!error) { + instance.userOldStatusText.set(result); + } + }); +}; + Template.headerRoom.onCreated(function() { this.currentChannel = (this.data && this.data._id && Rooms.findOne(this.data._id)) || undefined; this.hasTokenpass = new ReactiveVar(false); + this.userOldStatusText = new ReactiveVar(null); if (settings.get('API_Tokenpass_URL') !== '') { Meteor.call('getChannelTokenpass', this.data._id, (error, result) => { @@ -195,4 +236,6 @@ Template.headerRoom.onCreated(function() { } }); } + + loadUserStatusText(); }); diff --git a/app/user-status/client/admin/userStatusEdit.html b/app/user-status/client/admin/userStatusEdit.html index d0ac4103511b..70a4ff47cd28 100644 --- a/app/user-status/client/admin/userStatusEdit.html +++ b/app/user-status/client/admin/userStatusEdit.html @@ -13,7 +13,7 @@

{{_ "Custom_User_Status_Add"}}

@@ -22,7 +22,7 @@

{{_ "Custom_User_Status_Add"}}