RN-220 Add "in:" and "from:" search modifiers (#869)
* Fix search and search preview * Add "in:" and "from:" search modifiers
This commit is contained in:
parent
18b3d6eec9
commit
553f3796b1
6 changed files with 147 additions and 25 deletions
|
|
@ -167,13 +167,14 @@ export function loadFilesForPostIfNecessary(postId) {
|
|||
};
|
||||
}
|
||||
|
||||
export function loadThreadIfNecessary(rootId) {
|
||||
export function loadThreadIfNecessary(rootId, channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {posts} = state.entities.posts;
|
||||
const {posts, postsInChannel} = state.entities.posts;
|
||||
const channelPosts = postsInChannel[channelId];
|
||||
|
||||
if (rootId && !posts[rootId]) {
|
||||
getPostThread(rootId)(dispatch, getState);
|
||||
if (rootId && (!posts[rootId] || !channelPosts || !channelPosts[rootId])) {
|
||||
getPostThread(rootId, false)(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,9 +52,17 @@ export default class SearchPreview extends PureComponent {
|
|||
};
|
||||
|
||||
state = {
|
||||
showPosts: false
|
||||
showPosts: false,
|
||||
animationEnded: false
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {animationEnded, showPosts} = this.state;
|
||||
if (animationEnded && !showPosts && nextProps.posts.length) {
|
||||
this.setState({showPosts: true});
|
||||
}
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.refs.view.zoomOut().then(() => {
|
||||
if (this.props.onClose) {
|
||||
|
|
@ -74,6 +82,7 @@ export default class SearchPreview extends PureComponent {
|
|||
};
|
||||
|
||||
showPostList = () => {
|
||||
this.setState({animationEnded: true});
|
||||
if (!this.state.showPosts && this.props.posts.length) {
|
||||
this.setState({showPosts: true});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class ChannelPostList extends PureComponent {
|
|||
const channelId = post.channel_id;
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
actions.loadThreadIfNecessary(post.root_id);
|
||||
actions.loadThreadIfNecessary(post.root_id, channelId);
|
||||
actions.selectPost(rootId);
|
||||
|
||||
let title;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Dimensions,
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
|
|
@ -14,6 +15,7 @@ import {
|
|||
View
|
||||
} from 'react-native';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
|
|
@ -32,6 +34,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
const SECTION_HEIGHT = 20;
|
||||
const RECENT_LABEL_HEIGHT = 42;
|
||||
const RECENT_SEPARATOR_HEIGHT = 3;
|
||||
const MODIFIER_LABEL_HEIGHT = 58;
|
||||
const POSTS_PER_PAGE = ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
|
||||
const SEARCHING = 'searching';
|
||||
const NO_RESULTS = 'no results';
|
||||
|
|
@ -99,7 +102,7 @@ class Search extends Component {
|
|||
setTimeout(() => {
|
||||
this.refs.list.getWrapperRef().getListRef().scrollToOffset({
|
||||
animated: true,
|
||||
offset: SECTION_HEIGHT + (recentLenght * RECENT_LABEL_HEIGHT) + ((recentLenght - 1) * RECENT_SEPARATOR_HEIGHT)
|
||||
offset: SECTION_HEIGHT + (2 * MODIFIER_LABEL_HEIGHT) + (recentLenght * RECENT_LABEL_HEIGHT) + ((recentLenght + 1) * RECENT_SEPARATOR_HEIGHT)
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
|
|
@ -121,7 +124,8 @@ class Search extends Component {
|
|||
const channel = channels.find((c) => c.id === channelId);
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
actions.loadThreadIfNecessary(post.root_id);
|
||||
Keyboard.dismiss();
|
||||
actions.loadThreadIfNecessary(rootId, channelId);
|
||||
actions.selectPost(rootId);
|
||||
|
||||
let title;
|
||||
|
|
@ -149,11 +153,7 @@ class Search extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
navigator.showModal(options);
|
||||
} else {
|
||||
navigator.push(options);
|
||||
}
|
||||
navigator.push(options);
|
||||
};
|
||||
|
||||
handleSelectionChange = (event) => {
|
||||
|
|
@ -173,6 +173,10 @@ class Search extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
keyModifierExtractor = (item) => {
|
||||
return `modifier-${item.value}`;
|
||||
};
|
||||
|
||||
keyRecentExtractor = (item) => {
|
||||
return `recent-${item.terms}`;
|
||||
};
|
||||
|
|
@ -205,7 +209,8 @@ class Search extends Component {
|
|||
const focusedPostId = post.id;
|
||||
const channelId = post.channel_id;
|
||||
|
||||
actions.getPostThread(focusedPostId);
|
||||
Keyboard.dismiss();
|
||||
actions.getPostThread(focusedPostId, false);
|
||||
actions.getPostsBefore(channelId, focusedPostId, 0, POSTS_PER_PAGE);
|
||||
actions.getPostsAfter(channelId, focusedPostId, 0, POSTS_PER_PAGE);
|
||||
|
||||
|
|
@ -224,6 +229,40 @@ class Search extends Component {
|
|||
actions.removeSearchTerms(currentTeamId, item.terms);
|
||||
};
|
||||
|
||||
renderModifiers = ({item}) => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
key={item.modifier}
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
onPress={() => preventDoubleTap(this.setModifierValue, this, item.value)}
|
||||
>
|
||||
<View style={style.modifierItemContainer}>
|
||||
<View style={style.modifierItemWrapper}>
|
||||
<View style={style.modifierItemLabelContainer}>
|
||||
<View style={style.modifierLabelIconContainer}>
|
||||
<AwesomeIcon
|
||||
style={style.modifierLabelIcon}
|
||||
name='plus-square-o'
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={style.modifierItemLabel}
|
||||
>
|
||||
{item.modifier}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={style.modifierItemDescription}>
|
||||
{item.description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
|
||||
renderPost = ({item, index}) => {
|
||||
const {channels, posts, theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
|
@ -297,15 +336,19 @@ class Search extends Component {
|
|||
const {title} = section;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={style.sectionContainer}>
|
||||
<Text style={style.sectionLabel}>
|
||||
{title}
|
||||
</Text>
|
||||
if (title) {
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={style.sectionContainer}>
|
||||
<Text style={style.sectionLabel}>
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return <View/>;
|
||||
};
|
||||
|
||||
renderRecentItem = ({item}) => {
|
||||
|
|
@ -353,6 +396,17 @@ class Search extends Component {
|
|||
actions.searchPosts(currentTeamId, terms, isOrSearch);
|
||||
};
|
||||
|
||||
setModifierValue = (modifier) => {
|
||||
const {value} = this.state;
|
||||
if (!value) {
|
||||
this.handleTextChanged(modifier);
|
||||
} else if (value.endsWith(' ')) {
|
||||
this.handleTextChanged(`${value}${modifier}`);
|
||||
} else {
|
||||
this.handleTextChanged(`${value} ${modifier}`);
|
||||
}
|
||||
};
|
||||
|
||||
setRecentValue = (recent) => {
|
||||
const {terms, isOrSearch} = recent;
|
||||
this.handleTextChanged(terms);
|
||||
|
|
@ -405,7 +459,28 @@ class Search extends Component {
|
|||
|
||||
const {channelName, postId, preview, value} = this.state;
|
||||
const style = getStyleFromTheme(theme);
|
||||
const sections = [];
|
||||
const sections = [{
|
||||
data: [{
|
||||
value: 'from:',
|
||||
modifier: `from:${intl.formatMessage({id: 'mobile.search.from_modifier_title', defaultMessage: 'username'})}`,
|
||||
description: intl.formatMessage({
|
||||
id: 'mobile.search.from_modifier_description',
|
||||
defaultMessage: 'to find posts from specific users'
|
||||
})
|
||||
}, {
|
||||
value: 'in:',
|
||||
modifier: `in:${intl.formatMessage({id: 'mobile.search.in_modifier_title', defaultMessage: 'channel-name'})}`,
|
||||
description: intl.formatMessage({
|
||||
id: 'mobile.search.in_modifier_description',
|
||||
defaultMessage: 'to find posts in specific channels'
|
||||
})
|
||||
}],
|
||||
key: 'modifiers',
|
||||
title: '',
|
||||
renderItem: this.renderModifiers,
|
||||
keyExtractor: this.keyModifierExtractor,
|
||||
ItemSeparatorComponent: this.renderRecentSeparator
|
||||
}];
|
||||
|
||||
if (recent.length) {
|
||||
sections.push({
|
||||
|
|
@ -520,7 +595,8 @@ class Search extends Component {
|
|||
style={style.sectionList}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
sections={sections}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
stickySectionHeadersEnabled={Platform.OS === 'ios'}
|
||||
/>
|
||||
{previewComponent}
|
||||
|
|
@ -559,6 +635,38 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 12,
|
||||
fontWeight: '600'
|
||||
},
|
||||
modifierItemContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: MODIFIER_LABEL_HEIGHT
|
||||
},
|
||||
modifierItemWrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 16
|
||||
},
|
||||
modifierItemLabelContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
modifierLabelIconContainer: {
|
||||
alignItems: 'center',
|
||||
marginRight: 5
|
||||
},
|
||||
modifierLabelIcon: {
|
||||
fontSize: 16,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5)
|
||||
},
|
||||
modifierItemLabel: {
|
||||
fontSize: 14,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
modifierItemDescription: {
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginTop: 5
|
||||
},
|
||||
recentItemContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
|
|
|
|||
|
|
@ -1906,7 +1906,11 @@
|
|||
"mobile.routes.thread_dm": "Direct Message Thread",
|
||||
"mobile.routes.user_profile": "Profile",
|
||||
"mobile.routes.user_profile.send_message": "Send Message",
|
||||
"mobile.search.from_modifier_description": "to find posts from specific users",
|
||||
"mobile.search.from_modifier_title": "username",
|
||||
"mobile.search.jump": "JUMP",
|
||||
"mobile.search.in_modifier_description": "to find posts in specific channels",
|
||||
"mobile.search.in_modifier_title": "channel-name",
|
||||
"mobile.search.no_results": "No Results Found",
|
||||
"mobile.select_team.choose": "Your teams:",
|
||||
"mobile.select_team.join_open": "Open teams you can join",
|
||||
|
|
|
|||
|
|
@ -3656,7 +3656,7 @@ makeerror@1.0.x:
|
|||
|
||||
mattermost-redux@mattermost/mattermost-redux#master:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/09be668d27afd13ea9f3618f4c368c0d0a0db485"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/1afaf513717108b609c4dc677cf49d474624e0ca"
|
||||
dependencies:
|
||||
deep-equal "1.0.1"
|
||||
harmony-reflect "1.5.1"
|
||||
|
|
|
|||
Loading…
Reference in a new issue