diff --git a/website/client/src/assets/scss/form.scss b/website/client/src/assets/scss/form.scss index 2e5934f5d5..edfb99245e 100644 --- a/website/client/src/assets/scss/form.scss +++ b/website/client/src/assets/scss/form.scss @@ -23,7 +23,7 @@ input, textarea, input.form-control, textarea.form-control { border-radius: 2px; font-size: 14px; line-height: 1.43; - color: $gray-200; + color: $gray-50; border: 1px solid $gray-400; &:hover:not(:disabled) { @@ -32,7 +32,6 @@ input, textarea, input.form-control, textarea.form-control { &:active:not(:disabled), &:focus:not(:disabled) { border-color: $purple-500; - color: $gray-50; outline: 0; box-shadow: none; } diff --git a/website/client/src/components/header/userDropdown.vue b/website/client/src/components/header/userDropdown.vue index fc0ae7edb5..093868c844 100644 --- a/website/client/src/components/header/userDropdown.vue +++ b/website/client/src/components/header/userDropdown.vue @@ -133,13 +133,12 @@ diff --git a/website/client/src/store/actions/user.js b/website/client/src/store/actions/user.js index 3a98137fed..fa1de5dcc7 100644 --- a/website/client/src/store/actions/user.js +++ b/website/client/src/store/actions/user.js @@ -6,6 +6,7 @@ import { togglePinnedItem as togglePinnedItemOp } from '@/../../common/script/op import changeClassOp from '@/../../common/script/ops/changeClass'; import disableClassesOp from '@/../../common/script/ops/disableClasses'; import openMysteryItemOp from '@/../../common/script/ops/openMysteryItem'; +import markPMSRead from '../../../../common/script/ops/markPMSRead'; export function fetch (store, options = {}) { // eslint-disable-line no-shadow return loadAsyncResource({ @@ -172,6 +173,11 @@ export function unblock (store, params) { return axios.post(`/api/v4/user/block/${params.uuid}`); } +export function markPrivMessagesRead (store) { + markPMSRead(store.state.user.data); + return axios.post('/api/v4/user/mark-pms-read'); +} + export function newPrivateMessageTo (store, params) { const { member } = params; diff --git a/website/common/locales/en/generic.json b/website/common/locales/en/generic.json index 0a10811c2a..ac5b9c5555 100644 --- a/website/common/locales/en/generic.json +++ b/website/common/locales/en/generic.json @@ -296,7 +296,7 @@ "dismissAll": "Dismiss All", "messages": "Messages", "emptyMessagesLine1": "You don't have any messages", - "emptyMessagesLine2": "Send a message to start a conversation!", + "emptyMessagesLine2": "You can send a new message to a user by visiting their profile and clicking the \"Message\" button.", "userSentMessage": "<%= user %> sent you a message", "letsgo": "Let's Go!", "selected": "Selected", diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index ee7280939d..31c8edff5f 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -143,6 +143,9 @@ "PMDisabledOptPopoverText": "Private Messages are disabled. Enable this option to allow users to contact you via your profile.", "PMDisabledCaptionTitle": "Private Messages are disabled", "PMDisabledCaptionText": "You can still send messages, but no one can send them to you.", + "PMCanNotReply": "You can not reply to this conversation", + "PMUserDoesNotReceiveMessages": "This user is no longer receiving private messages", + "PMUnblockUserToSendMessages": "Unblock this user to continue sending and receiving messages.", "block": "Block", "unblock": "Un-block", "blockWarning": "Block - This will have no effect if the player is a moderator now or becomes a moderator in future.", diff --git a/website/server/controllers/api-v4/inbox.js b/website/server/controllers/api-v4/inbox.js index be5cca6a4d..28843f93e9 100644 --- a/website/server/controllers/api-v4/inbox.js +++ b/website/server/controllers/api-v4/inbox.js @@ -93,6 +93,7 @@ api.clearMessages = { * "text":"last message of conversation", * "userStyles": {}, * "contributor": {}, + * "canReceive": true, * "count":1 * } * } diff --git a/website/server/libs/inbox/conversation.methods.js b/website/server/libs/inbox/conversation.methods.js index b1f20d53fe..72a50111e3 100644 --- a/website/server/libs/inbox/conversation.methods.js +++ b/website/server/libs/inbox/conversation.methods.js @@ -2,70 +2,40 @@ import { inboxModel as Inbox, setUserStyles } from '../../models/message'; import { model as User } from '../../models/user'; /** - * Get the users for conversations - * 1. Get the user data of last sent message by conversation - * 2. If the target user hasn't replied yet ( 'sent:true' ) , list user data by users directly - * @param owner + * Get the current user (avatar/setting etc) for conversations * @param users * @returns {Promise} */ -async function usersMapByConversations (owner, users) { - const query = Inbox - .aggregate([ - { - $match: { - ownerId: owner._id, - uuid: { $in: users }, - sent: false, // only messages the other user sent to you - }, - }, - { - $group: { - _id: '$uuid', - userStyles: { $last: '$userStyles' }, - contributor: { $last: '$contributor' }, - backer: { $last: '$backer' }, - }, - }, - ]); - - - const usersAr = await query.exec(); +async function usersMapByConversations (users) { const usersMap = {}; - for (const usr of usersAr) { - usersMap[usr._id] = usr; - } + const usersQuery = { + _id: { $in: users }, + }; - // if a conversation doesn't have a response of the chat-partner, - // those won't be listed by the query above - const usersStillNeedToBeLoaded = users.filter(userId => !usersMap[userId]); + const loadedUsers = await User.find(usersQuery, { + _id: 1, + contributor: 1, + backer: 1, + items: 1, + preferences: 1, + stats: 1, + flags: 1, + inbox: 1, + }).exec(); - if (usersStillNeedToBeLoaded.length > 0) { - const usersQuery = { - _id: { $in: usersStillNeedToBeLoaded }, + for (const usr of loadedUsers) { + const loadedUserConversation = { + _id: usr._id, + backer: usr.backer, + contributor: usr.contributor, + optOut: usr.inbox.optOut, + blocks: usr.inbox.blocks || [], }; + // map user values to conversation properties + setUserStyles(loadedUserConversation, usr); - const loadedUsers = await User.find(usersQuery, { - _id: 1, - contributor: 1, - backer: 1, - items: 1, - preferences: 1, - stats: 1, - }).exec(); - - for (const usr of loadedUsers) { - const loadedUserConversation = { - _id: usr._id, - backer: usr.backer, - contributor: usr.contributor, - }; - // map user values to conversation properties - setUserStyles(loadedUserConversation, usr); - - usersMap[usr._id] = loadedUserConversation; - } + usersMap[usr._id] = loadedUserConversation; } return usersMap; @@ -98,7 +68,7 @@ export async function listConversations (owner) { const userIdList = conversationsList.map(c => c._id); // get user-info based on conversations - const usersMap = await usersMapByConversations(owner, userIdList); + const usersMap = await usersMapByConversations(userIdList); const conversations = conversationsList.map(res => { const uuid = res._id; @@ -109,9 +79,15 @@ export async function listConversations (owner) { }; if (usersMap[uuid]) { - conversation.userStyles = usersMap[uuid].userStyles; - conversation.contributor = usersMap[uuid].contributor; - conversation.backer = usersMap[uuid].backer; + const user = usersMap[uuid]; + + conversation.userStyles = user.userStyles; + conversation.contributor = user.contributor; + conversation.backer = user.backer; + + const isOwnerBlocked = user.blocks.includes(owner._id); + + conversation.canReceive = !(user.optOut || isOwnerBlocked) || owner.isAdmin(); } return conversation;