forked from Ivasoft/mattermost-mobile
[MM-38216] Add multiteam mentions and saved posts (#5677)
* Add multiteam mentions and saved posts * Minor fix * Fix long names * Add tests, improve separation styling, revert changes on the flagged posts client, and omit the team name when the user only belongs to one team * Fix separator on iOS * Update snapshot * Fix separator * Fix snapshot * Differentiate styling between iOS and Android * Change channelTeamName to teamName
This commit is contained in:
committed by
GitHub
parent
6dbb537f22
commit
edfd743699
@@ -250,8 +250,13 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
searchPostsWithParams = async (teamId: string, params: any) => {
|
||||
analytics.trackAPI('api_posts_search', {team_id: teamId});
|
||||
|
||||
let route = `${this.getPostsRoute()}/search`;
|
||||
if (teamId) {
|
||||
route = `${this.getTeamRoute(teamId)}/posts/search`;
|
||||
}
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamRoute(teamId)}/posts/search`,
|
||||
route,
|
||||
{method: 'post', body: JSON.stringify(params)},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -114,13 +114,12 @@ export function getFlaggedPosts(): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const state = getState();
|
||||
const userId = getCurrentUserId(state);
|
||||
const teamId = getCurrentTeamId(state);
|
||||
|
||||
dispatch({type: SearchTypes.SEARCH_FLAGGED_POSTS_REQUEST});
|
||||
|
||||
let posts;
|
||||
try {
|
||||
posts = await Client4.getFlaggedPosts(userId, '', teamId);
|
||||
posts = await Client4.getFlaggedPosts(userId);
|
||||
|
||||
await Promise.all([getProfilesAndStatusesForPosts(posts.posts, dispatch, getState) as any, dispatch(getMissingChannelsFromPosts(posts.posts)) as any]);
|
||||
} catch (error) {
|
||||
@@ -202,7 +201,6 @@ export function clearPinnedPosts(channelId: string): ActionFunc {
|
||||
export function getRecentMentions(): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const state = getState();
|
||||
const teamId = getCurrentTeamId(state);
|
||||
|
||||
let posts;
|
||||
try {
|
||||
@@ -213,7 +211,7 @@ export function getRecentMentions(): ActionFunc {
|
||||
const terms = termKeys.map(({key}) => key).join(' ').trim() + ' ';
|
||||
|
||||
analytics.trackAPI('api_posts_search_mention');
|
||||
posts = await Client4.searchPosts(teamId, terms, true);
|
||||
posts = await Client4.searchPosts('', terms, true);
|
||||
|
||||
const profilesAndStatuses = getProfilesAndStatusesForPosts(posts.posts, dispatch, getState);
|
||||
const missingChannels = dispatch(getMissingChannelsFromPosts(posts.posts));
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SearchResultPost should match snapshot when no team is provided 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "baseline",
|
||||
"flexDirection": "row",
|
||||
"marginTop": 5,
|
||||
"paddingHorizontal": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.8)",
|
||||
"flexShrink": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
}
|
||||
>
|
||||
channel
|
||||
</Text>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`SearchResultPost should match snapshot when team is provided 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "baseline",
|
||||
"flexDirection": "row",
|
||||
"marginTop": 5,
|
||||
"paddingHorizontal": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.8)",
|
||||
"flexShrink": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
}
|
||||
>
|
||||
channel
|
||||
</Text>
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignSelf": "stretch",
|
||||
"borderLeftColor": "rgba(63,67,80,0.2)",
|
||||
"borderLeftWidth": 1,
|
||||
"borderStyle": "solid",
|
||||
"height": 16,
|
||||
"marginLeft": 8,
|
||||
"marginRight": 8,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.5)",
|
||||
"flexShrink": 2,
|
||||
"fontSize": 12,
|
||||
"fontWeight": "400",
|
||||
}
|
||||
}
|
||||
>
|
||||
team
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
</View>
|
||||
`;
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Text} from 'react-native';
|
||||
import {Platform, Text, View} from 'react-native';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
@@ -11,14 +11,29 @@ export default class ChannelDisplayName extends PureComponent {
|
||||
static propTypes = {
|
||||
displayName: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
teamName: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {displayName, theme} = this.props;
|
||||
const {displayName, theme, teamName} = this.props;
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<Text style={styles.channelName}>{displayName}</Text>
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
style={styles.channelName}
|
||||
numberOfLines={1}
|
||||
>{displayName}</Text>
|
||||
{Boolean(teamName) &&
|
||||
<>
|
||||
<View style={styles.separator}/>
|
||||
<Text
|
||||
style={styles.teamName}
|
||||
numberOfLines={1}
|
||||
>{teamName}</Text>
|
||||
</>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,8 +44,28 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
flexShrink: 1,
|
||||
},
|
||||
teamName: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 12,
|
||||
fontWeight: '400',
|
||||
flexShrink: 2,
|
||||
},
|
||||
separator: {
|
||||
borderStyle: 'solid',
|
||||
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderLeftWidth: 1,
|
||||
height: 16,
|
||||
marginRight: 8,
|
||||
marginLeft: 8,
|
||||
alignSelf: Platform.select({ios: 'stretch'}),
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 5,
|
||||
paddingHorizontal: 16,
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
|
||||
import ChannelDisplayName from './channel_display_name';
|
||||
|
||||
describe('SearchResultPost', () => {
|
||||
const baseProps = {
|
||||
displayName: 'channel',
|
||||
teamName: '',
|
||||
theme: Preferences.THEMES.denim,
|
||||
};
|
||||
|
||||
test('should match snapshot when no team is provided', async () => {
|
||||
const wrapper = shallow(<ChannelDisplayName {...baseProps}/>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when team is provided', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
teamName: 'team',
|
||||
};
|
||||
const wrapper = shallow(<ChannelDisplayName {...props}/>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -3,21 +3,33 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {makeGetChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getTeam, getTeamMemberships} from '@mm-redux/selectors/entities/teams';
|
||||
|
||||
import ChannelDisplayName from './channel_display_name';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
// Exported for testing
|
||||
export function makeMapStateToProps() {
|
||||
const getChannel = makeGetChannel();
|
||||
return (state, ownProps) => {
|
||||
const post = getPost(state, ownProps.postId);
|
||||
const channel = post ? getChannel(state, {id: post.channel_id}) : null;
|
||||
let teamName = '';
|
||||
if (channel) {
|
||||
const isDMorGM = channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL;
|
||||
const memberships = getTeamMemberships(state);
|
||||
if (!isDMorGM && memberships && Object.values(memberships).length > 1) {
|
||||
teamName = getTeam(state, channel.team_id)?.display_name;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: channel ? channel.display_name : '',
|
||||
theme: getTheme(state),
|
||||
teamName,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
202
app/screens/search/channel_display_name/index.test.js
Normal file
202
app/screens/search/channel_display_name/index.test.js
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import {makeMapStateToProps} from './index';
|
||||
|
||||
describe('components/SearchResultsItem/WithStore', () => {
|
||||
const team = {
|
||||
id: 'team_id',
|
||||
display_name: 'team display name',
|
||||
name: 'team_name',
|
||||
};
|
||||
|
||||
const otherTeam = {
|
||||
id: 'other_team_id',
|
||||
display_name: 'other team display name',
|
||||
name: 'other_team_name',
|
||||
};
|
||||
|
||||
const currentUserID = 'other';
|
||||
const user = {
|
||||
id: 'user_id',
|
||||
username: 'username',
|
||||
is_bot: false,
|
||||
};
|
||||
const channel = {
|
||||
id: 'channel_id_open',
|
||||
type: General.OPEN_CHANNEL,
|
||||
team_id: team.id,
|
||||
name: 'open channel',
|
||||
display_name: 'open channel',
|
||||
};
|
||||
|
||||
const dmChannel = {
|
||||
id: 'channel_id_dm',
|
||||
type: General.DM_CHANNEL,
|
||||
name: `${currentUserID}__${user.id}`,
|
||||
display_name: `${currentUserID}__${user.id}`,
|
||||
};
|
||||
|
||||
const post = {
|
||||
channel_id: channel.id,
|
||||
create_at: 1502715365009,
|
||||
delete_at: 0,
|
||||
edit_at: 1502715372443,
|
||||
id: 'id',
|
||||
is_pinned: false,
|
||||
message: 'post message',
|
||||
original_id: '',
|
||||
pending_post_id: '',
|
||||
props: {},
|
||||
root_id: '',
|
||||
type: '',
|
||||
update_at: 1502715372443,
|
||||
user_id: 'user_id',
|
||||
reply_count: 0,
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {
|
||||
EnablePostUsernameOverride: 'true',
|
||||
},
|
||||
license: {},
|
||||
},
|
||||
users: {
|
||||
profiles: {
|
||||
[user.id]: user,
|
||||
},
|
||||
currentUserId: currentUserID,
|
||||
statuses: {},
|
||||
profilesInChannel: {},
|
||||
},
|
||||
channels: {
|
||||
channels: {
|
||||
[channel.id]: channel,
|
||||
[dmChannel.id]: dmChannel,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
myMembers: {
|
||||
[team.id]: {},
|
||||
},
|
||||
teams: {
|
||||
[team.id]: team,
|
||||
[otherTeam.id]: otherTeam,
|
||||
},
|
||||
currentTeamId: team.id,
|
||||
},
|
||||
posts: {
|
||||
posts: {
|
||||
[post.id]: post,
|
||||
},
|
||||
postsInThread: {},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {
|
||||
hasOwnProperty: () => true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
postId: post.id,
|
||||
};
|
||||
|
||||
const mstp = makeMapStateToProps();
|
||||
|
||||
test('should not show team name if user only belongs to one team', () => {
|
||||
const newProps = mstp(defaultState, defaultProps);
|
||||
expect(newProps.teamName).toBe('');
|
||||
});
|
||||
|
||||
test('should show team name for open and private channels when user belongs to more than one team', () => {
|
||||
let state = {
|
||||
...defaultState,
|
||||
entities: {
|
||||
...defaultState.entities,
|
||||
teams: {
|
||||
...defaultState.entities.teams,
|
||||
myMembers: {
|
||||
...defaultState.entities.teams.myMembers,
|
||||
[otherTeam.id]: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
let newProps = mstp(state, defaultProps);
|
||||
expect(newProps.teamName).toBe(team.display_name);
|
||||
|
||||
state = {
|
||||
...state,
|
||||
entities: {
|
||||
...state.entities,
|
||||
channels: {
|
||||
...state.entities.channels,
|
||||
channels: {
|
||||
...state.entities.channels.channels,
|
||||
[channel.id]: {
|
||||
...channel,
|
||||
type: General.PRIVATE_CHANNEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
newProps = mstp(state, defaultProps);
|
||||
expect(newProps.teamName).toBe(team.display_name);
|
||||
});
|
||||
|
||||
test('should not show team name for dm and group channels when user belongs to more than one team', () => {
|
||||
let state = {
|
||||
...defaultState,
|
||||
entities: {
|
||||
...defaultState.entities,
|
||||
teams: {
|
||||
...defaultState.entities.teams,
|
||||
myMembers: {
|
||||
...defaultState.entities.teams.myMembers,
|
||||
[otherTeam.id]: {},
|
||||
},
|
||||
},
|
||||
posts: {
|
||||
...defaultState.entities.posts,
|
||||
posts: {
|
||||
...defaultState.entities.posts.posts,
|
||||
[post.id]: {
|
||||
...defaultState.entities.posts.posts[post.id],
|
||||
channel_id: dmChannel.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let newProps = mstp(state, defaultProps);
|
||||
expect(newProps.teamName).toBe('');
|
||||
|
||||
state = {
|
||||
...state,
|
||||
entities: {
|
||||
...state.entities,
|
||||
channels: {
|
||||
...state.entities.channels,
|
||||
channels: {
|
||||
...state.entities.channels.channels,
|
||||
[dmChannel.id]: {
|
||||
...dmChannel,
|
||||
type: General.GM_CHANNEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
newProps = mstp(state, defaultProps);
|
||||
expect(newProps.teamName).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user