[MM-11274 & MM-10797] Add profile pop-up to combined system messages and fix it's translation issue (#1926)

* add profile pop-up to combined system messages and fix it's translation issue

* Pushing userlink changes

* add spacing

* fix styling and spacing issue. also removed tests due to transpiling issue

* update per comment
This commit is contained in:
Saturnino Abril
2018-07-20 15:53:23 +08:00
committed by Elias Nahum
parent 44152ad5e5
commit c714072b9b
11 changed files with 275 additions and 636 deletions

View File

@@ -8,6 +8,8 @@ import {intlShape} from 'react-intl';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import {emptyFunction} from 'app/utils/general';
import CustomPropTypes from 'app/constants/custom_prop_types';
import mattermostManaged from 'app/mattermost_managed';
@@ -25,6 +27,10 @@ export default class AtMention extends React.PureComponent {
usersByUsername: PropTypes.object.isRequired,
};
static defaultProps = {
onLongPress: emptyFunction,
};
static contextTypes = {
intl: intlShape,
};

View File

@@ -1,89 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CombinedSystemMessage should match snapshot 1`] = `
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"opacity": 0.6,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="{firstUser} "
id="mobile.combined_system_message.first_user"
values={
Object {
"firstUser": "@user1",
"secondUser": null,
}
}
/>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"fontSize": 14,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="added to the team"
id="mobile.combined_system_message.added_to_team"
/>
</Text>
<InjectIntl(FormattedText)
defaultMessage=" by {actor}."
id="mobile.combined_system_message.by_actor"
values={
Object {
"actor": "@user2",
}
}
/>
</Text>
`;
exports[`CombinedSystemMessage should match snapshot 2`] = `
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"opacity": 0.6,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="{firstUser} and {secondUser} were "
id="combined_system_message.first_user_and_second_user_were"
values={
Object {
"firstUser": "first_user",
"secondUser": "second_user",
}
}
/>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"fontSize": 14,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="removed from the team"
id="combined_system_message.removed_from_team"
/>
</Text>
.
</Text>
`;

View File

@@ -1,117 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LastUsers should match snapshot 1`] = `
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"opacity": 0.6,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="E"
id="e"
values={
Object {
"lastUser": "User Two",
"users": "User One",
}
}
/>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"fontSize": 14,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="x"
id="x"
/>
</Text>
<InjectIntl(FormattedText)
defaultMessage="p"
id="p"
values={
Object {
"actor": "actor",
}
}
/>
</Text>
`;
exports[`LastUsers should match snapshot 2`] = `
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"opacity": 0.6,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="{firstUser} and "
id="mobile.combined_system_message.first_user_and"
values={
Object {
"firstUser": "User One",
}
}
/>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
onPress={[Function]}
style={1}
>
<InjectIntl(FormattedText)
defaultMessage="{numOthers} others "
id="mobile.combined_system_message.others"
values={
Object {
"numOthers": 1,
}
}
/>
</Text>
<InjectIntl(FormattedText)
defaultMessage="were "
id="mobile.combined_system_message.were"
/>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"fontSize": 14,
}
}
>
<InjectIntl(FormattedText)
defaultMessage="added to the team"
id="mobile.combined_system_message.added_to_team"
/>
</Text>
<InjectIntl(FormattedText)
defaultMessage=" by {actor}."
id="mobile.combined_system_message.by_actor"
values={
Object {
"actor": "actor",
}
}
/>
</Text>
`;

View File

@@ -3,17 +3,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import {Text} from 'react-native';
import {intlShape} from 'react-intl';
import {Posts} from 'mattermost-redux/constants';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import LastUsers from './last_users';
@@ -25,130 +21,130 @@ const {
const postTypeMessage = {
[JOIN_CHANNEL]: {
one: {
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.joined_channel'],
defaultMessage: ['{firstUser} ', 'joined the channel'],
id: 'combined_system_message.joined_channel.one',
defaultMessage: '{firstUser} **joined the channel**.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.joined_channel'],
defaultMessage: ['{firstUser} and {secondUser} ', 'joined the channel'],
id: 'combined_system_message.joined_channel.two',
defaultMessage: '{firstUser} and {secondUser} **joined the channel**.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.joined_channel'],
defaultMessage: ['{users} and {lastUser} ', 'joined the channel'],
id: 'combined_system_message.joined_channel.many_expanded',
defaultMessage: '{users} and {lastUser} **joined the channel**.',
},
},
[ADD_TO_CHANNEL]: {
one: {
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['{firstUser} ', 'added to the channel', ' by {actor}.'],
id: 'combined_system_message.added_to_channel.one',
defaultMessage: '{firstUser} **added to the channel** by {actor}.',
},
one_you: {
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['You were ', 'added to the channel', ' by {actor}.'],
id: 'combined_system_message.added_to_channel.one_you',
defaultMessage: 'You were **added to the channel** by {actor}.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['{firstUser} and {secondUser} ', 'added to the channel', ' by {actor}.'],
id: 'combined_system_message.added_to_channel.two',
defaultMessage: '{firstUser} and {secondUser} **added to the channel** by {actor}.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['{users} and {lastUser} ', 'added to the channel', ' by {actor}.'],
id: 'combined_system_message.added_to_channel.many_expanded',
defaultMessage: '{users} and {lastUser} were **added to the channel** by {actor}.',
},
},
[REMOVE_FROM_CHANNEL]: {
one: {
id: ['mobile.combined_system_message.first_user_was', 'mobile.combined_system_message.removed_from_channel'],
defaultMessage: ['{firstUser} was ', 'removed from the channel'],
id: 'combined_system_message.removed_from_channel.one',
defaultMessage: '{firstUser} was **removed from the channel**.',
},
one_you: {
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.removed_from_channel'],
defaultMessage: ['You were ', 'removed from the channel'],
id: 'combined_system_message.removed_from_channel.one_you',
defaultMessage: 'You were **removed from the channel**.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user_were', 'mobile.combined_system_message.removed_from_channel'],
defaultMessage: ['{firstUser} and {secondUser} were ', 'removed from the channel'],
id: 'combined_system_message.removed_from_channel.two',
defaultMessage: '{firstUser} and {secondUser} were **removed from the channel**.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user_were', 'mobile.combined_system_message.removed_from_channel'],
defaultMessage: ['{users} and {lastUser} were ', 'removed from the channel'],
id: 'combined_system_message.removed_from_channel.many_expanded',
defaultMessage: '{users} and {lastUser} were **removed from the channel**.',
},
},
[LEAVE_CHANNEL]: {
one: {
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.left_channel'],
defaultMessage: ['{firstUser} ', 'left the channel'],
id: 'combined_system_message.left_channel.one',
defaultMessage: '{firstUser} **left the channel**.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.left_channel'],
defaultMessage: ['{firstUser} and {secondUser} ', 'left the channel'],
id: 'combined_system_message.left_channel.two',
defaultMessage: '{firstUser} and {secondUser} **left the channel**.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.left_channel'],
defaultMessage: ['{users} and {lastUser} ', 'left the channel'],
id: 'combined_system_message.left_channel.many_expanded',
defaultMessage: '{users} and {lastUser} **left the channel**.',
},
},
[JOIN_TEAM]: {
one: {
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.joined_team'],
defaultMessage: ['{firstUser} ', 'joined the team'],
id: 'combined_system_message.joined_team.one',
defaultMessage: '{firstUser} **joined the team**.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.joined_team'],
defaultMessage: ['{firstUser} and {secondUser} ', 'joined the team'],
id: 'combined_system_message.joined_team.two',
defaultMessage: '{firstUser} and {secondUser} **joined the team**.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.joined_team'],
defaultMessage: ['{users} and {lastUser} ', 'joined the team'],
id: 'combined_system_message.joined_team.many_expanded',
defaultMessage: '{users} and {lastUser} **joined the team**.',
},
},
[ADD_TO_TEAM]: {
one: {
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['{firstUser} ', 'added to the team', ' by {actor}.'],
id: 'combined_system_message.added_to_team.one',
defaultMessage: '{firstUser} **added to the team** by {actor}.',
},
one_you: {
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['You were ', 'added to the team', ' by {actor}.'],
id: 'combined_system_message.added_to_team.one_you',
defaultMessage: 'You were **added to the team** by {actor}.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['{firstUser} and {secondUser} ', 'added to the team', ' by {actor}.'],
id: 'combined_system_message.added_to_team.two',
defaultMessage: '{firstUser} and {secondUser} **added to the team** by {actor}.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['{users} and {lastUser} ', 'added to the team', ' by {actor}.'],
id: 'combined_system_message.added_to_team.many_expanded',
defaultMessage: '{users} and {lastUser} were **added to the team** by {actor}.',
},
},
[REMOVE_FROM_TEAM]: {
one: {
id: ['mobile.combined_system_message.first_user_was', 'mobile.combined_system_message.removed_from_team'],
defaultMessage: ['{firstUser} was ', 'removed from the team'],
id: 'combined_system_message.removed_from_team.one',
defaultMessage: '{firstUser} was **removed from the team**.',
},
one_you: {
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.removed_from_team'],
defaultMessage: ['You were ', 'removed from the team'],
id: 'combined_system_message.removed_from_team.one_you',
defaultMessage: 'You were **removed from the team**.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user_were', 'mobile.combined_system_message.removed_from_team'],
defaultMessage: ['{firstUser} and {secondUser} were ', 'removed from the team'],
id: 'combined_system_message.removed_from_team.two',
defaultMessage: '{firstUser} and {secondUser} were **removed from the team**.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user_were', 'mobile.combined_system_message.removed_from_team'],
defaultMessage: ['{users} and {lastUser} were ', 'removed from the team'],
id: 'combined_system_message.removed_from_team.many_expanded',
defaultMessage: '{users} and {lastUser} were **removed from the team**.',
},
},
[LEAVE_TEAM]: {
one: {
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.left_team'],
defaultMessage: ['{firstUser} ', 'left the team'],
id: 'combined_system_message.left_team.one',
defaultMessage: '{firstUser} **left the team**.',
},
two: {
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.left_team'],
defaultMessage: ['{firstUser} and {secondUser} ', 'left the team'],
id: 'combined_system_message.left_team.two',
defaultMessage: '{firstUser} and {secondUser} **left the team**.',
},
many_expanded: {
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.left_team'],
defaultMessage: ['{users} and {lastUser} ', 'left the team'],
id: 'combined_system_message.left_team.many_expanded',
defaultMessage: '{users} and {lastUser} **left the team**.',
},
},
};
@@ -163,10 +159,10 @@ export default class CombinedSystemMessage extends React.PureComponent {
allUsernames: PropTypes.array.isRequired,
currentUserId: PropTypes.string.isRequired,
currentUsername: PropTypes.string.isRequired,
linkStyle: CustomPropTypes.Style,
messageData: PropTypes.array.isRequired,
navigator: PropTypes.object.isRequired,
showJoinLeave: PropTypes.bool.isRequired,
teammateNameDisplay: PropTypes.string.isRequired,
textStyles: PropTypes.object,
theme: PropTypes.object.isRequired,
userProfiles: PropTypes.array.isRequired,
};
@@ -184,9 +180,10 @@ export default class CombinedSystemMessage extends React.PureComponent {
this.loadUserProfiles(this.props.allUserIds, this.props.allUsernames);
}
componentWillReceiveProps(nextProps) {
if (this.props.allUserIds !== nextProps.allUserIds || this.props.allUsernames !== nextProps.allUsernames) {
this.loadUserProfiles(nextProps.allUserIds, nextProps.allUsernames);
componentDidUpdate(prevProps) {
const {allUserIds, allUsernames} = this.props;
if (allUserIds !== prevProps.allUserIds || allUsernames !== prevProps.allUsernames) {
this.loadUserProfiles(allUserIds, allUsernames);
}
}
@@ -200,146 +197,115 @@ export default class CombinedSystemMessage extends React.PureComponent {
}
}
getAllUsersDisplayName = () => {
getAllUsernames = () => {
const {
allUserIds,
allUsernames,
currentUserId,
currentUsername,
teammateNameDisplay,
userProfiles,
} = this.props;
const {formatMessage} = this.context.intl;
const usersDisplayName = userProfiles.reduce((acc, user) => {
const displayName = displayUsername(user, teammateNameDisplay, true);
acc[user.id] = displayName;
acc[user.username] = displayName;
const usernames = userProfiles.reduce((acc, user) => {
acc[user.id] = user.username;
acc[user.username] = user.username;
return acc;
}, {});
const currentUserDisplayName = formatMessage({id: 'combined_system_message.you', defaultMessage: 'You'});
if (allUserIds.includes(currentUserId)) {
usersDisplayName[currentUserId] = formatMessage({id: 'mobile.combined_system_message.you', defaultMessage: 'You'});
usernames[currentUserId] = currentUserDisplayName;
} else if (allUsernames.includes(currentUsername)) {
usersDisplayName[currentUsername] = formatMessage({id: 'mobile.combined_system_message.you', defaultMessage: 'You'});
usernames[currentUsername] = currentUserDisplayName;
}
return usersDisplayName;
return usernames;
}
getDisplayNameByIds = (userIds = []) => {
getUsernamesByIds = (userIds = []) => {
const {currentUserId, currentUsername} = this.props;
const usersDisplayName = this.getAllUsersDisplayName();
const displayNames = userIds.
const allUsernames = this.getAllUsernames();
const usernames = userIds.
filter((userId) => {
return (
usersDisplayName[userId] &&
userId !== currentUserId &&
userId !== currentUsername
);
return userId !== currentUserId && userId !== currentUsername;
}).
map((userId) => {
return usersDisplayName[userId];
}).filter((displayName) => {
return displayName && displayName !== '';
return `@${allUsernames[userId]}`;
}).filter((username) => {
return username && username !== '';
});
if (userIds.includes(currentUserId)) {
displayNames.unshift(usersDisplayName[currentUserId]);
usernames.unshift(allUsernames[currentUserId]);
} else if (userIds.includes(currentUsername)) {
displayNames.unshift(usersDisplayName[currentUsername]);
usernames.unshift(allUsernames[currentUsername]);
}
return displayNames;
return usernames;
}
renderSystemMessage(postType, userIds, actorId, style) {
const {currentUserId, currentUsername} = this.props;
const usersDisplayName = this.getDisplayNameByIds(userIds);
let actorDisplayName = actorId ? this.getDisplayNameByIds([actorId])[0] : '';
if (actorDisplayName && (actorId === currentUserId || actorId === currentUsername)) {
actorDisplayName = actorDisplayName.toLowerCase();
renderFormattedMessage(postType, userIds, actorId, style) {
const {formatMessage} = this.context.intl;
const {
currentUserId,
currentUsername,
navigator,
textStyles,
theme,
} = this.props;
const usernames = this.getUsernamesByIds(userIds);
let actor = actorId ? this.getUsernamesByIds([actorId])[0] : '';
if (actor && (actorId === currentUserId || actorId === currentUsername)) {
actor = actor.toLowerCase();
}
const firstUser = usersDisplayName[0];
const numOthers = usersDisplayName.length - 1;
const firstUser = usernames[0];
const secondUser = usernames[1];
const numOthers = usernames.length - 1;
let formattedMessage;
if (numOthers === 0) {
formattedMessage = this.renderFormattedMessage(
postTypeMessage[postType].one,
firstUser,
null,
actorDisplayName,
style,
if (numOthers > 1) {
return (
<LastUsers
actor={actor}
expandedLocale={postTypeMessage[postType].many_expanded}
navigator={navigator}
postType={postType}
style={style}
textStyles={textStyles}
theme={theme}
usernames={usernames}
/>
);
}
let localeHolder;
if (numOthers === 0) {
localeHolder = postTypeMessage[postType].one;
if (
(userIds[0] === currentUserId || userIds[0] === currentUsername) &&
postTypeMessage[postType].one_you
) {
formattedMessage = this.renderFormattedMessage(
postTypeMessage[postType].one_you,
null,
null,
actorDisplayName,
style,
);
localeHolder = postTypeMessage[postType].one_you;
}
} else if (numOthers === 1) {
formattedMessage = this.renderFormattedMessage(
postTypeMessage[postType].two,
firstUser,
usersDisplayName[1],
actorDisplayName,
style,
);
} else if (numOthers > 1) {
formattedMessage = (
<LastUsers
actor={actorDisplayName}
expandedLocale={postTypeMessage[postType].many_expanded}
postType={postType}
style={style}
userDisplayNames={usersDisplayName}
/>
);
localeHolder = postTypeMessage[postType].two;
}
return formattedMessage;
}
const formattedMessage = formatMessage(localeHolder, {firstUser, secondUser, actor});
renderFormattedMessage = (localeFormat, firstUser, secondUser, actor, style) => {
return (
<Text style={style.text}>
<FormattedText
id={localeFormat.id[0]}
defaultMessage={localeFormat.defaultMessage[0]}
values={{
firstUser,
secondUser,
}}
/>
<Text style={style.activityType}>
<FormattedText
id={localeFormat.id[1]}
defaultMessage={localeFormat.defaultMessage[1]}
/>
</Text >
{localeFormat.id[2] ? (
<FormattedText
id={localeFormat.id[2]}
defaultMessage={localeFormat.defaultMessage[2]}
values={{actor}}
/>
) : ('.')
}
</Text>
<Markdown
baseTextStyle={style.baseText}
navigator={navigator}
textStyles={textStyles}
value={formattedMessage}
/>
);
}
render() {
const {
linkStyle,
messageData,
theme,
} = this.props;
@@ -367,7 +333,7 @@ export default class CombinedSystemMessage extends React.PureComponent {
content.push(
<React.Fragment key={postType + actorId}>
{this.renderSystemMessage(postType, userIds, actorId, {activityType: style.activityType, link: linkStyle, text: style.text})}
{this.renderFormattedMessage(postType, userIds, actorId, {baseText: style.baseText, linkText: style.linkText})}
</React.Fragment>
);
}
@@ -382,14 +348,13 @@ export default class CombinedSystemMessage extends React.PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
activityType: {
color: theme.centerChannelColor,
fontSize: 14,
fontWeight: 'bold',
},
text: {
baseText: {
color: theme.centerChannelColor,
opacity: 0.6,
},
linkText: {
color: theme.linkColor,
opacity: 0.8,
},
};
});

View File

@@ -1,98 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({adapter: new Adapter()});
import {shallowWithIntl} from 'test/intl-test-helper';
import {emptyFunction} from 'app/utils/general';
import {Posts} from 'mattermost-redux/constants';
import CombinedSystemMessage from './combined_system_message';
/* eslint-disable max-nested-callbacks */
describe('CombinedSystemMessage', () => {
const baseProps = {
actions: {
getMissingProfilesByIds: emptyFunction,
getMissingProfilesByUsernames: emptyFunction,
},
allUserIds: ['user_id_1', 'user_id_2', 'user_id_3'],
currentUserId: 'user_id_3',
currentUsername: 'username_3',
linkStyle: 1,
messageData: [
{postType: Posts.POST_TYPES.ADD_TO_TEAM, userIds: ['user_id_1'], actorId: 'user_id_2'},
],
showJoinLeave: true,
teammateNameDisplay: 'username',
theme: {centerChannelColor: '#aaa'},
userProfiles: [{id: 'user_id_1', username: 'user1'}, {id: 'user_id_2', username: 'user2'}, {id: 'user_id_3', username: 'user3'}],
};
test('should match snapshot', () => {
const props = {
...baseProps,
actions: {
getMissingProfilesByIds: jest.fn(),
getMissingProfilesByUsernames: emptyFunction,
},
};
const wrapper = shallowWithIntl(
<CombinedSystemMessage {...props}/>
);
const {postType, userIds, actorId} = baseProps.messageData[0];
expect(wrapper.instance().renderSystemMessage(postType, userIds, actorId, {activityType: {fontSize: 14}, text: {opacity: 0.6}}, 1)).toMatchSnapshot();
// on componentDidMount
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(1);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledWith(props.allUserIds);
});
test('should match snapshot', () => {
const localeFormat = {
id: ['combined_system_message.first_user_and_second_user_were', 'combined_system_message.removed_from_team'],
defaultMessage: ['{firstUser} and {secondUser} were ', 'removed from the team'],
};
const wrapper = shallowWithIntl(
<CombinedSystemMessage {...baseProps}/>
);
expect(wrapper.instance().renderFormattedMessage(localeFormat, 'first_user', 'second_user', 'actor', {activityType: {fontSize: 14}, text: {opacity: 0.6}})).toMatchSnapshot();
});
test('should call getMissingProfilesByIds and/or getMissingProfilesByUsernames on loadUserProfiles', () => {
const props = {
...baseProps,
allUserIds: [],
actions: {
getMissingProfilesByIds: jest.fn(),
getMissingProfilesByUsernames: jest.fn(),
},
};
const wrapper = shallowWithIntl(
<CombinedSystemMessage {...props}/>
);
wrapper.instance().loadUserProfiles([], []);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(0);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledTimes(0);
wrapper.instance().loadUserProfiles(['user_id_1'], []);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(1);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledWith(['user_id_1']);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledTimes(0);
wrapper.instance().loadUserProfiles(['user_id_1', 'user_id_2'], ['user1']);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledTimes(2);
expect(props.actions.getMissingProfilesByIds).toHaveBeenCalledWith(['user_id_1', 'user_id_2']);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledTimes(1);
expect(props.actions.getMissingProfilesByUsernames).toHaveBeenCalledWith(['user1']);
});
});

View File

@@ -6,7 +6,7 @@ import {bindActionCreators} from 'redux';
import {getMissingProfilesByIds, getMissingProfilesByUsernames} from 'mattermost-redux/actions/users';
import {Preferences} from 'mattermost-redux/constants';
import {getBool, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser, makeGetProfilesByIdsAndUsernames} from 'mattermost-redux/selectors/entities/users';
import CombinedSystemMessage from './combined_system_message';
@@ -21,7 +21,6 @@ function makeMapStateToProps() {
currentUserId: currentUser.id,
currentUsername: currentUser.username,
showJoinLeave: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true),
teammateNameDisplay: getTeammateNameDisplaySetting(state),
userProfiles: getProfilesByIdsAndUsernames(state, {allUserIds, allUsernames}),
};
};

View File

@@ -4,43 +4,46 @@
import PropTypes from 'prop-types';
import React from 'react';
import {Text} from 'react-native';
import {intlShape} from 'react-intl';
import {Posts} from 'mattermost-redux/constants';
import FormattedMarkdownText from 'app/components/formatted_markdown_text';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
const typeMessage = {
[Posts.POST_TYPES.ADD_TO_CHANNEL]: {
id: ['mobile.combined_system_message.were', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['were ', 'added to the channel', ' by {actor}.'],
id: 'last_users_message.added_to_channel.type',
defaultMessage: 'were **added to the channel** by {actor}.',
},
[Posts.POST_TYPES.JOIN_CHANNEL]: {
id: ['', 'mobile.combined_system_message.joined_channel'],
defaultMessage: ['', 'joined the channel'],
id: 'last_users_message.joined_channel.type',
defaultMessage: '**joined the channel**.',
},
[Posts.POST_TYPES.LEAVE_CHANNEL]: {
id: ['', 'mobile.combined_system_message.left_channel', ''],
defaultMessage: ['', 'left the channel'],
id: 'last_users_message.left_channel.type',
defaultMessage: '**left the channel**.',
},
[Posts.POST_TYPES.REMOVE_FROM_CHANNEL]: {
id: ['mobile.combined_system_message.were', 'mobile.combined_system_message.removed_from_channel'],
defaultMessage: ['were ', 'removed from the channel'],
id: 'last_users_message.removed_from_channel.type',
defaultMessage: 'were **removed from the channel**.',
},
[Posts.POST_TYPES.ADD_TO_TEAM]: {
id: ['mobile.combined_system_message.were', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
defaultMessage: ['were ', 'added to the team', ' by {actor}.'],
id: 'last_users_message.added_to_team.type',
defaultMessage: 'were **added to the team** by {actor}.',
},
[Posts.POST_TYPES.JOIN_TEAM]: {
id: ['', 'mobile.combined_system_message.joined_team'],
defaultMessage: ['', 'joined the team'],
id: 'last_users_message.joined_team.type',
defaultMessage: '**joined the team**.',
},
[Posts.POST_TYPES.LEAVE_TEAM]: {
id: ['', 'mobile.combined_system_message.left_team'],
defaultMessage: ['', 'left the team'],
id: 'last_users_message.left_team.type',
defaultMessage: '**left the team**.',
},
[Posts.POST_TYPES.REMOVE_FROM_TEAM]: {
id: ['', 'mobile.combined_system_message.removed_from_team'],
defaultMessage: ['were ', 'removed from the team'],
id: 'last_users_message.removed_from_team.type',
defaultMessage: 'were **removed from the team**.',
},
};
@@ -48,9 +51,12 @@ export default class LastUsers extends React.PureComponent {
static propTypes = {
actor: PropTypes.string,
expandedLocale: PropTypes.object.isRequired,
navigator: PropTypes.object.isRequired,
postType: PropTypes.string.isRequired,
style: PropTypes.object.isRequired,
userDisplayNames: PropTypes.array.isRequired,
textStyles: PropTypes.object,
theme: PropTypes.object.isRequired,
usernames: PropTypes.array.isRequired,
};
constructor(props) {
@@ -61,99 +67,102 @@ export default class LastUsers extends React.PureComponent {
};
}
handleOnClick = (e) => {
static contextTypes = {
intl: intlShape,
};
handleOnPress = (e) => {
e.preventDefault();
this.setState({expand: true});
}
renderExpandedView = (expandedLocale, userDisplayNames, actor, lastIndex, style) => {
renderExpandedView = () => {
const {formatMessage} = this.context.intl;
const {
actor,
expandedLocale,
navigator,
style,
textStyles,
usernames,
} = this.props;
const lastIndex = usernames.length - 1;
const lastUser = usernames[lastIndex];
const formattedMessage = formatMessage(expandedLocale, {
users: usernames.slice(0, lastIndex).join(', '),
lastUser,
actor,
});
return (
<Text style={style.text}>
<FormattedText
id={expandedLocale.id[0]}
defaultMessage={expandedLocale.defaultMessage[0]}
values={{
users: userDisplayNames.slice(0, lastIndex).join(', '),
lastUser: userDisplayNames[lastIndex],
}}
/>
<Text style={style.activityType}>
<FormattedText
id={expandedLocale.id[1]}
defaultMessage={expandedLocale.defaultMessage[1]}
/>
</Text>
{expandedLocale.id[2] ? (
<FormattedText
id={expandedLocale.id[2]}
defaultMessage={expandedLocale.defaultMessage[2]}
values={{actor}}
/>
) : ('.')
}
</Text>
<Markdown
baseTextStyle={style.baseText}
navigator={navigator}
textStyles={textStyles}
value={formattedMessage}
/>
);
}
renderCollapsedView = (postType, userDisplayNames, actor, lastIndex, style) => {
renderCollapsedView = () => {
const {
actor,
navigator,
postType,
style,
textStyles,
theme,
usernames,
} = this.props;
const firstUser = usernames[0];
const numOthers = usernames.length - 1;
return (
<Text style={style.text}>
<FormattedText
id={'mobile.combined_system_message.first_user_and'}
<Text>
<FormattedMarkdownText
id={'last_users_message.first'}
defaultMessage={'{firstUser} and '}
values={{firstUser: userDisplayNames[0]}}
values={{firstUser}}
baseTextStyle={style.baseText}
navigator={navigator}
style={style.baseText}
textStyles={textStyles}
theme={theme}
/>
<Text>{' '}</Text>
<Text
style={style.link}
onPress={this.handleOnClick}
style={style.linkText}
onPress={this.handleOnPress}
>
<FormattedText
id={'mobile.combined_system_message.others'}
id={'last_users_message.others'}
defaultMessage={'{numOthers} others '}
values={{numOthers: lastIndex}}
values={{numOthers}}
/>
</Text>
{typeMessage[postType].id[0] &&
<FormattedText
id={typeMessage[postType].id[0]}
defaultMessage={typeMessage[postType].defaultMessage[0]}
<FormattedMarkdownText
id={typeMessage[postType].id}
defaultMessage={typeMessage[postType].defaultMessage}
values={{actor}}
baseTextStyle={style.baseText}
navigator={navigator}
style={style.baseText}
textStyles={textStyles}
theme={theme}
/>
}
<Text style={style.activityType}>
<FormattedText
id={typeMessage[postType].id[1]}
defaultMessage={typeMessage[postType].defaultMessage[1]}
/>
</Text>
{typeMessage[postType].id[2] ? (
<FormattedText
id={typeMessage[postType].id[2]}
defaultMessage={typeMessage[postType].defaultMessage[2]}
values={{actor}}
/>
) : ('.')
}
</Text>
);
}
render() {
const {expand} = this.state;
const {
actor,
expandedLocale,
postType,
userDisplayNames,
style,
} = this.props;
const lastIndex = userDisplayNames.length - 1;
if (expand) {
return this.renderExpandedView(expandedLocale, userDisplayNames, actor, lastIndex, style);
if (this.state.expand) {
return this.renderExpandedView();
}
return this.renderCollapsedView(postType, userDisplayNames, actor, lastIndex, style);
return this.renderCollapsedView();
}
}

View File

@@ -1,45 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({adapter: new Adapter()});
import {Posts} from 'mattermost-redux/constants';
import {shallowWithIntl} from 'test/intl-test-helper';
import LastUsers from './last_users';
describe('LastUsers', () => {
const baseProps = {
actor: 'actor',
expandedLocale: {id: 'expanded_locale_id', defaultMessage: 'Expanded Locale'},
postType: Posts.POST_TYPES.ADD_TO_TEAM,
style: {activityType: {fontSize: 14}, link: 1, text: {opacity: 0.6}},
userDisplayNames: ['User One', 'User Two'],
};
test('should match snapshot', () => {
const wrapper = shallowWithIntl(
<LastUsers {...baseProps}/>
);
const expanded = wrapper.instance().renderExpandedView(baseProps.expandedLocale, baseProps.userDisplayNames, baseProps.actor, 1, baseProps.style);
expect(expanded).toMatchSnapshot();
const collapsed = wrapper.instance().renderCollapsedView(baseProps.postType, baseProps.userDisplayNames, baseProps.actor, 1, baseProps.style);
expect(collapsed).toMatchSnapshot();
});
test('should match state on handleOnClick', () => {
const wrapper = shallowWithIntl(
<LastUsers {...baseProps}/>
);
wrapper.setState({expand: false});
wrapper.instance().handleOnClick({preventDefault: jest.fn()});
expect(wrapper.state('expand')).toEqual(true);
});
});

View File

@@ -7,11 +7,14 @@ import PropTypes from 'prop-types';
import Renderer from 'commonmark-react-renderer';
import {Parser} from 'commonmark';
import {injectIntl, intlShape} from 'react-intl';
import MarkdownLink from 'app/components/markdown/markdown_link';
import {concatStyles, changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {getMarkdownTextStyles} from 'app/utils/markdown';
import CustomPropTypes from 'app/constants/custom_prop_types';
import AtMention from 'app/components/at_mention';
import MarkdownLink from 'app/components/markdown/markdown_link';
const TARGET_BLANK_URL_PREFIX = '!';
/*
@@ -29,12 +32,19 @@ const TARGET_BLANK_URL_PREFIX = '!';
*/
class FormattedMarkdownText extends React.PureComponent {
static propTypes = {
intl: intlShape.isRequired,
id: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
baseTextStyle: CustomPropTypes.Style,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
id: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
onPostPress: PropTypes.func,
style: CustomPropTypes.Style,
textStyles: PropTypes.object,
theme: PropTypes.object.isRequired,
values: PropTypes.object,
};
static contextTypes = {
intl: intlShape,
};
constructor(props) {
@@ -63,6 +73,7 @@ class FormattedMarkdownText extends React.PureComponent {
del: Renderer.forwardChildren,
html_inline: this.renderHTML,
html_block: this.renderHTML,
atMention: this.renderAtMention,
},
});
}
@@ -99,10 +110,24 @@ class FormattedMarkdownText extends React.PureComponent {
return this.renderText(props);
}
renderAtMention = ({context, mentionName}) => {
return (
<AtMention
mentionStyle={this.props.textStyles.mention}
mentionName={mentionName}
navigator={this.props.navigator}
onLongPress={this.props.onPostPress}
onPostPress={this.props.onPostPress}
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
/>
);
}
render() {
const {id, defaultMessage, values, theme} = this.props;
const messageDescriptor = {id, defaultMessage};
const message = this.props.intl.formatMessage(messageDescriptor, values);
const {formatMessage} = this.context.intl;
const message = formatMessage(messageDescriptor, values);
const ast = this.parser.parse(message);
this.textStyles = getMarkdownTextStyles(theme);
this.baseTextStyle = getStyleSheet(theme).message;

View File

@@ -443,17 +443,21 @@ export default class PostBody extends PureComponent {
} else if (postType === Posts.POST_TYPES.COMBINED_USER_ACTIVITY) {
const {allUserIds, allUsernames, messageData} = postProps.user_activity;
messageComponent = (
<View style={style.row}>
<View style={style.flex}>
<CombinedSystemMessage
allUserIds={allUserIds}
allUsernames={allUsernames}
linkStyle={textStyles.link}
messageData={messageData}
theme={theme}
/>
<TouchableOpacity onLongPress={this.showOptionsContext}>
<View style={style.row}>
<View style={style.flex}>
<CombinedSystemMessage
allUserIds={allUserIds}
allUsernames={allUsernames}
linkStyle={textStyles.link}
messageData={messageData}
navigator={navigator}
textStyles={textStyles}
theme={theme}
/>
</View>
</View>
</View>
</TouchableOpacity>
);
} else if (message.length) {
messageComponent = (

View File

@@ -2357,26 +2357,6 @@
"mobile.client_upgrade.no_upgrade_subtitle": "You already have the latest version.",
"mobile.client_upgrade.no_upgrade_title": "Your App Is Up to Date",
"mobile.client_upgrade.upgrade": "Update",
"mobile.combined_system_message.added_to_channel": "added to the channel",
"mobile.combined_system_message.added_to_team": "added to the team",
"mobile.combined_system_message.by_actor": " by {actor}.",
"mobile.combined_system_message.first_user": "{firstUser} ",
"mobile.combined_system_message.first_user_and": "{firstUser} and ",
"mobile.combined_system_message.first_user_and_second_user": "{firstUser} and {secondUser} ",
"mobile.combined_system_message.first_user_and_second_user_were": "{firstUser} and {secondUser} were ",
"mobile.combined_system_message.first_user_was": "{firstUser} was ",
"mobile.combined_system_message.joined_channel": "joined the channel",
"mobile.combined_system_message.joined_team": "joined the team",
"mobile.combined_system_message.left_channel": "left the channel",
"mobile.combined_system_message.left_team": "left the team",
"mobile.combined_system_message.others": "{numOthers} others ",
"mobile.combined_system_message.removed_from_channel": "removed from the channel",
"mobile.combined_system_message.removed_from_team": "removed from the team",
"mobile.combined_system_message.users_and_last_user": "{users} and {lastUser} ",
"mobile.combined_system_message.users_and_last_user_were": "{users} and {lastUser} were ",
"mobile.combined_system_message.were": "were ",
"mobile.combined_system_message.you": "You",
"mobile.combined_system_message.you_were": "You were ",
"mobile.commands.error_title": "Error Executing Command",
"mobile.components.channels_list_view.yourChannels": "Your channels:",
"mobile.components.error_list.dismiss_all": "Dismiss All",