,
" and ",
@@ -891,22 +876,19 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
className="AlertBanner__message"
>
You can add
-
-
-
-
,
-
,
-
-
-
@@ -159,7 +159,7 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, privat
exports[`components/post_view/PostAddChannelMember should match snapshot, public channel 1`] = `
-
@@ -188,7 +188,7 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, public
exports[`components/post_view/PostAddChannelMember should match snapshot, with no-groups usernames 1`] = `
-
@@ -212,7 +212,7 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, with n
/>
-
diff --git a/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx b/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx
index 955894332b..5927676118 100644
--- a/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx
+++ b/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx
@@ -111,8 +111,8 @@ export default class PostAddChannelMember extends React.PureComponent
-
@joram
-
+
@@ -26,14 +26,14 @@ exports[`messageHtmlToComponent At mention 2`] = `
-
@joram
-
+
`;
@@ -43,14 +43,14 @@ exports[`messageHtmlToComponent At mention with group highlight disabled 1`] = `
-
@developers
-
+
`;
@@ -60,14 +60,14 @@ exports[`messageHtmlToComponent At mention with group highlight disabled 2`] = `
-
@developers
-
+
`;
diff --git a/webapp/channels/src/utils/position_utils.test.tsx b/webapp/channels/src/utils/position_utils.test.tsx
index 70d4c44741..b60c2b316a 100644
--- a/webapp/channels/src/utils/position_utils.test.tsx
+++ b/webapp/channels/src/utils/position_utils.test.tsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import {popOverOverlayPosition} from 'utils/position_utils';
+import {popOverOverlayPosition, approxGroupPopOverHeight} from 'utils/position_utils';
test('Should return placement position for overlay based on bounds, space required and innerHeight', () => {
const targetBounds = {
@@ -14,3 +14,28 @@ test('Should return placement position for overlay based on bounds, space requir
expect(popOverOverlayPosition(targetBounds as DOMRect, 1000, 450)).toEqual('bottom');
expect(popOverOverlayPosition(targetBounds as DOMRect, 1000, 600)).toEqual('left');
});
+
+test('Should return the correct height for the group list overlay bounded by viewport height or max list height', () => {
+ // constants. should not need to change
+ const viewportScaleFactor = 0.4;
+ const headerHeight = 130;
+ const maxListHeight = 800;
+
+ // array of [listHeight, viewPortHeight, expected]
+ // tests for cases when
+ // group list fits
+ // group list is too tall for viewport
+ // group list reaches max list height
+ const testCases = [[100, 1000, 230], [500, 500, 330], [800, 2000, maxListHeight]];
+
+ for (const [listHeight, viewPortHeight, expected] of testCases) {
+ expect(
+ approxGroupPopOverHeight(
+ listHeight,
+ viewPortHeight,
+ viewportScaleFactor,
+ headerHeight,
+ maxListHeight,
+ )).toBe(expected);
+ }
+});
diff --git a/webapp/channels/src/utils/position_utils.tsx b/webapp/channels/src/utils/position_utils.tsx
index e015ea6462..5a6223b10d 100644
--- a/webapp/channels/src/utils/position_utils.tsx
+++ b/webapp/channels/src/utils/position_utils.tsx
@@ -19,3 +19,17 @@ export function popOverOverlayPosition(
}
return placement;
}
+
+export function approxGroupPopOverHeight(
+ groupListHeight: number,
+ viewPortHeight: number,
+ viewportScaleFactor: number,
+ headerHeight: number,
+ maxListHeight: number,
+): number {
+ return Math.min(
+ (viewPortHeight * viewportScaleFactor) + headerHeight,
+ groupListHeight + headerHeight,
+ maxListHeight,
+ );
+}
diff --git a/webapp/channels/src/utils/post_utils.test.tsx b/webapp/channels/src/utils/post_utils.test.tsx
index 1b0c433eac..47c236e98a 100644
--- a/webapp/channels/src/utils/post_utils.test.tsx
+++ b/webapp/channels/src/utils/post_utils.test.tsx
@@ -1118,3 +1118,61 @@ describe('PostUtils.getPostURL', () => {
expect(PostUtils.getPostURL(state, postCase)).toBe(expected);
});
});
+
+describe('PostUtils.getMentionDetails', () => {
+ const user1 = TestHelper.getUserMock({username: 'user1'});
+ const user2 = TestHelper.getUserMock({username: 'user2'});
+ const users = {user1, user2};
+
+ test.each([
+ ['user1 data from mention', 'user1', user1],
+ ['user2 data from mention', 'user2', user2],
+ ['user1 data from mention with punctution', 'user1.', user1],
+ ['blank string when no matching user', 'user3', undefined],
+ ])('should return %s', (description, mention, expected) => {
+ expect(PostUtils.getMentionDetails(users, mention)).toEqual(expected);
+ });
+
+ const group1 = TestHelper.getGroupMock({name: 'group1'});
+ const group2 = TestHelper.getGroupMock({name: 'group2'});
+ const groups = {group1, group2};
+
+ test.each([
+ ['group1 data from mention', 'group1', group1],
+ ['group2 data from mention', 'group2', group2],
+ ['group1 data from mention with punctuation', 'group2.', group2],
+ ['blank string when no matching group', 'group3', undefined],
+ ])('shoud return %s', (description, mention, expected) => {
+ expect(PostUtils.getMentionDetails(groups, mention)).toEqual(expected);
+ });
+});
+
+describe('PostUtils.getUserOrGroupFromMentionName', () => {
+ const userMention = 'user1';
+ const groupMention = 'group1';
+ const userAndGroupMention = 'user2';
+ const user1 = TestHelper.getUserMock({username: 'user1'});
+ const user2 = TestHelper.getUserMock({username: 'user2'});
+ const users = {user1, user2};
+ const group1 = TestHelper.getGroupMock({name: 'group1'});
+ const group2 = TestHelper.getGroupMock({name: 'user2'});
+ const groups = {group1, user2: group2};
+
+ test.each([
+ ['the found user', userMention, false, [user1, undefined]],
+ ['nothing when not matching user or group', 'user3', false, [undefined, undefined]],
+ ['the found group', groupMention, false, [undefined, group1]],
+ ['no group when groups highlights are disabled', groupMention, true, [undefined, undefined]],
+ ['user when there is a matching user and group mention', userAndGroupMention, false, [user2, undefined]],
+ ])('should return %s', (description, mention, disabledGroups, expected) => {
+ const result = PostUtils.getUserOrGroupFromMentionName(
+ mention,
+ users,
+ groups,
+ disabledGroups,
+ (usersOrGroups, mention) => usersOrGroups[mention],
+ );
+
+ expect(result).toEqual(expected);
+ });
+});
diff --git a/webapp/channels/src/utils/post_utils.ts b/webapp/channels/src/utils/post_utils.ts
index 9b61543c72..1fb1005daf 100644
--- a/webapp/channels/src/utils/post_utils.ts
+++ b/webapp/channels/src/utils/post_utils.ts
@@ -434,7 +434,7 @@ export function makeGetMentionsFromMessage(): (state: GlobalState, post: Post) =
const mentionsArray = post.message.match(Constants.MENTIONS_REGEX) || [];
for (let i = 0; i < mentionsArray.length; i++) {
const mention = mentionsArray[i];
- const user = getUserOrGroupFromMentionName(users, mention.substring(1)) as UserProfile | '';
+ const user = getMentionDetails(users, mention.substring(1)) as UserProfile | '';
if (user) {
mentions[mention] = user;
@@ -704,7 +704,7 @@ export function makeGetUniqueReactionsToPost(): (state: GlobalState, postId: Pos
);
}
-export function getUserOrGroupFromMentionName(usersByUsername: Record, mentionName: string) {
+export function getMentionDetails(usersByUsername: Record, mentionName: string): UserProfile | Group | undefined {
let mentionNameToLowerCase = mentionName.toLowerCase();
while (mentionNameToLowerCase.length > 0) {
@@ -720,7 +720,29 @@ export function getUserOrGroupFromMentionName(usersByUsername: Record,
+ groups: Record,
+ groupsDisabled?: boolean,
+ getMention = getMentionDetails,
+): [UserProfile?, Group?] {
+ const user = getMention(users, mentionName) as UserProfile | undefined;
+
+ // prioritizes user if user exists with the same name as a group.
+ if (!user && !groupsDisabled) {
+ const group = getMention(groups, mentionName) as Group | undefined;
+ if (group && !group.allow_reference) {
+ return [undefined, undefined]; // remove group mention if not allowed to reference
+ }
+
+ return [undefined, group];
+ }
+
+ return [user, undefined];
}
export function mentionsMinusSpecialMentionsInText(message: string) {
@@ -736,10 +758,6 @@ export function mentionsMinusSpecialMentionsInText(message: string) {
return mentions;
}
-function isUserProfile(entity: UserProfile | Group): entity is UserProfile {
- return (entity as UserProfile).username !== undefined;
-}
-
export function makeGetUserOrGroupMentionCountFromMessage(): (state: GlobalState, message: Post['message']) => number {
return createSelector(
'getUserOrGroupMentionCountFromMessage',
@@ -751,15 +769,12 @@ export function makeGetUserOrGroupMentionCountFromMessage(): (state: GlobalState
const markdownCleanedText = formatWithRenderer(message, new MentionableRenderer());
const mentions = new Set(markdownCleanedText.match(Constants.MENTIONS_REGEX) || []);
mentions.forEach((mention) => {
- const data = {...groups, ...users};
- const userOrGroup = getUserOrGroupFromMentionName(data, mention.substring(1));
+ const [user, group] = getUserOrGroupFromMentionName(mention.substring(1), users, groups);
- if (userOrGroup) {
- if (isUserProfile(userOrGroup)) {
- count++;
- } else {
- count += userOrGroup.member_count;
- }
+ if (user) {
+ count++;
+ } else if (group) {
+ count += group.member_count;
}
});
return count;