MM-12901 Improve handling of hashtags on Recent Mentions, Flagged Posts, and Permalink screens (#2329)

* MM-12901 Create showSearchModal action and use for hashtags

* MM-12901 Improve handling of hashtags on Recent Mentions, Flagged Posts, and Permalink screens
This commit is contained in:
Harrison Healey 2018-11-09 18:43:22 -05:00 committed by Elias Nahum
parent 19a755d59f
commit 9727476ef6
19 changed files with 102 additions and 73 deletions

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {ViewTypes} from 'app/constants';
export function handleSearchDraftChanged(text) {
@ -11,3 +13,25 @@ export function handleSearchDraftChanged(text) {
}, getState);
};
}
export function showSearchModal(navigator, initialValue = '') {
return (dispatch, getState) => {
const theme = getTheme(getState());
const options = {
screen: 'Search',
animated: true,
backButtonTitle: '',
overrideBackPress: true,
passProps: {
initialValue,
},
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: theme.centerChannelBg,
},
};
navigator.showModal(options);
};
}

View file

@ -3,7 +3,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import {intlShape} from 'react-intl';
import {Text} from 'react-native';
import CustomPropTypes from 'app/constants/custom_prop_types';
@ -14,37 +13,23 @@ export default class Hashtag extends React.PureComponent {
linkStyle: CustomPropTypes.Style.isRequired,
onHashtagPress: PropTypes.func,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape,
actions: PropTypes.shape({
showSearchModal: PropTypes.func.isRequired,
}).isRequired,
};
handlePress = () => {
if (this.props.onHashtagPress) {
this.props.onHashtagPress(this.props.hashtag);
return;
}
const options = {
screen: 'Search',
animated: true,
backButtonTitle: '',
passProps: {
initialValue: '#' + this.props.hashtag,
},
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: this.props.theme.centerChannelBg,
},
};
// Close thread view, permalink view, etc
this.props.navigator.dismissAllModals();
this.props.navigator.popToRoot();
this.props.navigator.showModal(options);
this.props.actions.showSearchModal(this.props.navigator, '#' + this.props.hashtag);
};
render() {

View file

@ -11,8 +11,13 @@ describe('Hashtag', () => {
const baseProps = {
hashtag: 'test',
linkStyle: {color: 'red'},
navigator: {},
theme: {},
navigator: {
dismissAllModals: jest.fn(),
popToRoot: jest.fn(),
},
actions: {
showSearchModal: jest.fn(),
},
};
test('should match snapshot', () => {
@ -24,11 +29,6 @@ describe('Hashtag', () => {
test('should open hashtag search on click', () => {
const props = {
...baseProps,
navigator: {
dismissAllModals: jest.fn(),
popToRoot: jest.fn(),
showModal: jest.fn(),
},
};
const wrapper = shallow(<Hashtag {...props}/>);
@ -37,22 +37,12 @@ describe('Hashtag', () => {
expect(props.navigator.dismissAllModals).toHaveBeenCalled();
expect(props.navigator.popToRoot).toHaveBeenCalled();
expect(props.navigator.showModal).toHaveBeenCalledWith(expect.objectContaining({
screen: 'Search',
passProps: {
initialValue: '#test',
},
}));
expect(props.actions.showSearchModal).toHaveBeenCalledWith(props.navigator, '#test');
});
test('should call onHashtagPress if provided', () => {
const props = {
...baseProps,
navigator: {
dismissAllModals: jest.fn(),
popToRoot: jest.fn(),
showModal: jest.fn(),
},
onHashtagPress: jest.fn(),
};
@ -62,7 +52,7 @@ describe('Hashtag', () => {
expect(props.navigator.dismissAllModals).not.toBeCalled();
expect(props.navigator.popToRoot).not.toBeCalled();
expect(props.navigator.showModal).not.toBeCalled();
expect(props.actions.showSearchModal).not.toBeCalled();
expect(props.onHashtagPress).toBeCalled();
});

View file

@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {showSearchModal} from 'app/actions/views/search';
import Hashtag from './hashtag';
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showSearchModal,
}, dispatch),
};
}
export default connect(null, mapDispatchToProps)(Hashtag);

View file

@ -232,7 +232,6 @@ export default class Markdown extends PureComponent {
linkStyle={this.props.textStyles.link}
onHashtagPress={this.props.onHashtagPress}
navigator={this.props.navigator}
theme={this.props.theme}
/>
);
}

View file

@ -31,6 +31,7 @@ export default class PostListBase extends PureComponent {
lastViewedAt: PropTypes.number, // Used by container // eslint-disable-line no-unused-prop-types
navigator: PropTypes.object,
onLoadMoreUp: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
onPostPress: PropTypes.func,
onRefresh: PropTypes.func,
@ -156,6 +157,7 @@ export default class PostListBase extends PureComponent {
highlightPostId,
isSearchResult,
navigator,
onHashtagPress,
onPostPress,
renderReplies,
shouldRenderReplyButton,
@ -168,6 +170,7 @@ export default class PostListBase extends PureComponent {
postId={postId}
previousPostId={previousPostId}
nextPostId={nextPostId}
onHashtagPress={onHashtagPress}
onPermalinkPress={this.handlePermalinkPress}
highlight={highlight}
renderReplies={renderReplies}
@ -212,7 +215,6 @@ export default class PostListBase extends PureComponent {
passProps: {
isPermalink: true,
onClose: this.handleClosePermalink,
onPermalinkPress: this.handlePermalinkPress,
},
};

View file

@ -12,34 +12,21 @@ import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
import {preventDoubleTap} from 'app/utils/tap';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
const SEARCH = 'search';
export default class ChannelSearchButton extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
clearSearch: PropTypes.func.isRequired,
handlePostDraftChanged: PropTypes.func.isRequired,
showSearchModal: PropTypes.func.isRequired,
}).isRequired,
navigator: PropTypes.object,
theme: PropTypes.object,
};
handlePress = preventDoubleTap(async () => {
const {actions, navigator, theme} = this.props;
const {actions, navigator} = this.props;
await actions.clearSearch();
actions.handlePostDraftChanged(SEARCH, '');
navigator.showModal({
screen: 'Search',
animated: true,
backButtonTitle: '',
overrideBackPress: true,
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: theme.centerChannelBg,
},
});
await actions.showSearchModal(navigator);
});
render() {

View file

@ -6,7 +6,7 @@ import {connect} from 'react-redux';
import {clearSearch} from 'mattermost-redux/actions/search';
import {handlePostDraftChanged} from 'app/actions/views/channel';
import {showSearchModal} from 'app/actions/views/search';
import ChannelSearchButton from './channel_search_button';
@ -14,7 +14,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
clearSearch,
handlePostDraftChanged,
showSearchModal,
}, dispatch),
};
}

View file

@ -308,7 +308,6 @@ export default class ChannelInfo extends PureComponent {
passProps: {
isPermalink: true,
onClose: this.handleClosePermalink,
onPermalinkPress: this.handlePermalinkPress,
},
};

View file

@ -33,6 +33,7 @@ export default class FlaggedPosts extends PureComponent {
getFlaggedPosts: PropTypes.func.isRequired,
selectFocusedPostId: PropTypes.func.isRequired,
selectPost: PropTypes.func.isRequired,
showSearchModal: PropTypes.func.isRequired,
}).isRequired,
didFail: PropTypes.bool,
isLoading: PropTypes.bool,
@ -112,6 +113,14 @@ export default class FlaggedPosts extends PureComponent {
this.showPermalinkView(postId, true);
};
handleHashtagPress = async (hashtag) => {
const {actions, navigator} = this.props;
await navigator.dismissModal();
actions.showSearchModal(navigator, '#' + hashtag);
};
keyExtractor = (item) => item;
onNavigatorEvent = (event) => {
@ -173,6 +182,7 @@ export default class FlaggedPosts extends PureComponent {
previewPost={this.previewPost}
goToThread={this.goToThread}
navigator={this.props.navigator}
onHashtagPress={this.handleHashtagPress}
onPermalinkPress={this.handlePermalinkPress}
managedConfig={managedConfig}
showFullDate={false}
@ -212,7 +222,6 @@ export default class FlaggedPosts extends PureComponent {
passProps: {
isPermalink,
onClose: this.handleClosePermalink,
onPermalinkPress: this.handlePermalinkPress,
},
};

View file

@ -10,6 +10,7 @@ import {RequestStatus} from 'mattermost-redux/constants';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel';
import {showSearchModal} from 'app/actions/views/search';
import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list';
import FlaggedPosts from './flagged_posts';
@ -40,6 +41,7 @@ function mapDispatchToProps(dispatch) {
getFlaggedPosts,
selectFocusedPostId,
selectPost,
showSearchModal,
}, dispatch),
};
}

View file

@ -18,6 +18,7 @@ import {
setChannelDisplayName,
setChannelLoading,
} from 'app/actions/views/channel';
import {showSearchModal} from 'app/actions/views/search';
import {handleTeamChange} from 'app/actions/views/select_team';
import Permalink from './permalink';
@ -72,6 +73,7 @@ function mapDispatchToProps(dispatch) {
selectPost,
setChannelDisplayName,
setChannelLoading,
showSearchModal,
}, dispatch),
};
}

View file

@ -70,8 +70,6 @@ export default class Permalink extends PureComponent {
myMembers: PropTypes.object.isRequired,
navigator: PropTypes.object,
onClose: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
onPress: PropTypes.func,
postIds: PropTypes.array,
theme: PropTypes.object.isRequired,
@ -209,6 +207,14 @@ export default class Permalink extends PureComponent {
}
};
handleHashtagPress = () => {
// Do nothing because we're already in a modal
};
handlePermalinkPress = () => {
// Do nothing because we're already in permalink view for a different post
};
handlePress = () => {
const {channelIdState, channelNameState} = this.state;
@ -359,8 +365,6 @@ export default class Permalink extends PureComponent {
currentUserId,
focusedPostId,
navigator,
onHashtagPress,
onPermalinkPress,
theme,
} = this.props;
const {
@ -398,8 +402,8 @@ export default class Permalink extends PureComponent {
isSearchResult={false}
shouldRenderReplyButton={false}
renderReplies={true}
onHashtagPress={onHashtagPress}
onPermalinkPress={onPermalinkPress}
onHashtagPress={this.handleHashtagPress}
onPermalinkPress={this.handlePermalinkPress}
onPostPress={this.goToThread}
postIds={postIdsState}
currentUserId={currentUserId}

View file

@ -48,8 +48,6 @@ describe('Permalink', () => {
myMembers: {},
navigator,
onClose: jest.fn(),
onHashtagPress: jest.fn(),
onPermalinkPress: jest.fn(),
onPress: jest.fn(),
postIds: ['post_id_1', 'focused_post_id', 'post_id_3'],
theme: Preferences.THEMES.default,

View file

@ -10,6 +10,7 @@ import {RequestStatus} from 'mattermost-redux/constants';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel';
import {showSearchModal} from 'app/actions/views/search';
import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list';
import RecentMentions from './recent_mentions';
@ -40,6 +41,7 @@ function mapDispatchToProps(dispatch) {
getRecentMentions,
selectFocusedPostId,
selectPost,
showSearchModal,
}, dispatch),
};
}

View file

@ -33,6 +33,7 @@ export default class RecentMentions extends PureComponent {
getRecentMentions: PropTypes.func.isRequired,
selectFocusedPostId: PropTypes.func.isRequired,
selectPost: PropTypes.func.isRequired,
showSearchModal: PropTypes.func.isRequired,
}).isRequired,
didFail: PropTypes.bool,
isLoading: PropTypes.bool,
@ -112,6 +113,14 @@ export default class RecentMentions extends PureComponent {
this.showPermalinkView(postId, true);
};
handleHashtagPress = async (hashtag) => {
const {actions, navigator} = this.props;
await navigator.dismissModal();
actions.showSearchModal(navigator, '#' + hashtag);
};
keyExtractor = (item) => item;
onNavigatorEvent = (event) => {
@ -173,6 +182,7 @@ export default class RecentMentions extends PureComponent {
previewPost={this.previewPost}
goToThread={this.goToThread}
navigator={this.props.navigator}
onHashtagPress={this.handleHashtagPress}
onPermalinkPress={this.handlePermalinkPress}
managedConfig={managedConfig}
showFullDate={false}
@ -212,7 +222,6 @@ export default class RecentMentions extends PureComponent {
passProps: {
isPermalink,
onClose: this.handleClosePermalink,
onPermalinkPress: this.handlePermalinkPress,
},
};

View file

@ -518,8 +518,6 @@ export default class Search extends PureComponent {
passProps: {
isPermalink,
onClose: this.handleClosePermalink,
onHashtagPress: this.handleHashtagPress,
onPermalinkPress: this.handlePermalinkPress,
},
};

View file

@ -12,7 +12,7 @@ export default class SearchResultPost extends PureComponent {
goToThread: PropTypes.func.isRequired,
managedConfig: PropTypes.object.isRequired,
navigator: PropTypes.object.isRequired,
onHashtagPress: PropTypes.func.isRequired,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func.isRequired,
postId: PropTypes.string.isRequired,
previewPost: PropTypes.func.isRequired,