diff --git a/test/api/unit/libs/slack.js b/test/api/unit/libs/slack.js
index 3252ed7e16..4b6acdf695 100644
--- a/test/api/unit/libs/slack.js
+++ b/test/api/unit/libs/slack.js
@@ -32,6 +32,7 @@ describe('slack', () => {
},
message: {
id: 'chat-id',
+ username: 'author',
user: 'Author',
uuid: 'author-id',
text: 'some text',
@@ -50,11 +51,11 @@ describe('slack', () => {
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
expect(IncomingWebhook.prototype.send).to.be.calledWith({
- text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
+ text: 'flagger (flagger-id; language: flagger-lang) flagged a group message',
attachments: [{
fallback: 'Flag Message',
color: 'danger',
- author_name: `Author - author@example.com - author-id\n${timestamp}`,
+ author_name: `@author Author (author@example.com; author-id)\n${timestamp}`,
title: 'Flag in Some group - (private guild)',
title_link: undefined,
text: 'some text',
diff --git a/test/api/v3/integration/chat/POST-chat.flag.test.js b/test/api/v3/integration/chat/POST-chat.flag.test.js
index 88b41744c8..723715c80b 100644
--- a/test/api/v3/integration/chat/POST-chat.flag.test.js
+++ b/test/api/v3/integration/chat/POST-chat.flag.test.js
@@ -63,11 +63,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
- text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
+ text: `${user.profile.name} (${user.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
- author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
+ author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
@@ -98,11 +98,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
- text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
+ text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
- author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
+ author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js
index bd0f158b7d..aa6b8c8806 100644
--- a/test/api/v3/integration/chat/POST-chat.test.js
+++ b/test/api/v3/integration/chat/POST-chat.test.js
@@ -257,7 +257,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
- author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
+ author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
title: 'Slur in Test Guild',
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
text: testSlurMessage,
@@ -310,7 +310,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
- author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
+ author_name: `@${members[0].auth.local.username} ${members[0].profile.name} (${members[0].auth.local.email}; ${members[0]._id})`,
title: 'Slur in Party - (private party)',
title_link: undefined,
text: testSlurMessage,
diff --git a/test/api/v4/members/POST-flag_private_message.test.js b/test/api/v4/members/POST-flag_private_message.test.js
new file mode 100644
index 0000000000..dbeb9b5e18
--- /dev/null
+++ b/test/api/v4/members/POST-flag_private_message.test.js
@@ -0,0 +1,74 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../helpers/api-integration/v4';
+
+describe('POST /members/flag-private-message/:messageId', () => {
+ let userToSendMessage;
+ let messageToSend = 'Test Private Message';
+
+ beforeEach(async () => {
+ userToSendMessage = await generateUser();
+ });
+
+ it('Allows players to flag their own private message', async () => {
+ let receiver = await generateUser();
+
+ await userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: receiver._id,
+ });
+
+ let senderMessages = await userToSendMessage.get('/inbox/messages');
+
+ let sendersMessageInSendersInbox = _.find(senderMessages, (message) => {
+ return message.uuid === receiver._id && message.text === messageToSend;
+ });
+
+ expect(sendersMessageInSendersInbox).to.exist;
+ await expect(userToSendMessage.post(`/members/flag-private-message/${sendersMessageInSendersInbox.id}`)).to.eventually.be.ok;
+ });
+
+ it('Flags a private message', async () => {
+ let receiver = await generateUser();
+
+ await userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: receiver._id,
+ });
+
+ let receiversMessages = await receiver.get('/inbox/messages');
+
+ let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
+ return message.uuid === userToSendMessage._id && message.text === messageToSend;
+ });
+
+ expect(sendersMessageInReceiversInbox).to.exist;
+ await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
+ });
+
+ it('Returns an error when user tries to flag a private message that is already flagged', async () => {
+ let receiver = await generateUser();
+
+ await userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: receiver._id,
+ });
+
+ let receiversMessages = await receiver.get('/inbox/messages');
+
+ let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
+ return message.uuid === userToSendMessage._id && message.text === messageToSend;
+ });
+
+ expect(sendersMessageInReceiversInbox).to.exist;
+ await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
+
+ await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('messageGroupChatFlagAlreadyReported'),
+ });
+ });
+});
diff --git a/website/client/components/chat/chatCard.vue b/website/client/components/chat/chatCard.vue
index 362a839f75..3a35fa1ace 100644
--- a/website/client/components/chat/chatCard.vue
+++ b/website/client/components/chat/chatCard.vue
@@ -1,8 +1,8 @@
div
.mentioned-icon(v-if='isUserMentioned')
- .message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
- .message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
+ .message-hidden(v-if='!inbox && msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
+ .message-hidden(v-if='!inbox && msg.flagCount > 1 && user.contributor.admin') Message hidden
.card-body
user-link(:userId="msg.uuid", :name="msg.user", :backer="msg.backer", :contributor="msg.contributor")
p.time
@@ -11,25 +11,28 @@ div
span(v-b-tooltip="", :title="msg.timestamp | date") {{ msg.timestamp | timeAgo }}
span(v-if="msg.client && user.contributor.level >= 4") ({{ msg.client }})
.text(v-html='atHighlight(parseMarkdown(msg.text))')
+ .reported(v-if="isMessageReported && (inbox === true)")
+ span(v-once) {{ $t('reportedMessage')}}
+ br
+ span(v-once) {{ $t('canDeleteNow') }}
hr
.d-flex(v-if='msg.id')
.action.d-flex.align-items-center(v-if='!inbox', @click='copyAsTodo(msg)')
.svg-icon(v-html="icons.copy")
div {{$t('copyAsTodo')}}
- .action.d-flex.align-items-center(v-if='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @click='report(msg)')
- .svg-icon(v-html="icons.report")
- div {{$t('report')}}
- // @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
+ .action.d-flex.align-items-center(v-if='(inbox || (user.flags.communityGuidelinesAccepted && msg.uuid !== "system")) && !isMessageReported', @click='report(msg)')
+ .svg-icon(v-html="icons.report", v-once)
+ div(v-once) {{$t('report')}}
.action.d-flex.align-items-center(v-if='msg.uuid === user._id || inbox || user.contributor.admin', @click='remove()')
- .svg-icon(v-html="icons.delete")
- | {{$t('delete')}}
+ .svg-icon(v-html="icons.delete", v-once)
+ div(v-once) {{$t('delete')}}
.ml-auto.d-flex(v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}", v-if='!inbox')
.action.d-flex.align-items-center.mr-0(@click='like()', v-if='likeCount > 0', :class='{active: msg.likes[user._id]}')
.svg-icon(v-html="icons.liked", :title='$t("liked")')
| +{{ likeCount }}
.action.d-flex.align-items-center.mr-0(@click='like()', v-if='likeCount === 0', :class='{active: msg.likes[user._id]}')
.svg-icon(v-html="icons.like", :title='$t("like")')
- span(v-if='!msg.likes[user._id]') {{ $t('like') }}
+ span(v-if='!msg.likes[user._id] && !inbox') {{ $t('like') }}