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:
parent
19a755d59f
commit
9727476ef6
19 changed files with 102 additions and 73 deletions
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
@ -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();
|
||||
});
|
||||
19
app/components/markdown/hashtag/index.js
Normal file
19
app/components/markdown/hashtag/index.js
Normal 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);
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,7 +308,6 @@ export default class ChannelInfo extends PureComponent {
|
|||
passProps: {
|
||||
isPermalink: true,
|
||||
onClose: this.handleClosePermalink,
|
||||
onPermalinkPress: this.handlePermalinkPress,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -518,8 +518,6 @@ export default class Search extends PureComponent {
|
|||
passProps: {
|
||||
isPermalink,
|
||||
onClose: this.handleClosePermalink,
|
||||
onHashtagPress: this.handleHashtagPress,
|
||||
onPermalinkPress: this.handlePermalinkPress,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue