forked from Ivasoft/mattermost-mobile
Flagged Posts and Recent Mentions (#1584)
* Fix reply count in post header * Make channel loader to trigger manually * post list selector for search * Include date separators in search results * Flagged posts * Recent Mentions * Retry option for flagged posts and recent mentions * feedback review * Update mattermost-redux
This commit is contained in:
committed by
Harrison Healey
parent
d6ee97c0c7
commit
484a5a11f9
@@ -6,10 +6,10 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ChannelLoader from './channel_loader';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {deviceWidth} = state.device.dimension;
|
||||
return {
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
channelIsLoading: ownProps.channelIsLoading || state.views.channel.loading,
|
||||
deviceWidth,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
|
||||
35
app/components/failed_network_action/cloud.js
Normal file
35
app/components/failed_network_action/cloud.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {View} from 'react-native';
|
||||
import Svg, {Path} from 'react-native-svg';
|
||||
|
||||
export default class CloudSvg extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {color, height, width} = this.props;
|
||||
return (
|
||||
<View style={{height, width, alignItems: 'flex-start'}}>
|
||||
<Svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox='0 0 72 47'
|
||||
>
|
||||
<Path
|
||||
d='M58.464,19.072c0,-5.181 -1.773,-9.599 -5.316,-13.249c-3.545,-3.649 -7.854,-5.474 -12.932,-5.474c-3.597,0 -6.902,0.979 -9.917,2.935c-3.014,1.959 -5.263,4.523 -6.743,7.696c-1.483,-0.739 -2.856,-1.111 -4.126,-1.111c-2.328,0 -4.363,0.769 -6.109,2.301c-1.745,1.535 -2.831,3.466 -3.252,5.792c-2.856,0.952 -5.185,2.672 -6.982,5.156c-1.8,2.487 -2.697,5.316 -2.697,8.489c0,3.915 1.4,7.299 4.204,10.155c2.802,2.857 6.161,4.285 10.076,4.285l43.794,0c3.595,0 6.664,-1.295 9.203,-3.888c2.538,-2.591 3.808,-5.685 3.808,-9.282c0,-3.702 -1.27,-6.848 -3.808,-9.441c-2.539,-2.591 -5.608,-3.888 -9.203,-3.888l0,-0.476Zm-31.294,16.424l17.17,0c-0.842,-1.62 -2.02,-2.92 -3.535,-3.898c-1.515,-0.977 -3.198,-1.467 -5.05,-1.467c-1.852,0 -3.535,0.49 -5.05,1.467c-1.515,0.978 -2.693,2.278 -3.535,3.898l0,0Zm17.338,-12.407c0,-0.782 -0.252,-1.411 -0.757,-1.886c-0.505,-0.474 -1.124,-0.713 -1.852,-0.713c-0.73,0 -1.347,0.239 -1.852,0.713c-0.505,0.475 -0.757,1.104 -0.757,1.886c0,0.783 0.252,1.412 0.757,1.886c0.505,0.476 1.122,0.713 1.852,0.713c0.728,0 1.347,-0.237 1.852,-0.713c0.505,-0.474 0.757,-1.103 0.757,-1.886Zm-12.288,0c0,-0.782 -0.253,-1.411 -0.758,-1.886c-0.505,-0.474 -1.123,-0.713 -1.851,-0.713c-0.73,0 -1.347,0.239 -1.852,0.713c-0.505,0.475 -0.757,1.104 -0.757,1.886c0,0.783 0.252,1.412 0.757,1.886c0.505,0.476 1.122,0.713 1.852,0.713c0.728,0 1.346,-0.237 1.851,-0.713c0.505,-0.474 0.758,-1.103 0.758,-1.886Z'
|
||||
fillRule='evenodd'
|
||||
strokeLinejoin='round'
|
||||
fill={color}
|
||||
/>
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
85
app/components/failed_network_action/index.js
Normal file
85
app/components/failed_network_action/index.js
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import Cloud from './cloud';
|
||||
|
||||
export default class FailedNetworkAction extends PureComponent {
|
||||
static propTypes = {
|
||||
onRetry: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme, onRetry} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<Cloud
|
||||
color={changeOpacity(theme.centerChannelColor, 0.15)}
|
||||
height={76}
|
||||
width={76}
|
||||
/>
|
||||
<FormattedText
|
||||
id='mobile.failed_network_action.title'
|
||||
defaultMessage='No internet connection'
|
||||
style={style.title}
|
||||
/>
|
||||
<FormattedText
|
||||
id='mobile.failed_network_action.description'
|
||||
defaultMessage='There seems to be a problem with your internet connection. Make sure you have an active connection and try again.'
|
||||
style={style.description}
|
||||
/>
|
||||
{onRetry &&
|
||||
<TouchableOpacity
|
||||
onPress={onRetry}
|
||||
style={style.retryContainer}
|
||||
>
|
||||
<FormattedText
|
||||
id='mobile.failed_network_action.retry'
|
||||
defaultMessage='Try Again'
|
||||
style={style.retry}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
title: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
marginBottom: 15,
|
||||
},
|
||||
description: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
fontSize: 17,
|
||||
textAlign: 'center',
|
||||
},
|
||||
retryContainer: {
|
||||
marginTop: 30,
|
||||
},
|
||||
retry: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
};
|
||||
});
|
||||
71
app/components/no_results.js
Normal file
71
app/components/no_results.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Text, View} from 'react-native';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class NoResults extends PureComponent {
|
||||
static propTypes = {
|
||||
description: PropTypes.string,
|
||||
iconName: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
description,
|
||||
iconName,
|
||||
theme,
|
||||
title,
|
||||
} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let icon;
|
||||
if (iconName) {
|
||||
icon = (
|
||||
<IonIcon
|
||||
size={76}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.4)}
|
||||
name={iconName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
{icon}
|
||||
<Text style={style.title}>{title}</Text>
|
||||
{description &&
|
||||
<Text style={style.description}>{description}</Text>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
title: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
marginBottom: 15,
|
||||
},
|
||||
description: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
fontSize: 17,
|
||||
textAlign: 'center',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -266,7 +266,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 30,
|
||||
height: 35,
|
||||
minWidth: 40,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
|
||||
44
app/components/post_separator.js
Normal file
44
app/components/post_separator.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const SEPARATOR_HEIGHT = 3;
|
||||
|
||||
export default class PostSeparator extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={[style.separatorContainer, style.postsSeparator]}>
|
||||
<View style={style.separator}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
separatorContainer: {
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
height: SEPARATOR_HEIGHT,
|
||||
},
|
||||
postsSeparator: {
|
||||
height: 15,
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -180,58 +180,45 @@ export default class SettingsDrawer extends PureComponent {
|
||||
});
|
||||
|
||||
goToEditProfile = preventDoubleTap(() => {
|
||||
const {currentUser, navigator, theme} = this.props;
|
||||
const {currentUser} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
this.closeSettingsDrawer();
|
||||
navigator.showModal({
|
||||
screen: 'EditProfile',
|
||||
title: formatMessage({id: 'mobile.routes.edit_profile', defaultMessage: 'Edit Profile'}),
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-settings',
|
||||
icon: this.closeButton,
|
||||
}],
|
||||
},
|
||||
passProps: {
|
||||
currentUser,
|
||||
},
|
||||
});
|
||||
this.openModal(
|
||||
'EditProfile',
|
||||
formatMessage({id: 'mobile.routes.edit_profile', defaultMessage: 'Edit Profile'}),
|
||||
{currentUser}
|
||||
);
|
||||
});
|
||||
|
||||
goToFlagged = preventDoubleTap(() => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
this.closeSettingsDrawer();
|
||||
this.openModal(
|
||||
'FlaggedPosts',
|
||||
formatMessage({id: 'search_header.title3', defaultMessage: 'Flagged Posts'}),
|
||||
);
|
||||
});
|
||||
|
||||
goToMentions = preventDoubleTap(() => {
|
||||
const {intl} = this.context;
|
||||
|
||||
this.closeSettingsDrawer();
|
||||
this.openModal(
|
||||
'RecentMentions',
|
||||
intl.formatMessage({id: 'search_header.title2', defaultMessage: 'Recent Mentions'}),
|
||||
);
|
||||
});
|
||||
|
||||
goToSettings = preventDoubleTap(() => {
|
||||
const {intl} = this.context;
|
||||
const {navigator, theme} = this.props;
|
||||
|
||||
this.closeSettingsDrawer();
|
||||
navigator.showModal({
|
||||
screen: 'Settings',
|
||||
title: intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-settings',
|
||||
icon: this.closeButton,
|
||||
}],
|
||||
},
|
||||
});
|
||||
this.openModal(
|
||||
'Settings',
|
||||
intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
|
||||
);
|
||||
});
|
||||
|
||||
logout = preventDoubleTap(() => {
|
||||
@@ -240,6 +227,32 @@ export default class SettingsDrawer extends PureComponent {
|
||||
InteractionManager.runAfterInteractions(logout);
|
||||
});
|
||||
|
||||
openModal = (screen, title, passProps) => {
|
||||
const {navigator, theme} = this.props;
|
||||
|
||||
this.closeSettingsDrawer();
|
||||
navigator.showModal({
|
||||
screen,
|
||||
title,
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-settings',
|
||||
icon: this.closeButton,
|
||||
}],
|
||||
},
|
||||
passProps,
|
||||
});
|
||||
};
|
||||
|
||||
renderUserStatusIcon = (userId) => {
|
||||
return (
|
||||
<UserStatus
|
||||
@@ -282,10 +295,34 @@ export default class SettingsDrawer extends PureComponent {
|
||||
<DrawerItem
|
||||
labelComponent={this.renderUserStatusLabel(currentUser.id)}
|
||||
leftComponent={this.renderUserStatusIcon(currentUser.id)}
|
||||
separator={true}
|
||||
separator={false}
|
||||
onPress={this.handleSetStatus}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.separator}/>
|
||||
<View style={style.block}>
|
||||
<DrawerItem
|
||||
defaultMessage='Recent Mentions'
|
||||
i18nId='search_header.title2'
|
||||
iconName='ios-at-outline'
|
||||
iconType='ion'
|
||||
onPress={this.goToMentions}
|
||||
separator={true}
|
||||
theme={theme}
|
||||
/>
|
||||
<DrawerItem
|
||||
defaultMessage='Flagged Posts'
|
||||
i18nId='search_header.title3'
|
||||
iconName='ios-flag-outline'
|
||||
iconType='ion'
|
||||
onPress={this.goToFlagged}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.separator}/>
|
||||
<View style={style.block}>
|
||||
<DrawerItem
|
||||
defaultMessage='Settings'
|
||||
i18nId='mobile.routes.settings'
|
||||
|
||||
276
app/screens/flagged_posts/flagged_posts.js
Normal file
276
app/screens/flagged_posts/flagged_posts.js
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Keyboard,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
SafeAreaView,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import DateHeader from 'app/components/post_list/date_header';
|
||||
import FailedNetworkAction from 'app/components/failed_network_action';
|
||||
import NoResults from 'app/components/no_results';
|
||||
import PostSeparator from 'app/components/post_separator';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import SearchResultPost from 'app/screens/search/search_result_post';
|
||||
import ChannelDisplayName from 'app/screens/search/channel_display_name';
|
||||
import {DATE_LINE} from 'app/selectors/post_list';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class FlaggedPosts extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
clearSearch: PropTypes.func.isRequired,
|
||||
loadChannelsByTeamName: PropTypes.func.isRequired,
|
||||
loadThreadIfNecessary: PropTypes.func.isRequired,
|
||||
getFlaggedPosts: PropTypes.func.isRequired,
|
||||
selectFocusedPostId: PropTypes.func.isRequired,
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
didFail: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
navigator: PropTypes.object,
|
||||
postIds: PropTypes.array,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
postIds: [],
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
props.actions.clearSearch();
|
||||
props.actions.getFlaggedPosts();
|
||||
|
||||
this.state = {
|
||||
managedConfig: {},
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.listenerId = mattermostManaged.addEventListener('change', this.setManagedConfig);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setManagedConfig();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
mattermostManaged.removeEventListener(this.listenerId);
|
||||
}
|
||||
|
||||
goToThread = (post) => {
|
||||
const {actions, navigator, theme} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
Keyboard.dismiss();
|
||||
actions.loadThreadIfNecessary(rootId, channelId);
|
||||
actions.selectPost(rootId);
|
||||
|
||||
const options = {
|
||||
screen: 'Thread',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
passProps: {
|
||||
channelId,
|
||||
rootId,
|
||||
},
|
||||
};
|
||||
|
||||
navigator.push(options);
|
||||
};
|
||||
|
||||
handleClosePermalink = () => {
|
||||
const {actions} = this.props;
|
||||
actions.selectFocusedPostId('');
|
||||
this.showingPermalink = false;
|
||||
};
|
||||
|
||||
handlePermalinkPress = (postId, teamName) => {
|
||||
this.props.actions.loadChannelsByTeamName(teamName);
|
||||
this.showPermalinkView(postId, true);
|
||||
};
|
||||
|
||||
keyExtractor = (item) => item;
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'close-settings') {
|
||||
this.props.navigator.dismissModal({
|
||||
animationType: 'slide-down',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
previewPost = (post) => {
|
||||
Keyboard.dismiss();
|
||||
|
||||
this.showPermalinkView(post.id, false);
|
||||
};
|
||||
|
||||
renderEmpty = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {theme} = this.props;
|
||||
|
||||
return (
|
||||
<NoResults
|
||||
description={formatMessage({
|
||||
id: 'mobile.flagged_posts.empty_description',
|
||||
defaultMessage: 'Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.',
|
||||
})}
|
||||
iconName='ios-flag-outline'
|
||||
title={formatMessage({id: 'mobile.flagged_posts.empty_title', defaultMessage: 'No Flagged Posts'})}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderPost = ({item, index}) => {
|
||||
const {postIds, theme} = this.props;
|
||||
const {managedConfig} = this.state;
|
||||
if (item.indexOf(DATE_LINE) === 0) {
|
||||
const date = new Date(item.substring(DATE_LINE.length));
|
||||
|
||||
return (
|
||||
<DateHeader
|
||||
date={date}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let separator;
|
||||
const nextPost = postIds[index + 1];
|
||||
if (nextPost && nextPost.indexOf(DATE_LINE) === -1) {
|
||||
separator = <PostSeparator theme={theme}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ChannelDisplayName postId={item}/>
|
||||
<SearchResultPost
|
||||
postId={item}
|
||||
previewPost={this.previewPost}
|
||||
goToThread={this.goToThread}
|
||||
navigator={this.props.navigator}
|
||||
onPermalinkPress={this.handlePermalinkPress}
|
||||
managedConfig={managedConfig}
|
||||
showFullDate={false}
|
||||
/>
|
||||
{separator}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
setManagedConfig = async (config) => {
|
||||
let nextConfig = config;
|
||||
if (!nextConfig) {
|
||||
nextConfig = await mattermostManaged.getLocalConfig();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
managedConfig: nextConfig,
|
||||
});
|
||||
};
|
||||
|
||||
showPermalinkView = (postId, isPermalink) => {
|
||||
const {actions, navigator} = this.props;
|
||||
|
||||
actions.selectFocusedPostId(postId);
|
||||
|
||||
if (!this.showingPermalink) {
|
||||
const options = {
|
||||
screen: 'Permalink',
|
||||
animationType: 'none',
|
||||
backButtonTitle: '',
|
||||
overrideBackPress: true,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
screenBackgroundColor: changeOpacity('#000', 0.2),
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
passProps: {
|
||||
isPermalink,
|
||||
onClose: this.handleClosePermalink,
|
||||
onPermalinkPress: this.handlePermalinkPress,
|
||||
},
|
||||
};
|
||||
|
||||
this.showingPermalink = true;
|
||||
navigator.showModal(options);
|
||||
}
|
||||
};
|
||||
|
||||
retry = () => {
|
||||
this.props.actions.getFlaggedPosts();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {didFail, isLoading, postIds, theme} = this.props;
|
||||
|
||||
let component;
|
||||
if (didFail) {
|
||||
component = (
|
||||
<FailedNetworkAction
|
||||
onRetry={this.retry}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
} else if (isLoading) {
|
||||
component = (
|
||||
<ChannelLoader channelIsLoading={true}/>
|
||||
);
|
||||
} else if (postIds.length) {
|
||||
component = (
|
||||
<FlatList
|
||||
ref='list'
|
||||
contentContainerStyle={style.sectionList}
|
||||
data={postIds}
|
||||
keyExtractor={this.keyExtractor}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
renderItem={this.renderPost}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
component = this.renderEmpty();
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={style.container}>
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
{component}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
47
app/screens/flagged_posts/index.js
Normal file
47
app/screens/flagged_posts/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {selectFocusedPostId, selectPost} from 'mattermost-redux/actions/posts';
|
||||
import {clearSearch, getFlaggedPosts} from 'mattermost-redux/actions/search';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel';
|
||||
import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list';
|
||||
|
||||
import FlaggedPosts from './flagged_posts';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const preparePostIds = makePreparePostIdsForSearchPosts();
|
||||
return (state) => {
|
||||
const postIds = preparePostIds(state, state.entities.search.flagged);
|
||||
const {flaggedPosts: flaggedPostsRequest} = state.requests.search;
|
||||
const isLoading = flaggedPostsRequest.status === RequestStatus.STARTED ||
|
||||
flaggedPostsRequest.status === RequestStatus.NOT_STARTED;
|
||||
|
||||
return {
|
||||
postIds,
|
||||
isLoading,
|
||||
didFail: flaggedPostsRequest.status === RequestStatus.FAILURE,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
clearSearch,
|
||||
loadChannelsByTeamName,
|
||||
loadThreadIfNecessary,
|
||||
getFlaggedPosts,
|
||||
selectFocusedPostId,
|
||||
selectPost,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(FlaggedPosts);
|
||||
@@ -21,6 +21,7 @@ import DisplaySettings from 'app/screens/settings/display_settings';
|
||||
import EditChannel from 'app/screens/edit_channel';
|
||||
import EditPost from 'app/screens/edit_post';
|
||||
import EditProfile from 'app/screens/edit_profile';
|
||||
import FlaggedPosts from 'app/screens/flagged_posts';
|
||||
import ImagePreview from 'app/screens/image_preview';
|
||||
import TextPreview from 'app/screens/text_preview';
|
||||
import Login from 'app/screens/login';
|
||||
@@ -37,6 +38,7 @@ import NotificationSettingsMentionsKeywords from 'app/screens/settings/notificat
|
||||
import NotificationSettingsMobile from 'app/screens/settings/notification_settings_mobile';
|
||||
import OptionsModal from 'app/screens/options_modal';
|
||||
import Permalink from 'app/screens/permalink';
|
||||
import RecentMentions from 'app/screens/recent_mentions';
|
||||
import Root from 'app/screens/root';
|
||||
import SSO from 'app/screens/sso';
|
||||
import Search from 'app/screens/search';
|
||||
@@ -81,6 +83,7 @@ export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(EditChannel), store, Provider);
|
||||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider);
|
||||
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(EditProfile), store, Provider);
|
||||
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(FlaggedPosts), store, Provider);
|
||||
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(ImagePreview), store, Provider);
|
||||
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(TextPreview), store, Provider);
|
||||
Navigation.registerComponent('Login', () => wrapWithContextProvider(Login), store, Provider);
|
||||
@@ -97,6 +100,7 @@ export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('NotificationSettingsMobile', () => wrapWithContextProvider(NotificationSettingsMobile), store, Provider);
|
||||
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(OptionsModal), store, Provider);
|
||||
Navigation.registerComponent('Permalink', () => wrapWithContextProvider(Permalink), store, Provider);
|
||||
Navigation.registerComponent('RecentMentions', () => wrapWithContextProvider(RecentMentions), store, Provider);
|
||||
Navigation.registerComponent('Root', () => Root, store, Provider);
|
||||
Navigation.registerComponent('Search', () => wrapWithContextProvider(Search), store, Provider);
|
||||
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(SelectServer), store, Provider);
|
||||
|
||||
47
app/screens/recent_mentions/index.js
Normal file
47
app/screens/recent_mentions/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {selectFocusedPostId, selectPost} from 'mattermost-redux/actions/posts';
|
||||
import {clearSearch, getRecentMentions} from 'mattermost-redux/actions/search';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel';
|
||||
import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list';
|
||||
|
||||
import RecentMentions from './recent_mentions';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const preparePostIds = makePreparePostIdsForSearchPosts();
|
||||
return (state) => {
|
||||
const postIds = preparePostIds(state, state.entities.search.results);
|
||||
const {recentMentions: recentMentionsRequest} = state.requests.search;
|
||||
const isLoading = recentMentionsRequest.status === RequestStatus.STARTED ||
|
||||
recentMentionsRequest.status === RequestStatus.NOT_STARTED;
|
||||
|
||||
return {
|
||||
postIds,
|
||||
isLoading,
|
||||
didFail: recentMentionsRequest.status === RequestStatus.FAILURE,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
clearSearch,
|
||||
loadChannelsByTeamName,
|
||||
loadThreadIfNecessary,
|
||||
getRecentMentions,
|
||||
selectFocusedPostId,
|
||||
selectPost,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(RecentMentions);
|
||||
276
app/screens/recent_mentions/recent_mentions.js
Normal file
276
app/screens/recent_mentions/recent_mentions.js
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Keyboard,
|
||||
FlatList,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import DateHeader from 'app/components/post_list/date_header';
|
||||
import FailedNetworkAction from 'app/components/failed_network_action';
|
||||
import NoResults from 'app/components/no_results';
|
||||
import PostSeparator from 'app/components/post_separator';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import SearchResultPost from 'app/screens/search/search_result_post';
|
||||
import ChannelDisplayName from 'app/screens/search/channel_display_name';
|
||||
import {DATE_LINE} from 'app/selectors/post_list';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class RecentMentions extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
clearSearch: PropTypes.func.isRequired,
|
||||
loadChannelsByTeamName: PropTypes.func.isRequired,
|
||||
loadThreadIfNecessary: PropTypes.func.isRequired,
|
||||
getRecentMentions: PropTypes.func.isRequired,
|
||||
selectFocusedPostId: PropTypes.func.isRequired,
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
didFail: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
navigator: PropTypes.object,
|
||||
postIds: PropTypes.array,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
postIds: [],
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
props.actions.clearSearch();
|
||||
props.actions.getRecentMentions();
|
||||
|
||||
this.state = {
|
||||
managedConfig: {},
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.listenerId = mattermostManaged.addEventListener('change', this.setManagedConfig);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setManagedConfig();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
mattermostManaged.removeEventListener(this.listenerId);
|
||||
}
|
||||
|
||||
goToThread = (post) => {
|
||||
const {actions, navigator, theme} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
Keyboard.dismiss();
|
||||
actions.loadThreadIfNecessary(rootId, channelId);
|
||||
actions.selectPost(rootId);
|
||||
|
||||
const options = {
|
||||
screen: 'Thread',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
passProps: {
|
||||
channelId,
|
||||
rootId,
|
||||
},
|
||||
};
|
||||
|
||||
navigator.push(options);
|
||||
};
|
||||
|
||||
handleClosePermalink = () => {
|
||||
const {actions} = this.props;
|
||||
actions.selectFocusedPostId('');
|
||||
this.showingPermalink = false;
|
||||
};
|
||||
|
||||
handlePermalinkPress = (postId, teamName) => {
|
||||
this.props.actions.loadChannelsByTeamName(teamName);
|
||||
this.showPermalinkView(postId, true);
|
||||
};
|
||||
|
||||
keyExtractor = (item) => item;
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'close-settings') {
|
||||
this.props.navigator.dismissModal({
|
||||
animationType: 'slide-down',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
previewPost = (post) => {
|
||||
Keyboard.dismiss();
|
||||
|
||||
this.showPermalinkView(post.id, false);
|
||||
};
|
||||
|
||||
renderEmpty = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {theme} = this.props;
|
||||
|
||||
return (
|
||||
<NoResults
|
||||
description={formatMessage({
|
||||
id: 'mobile.recent_mentions.empty_description',
|
||||
defaultMessage: 'Messages containing your username and other words that trigger mentions will appear here.',
|
||||
})}
|
||||
iconName='ios-at-outline'
|
||||
title={formatMessage({id: 'mobile.recent_mentions.empty_title', defaultMessage: 'No Recent Mentions'})}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderPost = ({item, index}) => {
|
||||
const {postIds, theme} = this.props;
|
||||
const {managedConfig} = this.state;
|
||||
if (item.indexOf(DATE_LINE) === 0) {
|
||||
const date = new Date(item.substring(DATE_LINE.length));
|
||||
|
||||
return (
|
||||
<DateHeader
|
||||
date={date}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let separator;
|
||||
const nextPost = postIds[index + 1];
|
||||
if (nextPost && nextPost.indexOf(DATE_LINE) === -1) {
|
||||
separator = <PostSeparator theme={theme}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ChannelDisplayName postId={item}/>
|
||||
<SearchResultPost
|
||||
postId={item}
|
||||
previewPost={this.previewPost}
|
||||
goToThread={this.goToThread}
|
||||
navigator={this.props.navigator}
|
||||
onPermalinkPress={this.handlePermalinkPress}
|
||||
managedConfig={managedConfig}
|
||||
showFullDate={false}
|
||||
/>
|
||||
{separator}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
setManagedConfig = async (config) => {
|
||||
let nextConfig = config;
|
||||
if (!nextConfig) {
|
||||
nextConfig = await mattermostManaged.getLocalConfig();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
managedConfig: nextConfig,
|
||||
});
|
||||
};
|
||||
|
||||
showPermalinkView = (postId, isPermalink) => {
|
||||
const {actions, navigator} = this.props;
|
||||
|
||||
actions.selectFocusedPostId(postId);
|
||||
|
||||
if (!this.showingPermalink) {
|
||||
const options = {
|
||||
screen: 'Permalink',
|
||||
animationType: 'none',
|
||||
backButtonTitle: '',
|
||||
overrideBackPress: true,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
screenBackgroundColor: changeOpacity('#000', 0.2),
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
passProps: {
|
||||
isPermalink,
|
||||
onClose: this.handleClosePermalink,
|
||||
onPermalinkPress: this.handlePermalinkPress,
|
||||
},
|
||||
};
|
||||
|
||||
this.showingPermalink = true;
|
||||
navigator.showModal(options);
|
||||
}
|
||||
};
|
||||
|
||||
retry = () => {
|
||||
this.props.actions.getRecentMentions();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {didFail, isLoading, postIds, theme} = this.props;
|
||||
|
||||
let component;
|
||||
if (didFail) {
|
||||
component = (
|
||||
<FailedNetworkAction
|
||||
onRetry={this.retry}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
} else if (isLoading) {
|
||||
component = (
|
||||
<ChannelLoader channelIsLoading={true}/>
|
||||
);
|
||||
} else if (postIds.length) {
|
||||
component = (
|
||||
<FlatList
|
||||
ref='list'
|
||||
contentContainerStyle={style.sectionList}
|
||||
data={postIds}
|
||||
keyExtractor={this.keyExtractor}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
renderItem={this.renderPost}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
component = this.renderEmpty();
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={style.container}>
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
{component}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -12,24 +12,30 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list';
|
||||
import {handleSearchDraftChanged} from 'app/actions/views/search';
|
||||
|
||||
import Search from './search';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
const {recent} = state.entities.search;
|
||||
const {searchPosts: searchRequest} = state.requests.search;
|
||||
function makeMapStateToProps() {
|
||||
const preparePostIds = makePreparePostIdsForSearchPosts();
|
||||
|
||||
return {
|
||||
currentTeamId,
|
||||
currentChannelId,
|
||||
isLandscape: isLandscape(state),
|
||||
postIds: state.entities.search.results,
|
||||
recent: recent[currentTeamId],
|
||||
searchingStatus: searchRequest.status,
|
||||
theme: getTheme(state),
|
||||
return (state) => {
|
||||
const postIds = preparePostIds(state, state.entities.search.results);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
const {recent} = state.entities.search;
|
||||
const {searchPosts: searchRequest} = state.requests.search;
|
||||
|
||||
return {
|
||||
currentTeamId,
|
||||
currentChannelId,
|
||||
isLandscape: isLandscape(state),
|
||||
postIds,
|
||||
recent: recent[currentTeamId],
|
||||
searchingStatus: searchRequest.status,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,4 +54,4 @@ function mapDispatchToProps(dispatch) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Search);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(Search);
|
||||
|
||||
@@ -20,6 +20,7 @@ import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import Autocomplete from 'app/components/autocomplete';
|
||||
import DateHeader from 'app/components/post_list/date_header';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
import PostListRetry from 'app/components/post_list_retry';
|
||||
@@ -27,6 +28,7 @@ import SafeAreaView from 'app/components/safe_area_view';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import {DATE_LINE} from 'app/selectors/post_list';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
@@ -259,6 +261,15 @@ export default class Search extends PureComponent {
|
||||
actions.removeSearchTerms(currentTeamId, item.terms);
|
||||
});
|
||||
|
||||
renderDateHeader = (date, index) => {
|
||||
return (
|
||||
<DateHeader
|
||||
date={date}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderModifiers = ({item}) => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -306,8 +317,14 @@ export default class Search extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
if (item.indexOf(DATE_LINE) === 0) {
|
||||
const date = item.substring(DATE_LINE.length);
|
||||
return this.renderDateHeader(new Date(date), index);
|
||||
}
|
||||
|
||||
let separator;
|
||||
if (index === postIds.length - 1) {
|
||||
const nextPost = postIds[index + 1];
|
||||
if (nextPost && nextPost.indexOf(DATE_LINE) === -1) {
|
||||
separator = this.renderPostSeparator();
|
||||
}
|
||||
|
||||
@@ -569,7 +586,6 @@ export default class Search extends PureComponent {
|
||||
title: intl.formatMessage({id: 'search_header.results', defaultMessage: 'Search Results'}),
|
||||
renderItem: this.renderPost,
|
||||
keyExtractor: this.keyPostExtractor,
|
||||
ItemSeparatorComponent: this.renderPostSeparator,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ export default class SearchResultPost extends PureComponent {
|
||||
onPermalinkPress: PropTypes.func.isRequired,
|
||||
postId: PropTypes.string.isRequired,
|
||||
previewPost: PropTypes.func.isRequired,
|
||||
showFullDate: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
showFullDate: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -35,7 +40,7 @@ export default class SearchResultPost extends PureComponent {
|
||||
postId={this.props.postId}
|
||||
{...postComponentProps}
|
||||
isSearchResult={true}
|
||||
showFullDate={true}
|
||||
showFullDate={this.props.showFullDate}
|
||||
navigator={this.props.navigator}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -80,3 +80,44 @@ export function makePreparePostIdsForPostList() {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function makePreparePostIdsForSearchPosts() {
|
||||
const getMyPosts = makeGetPostsForIds();
|
||||
|
||||
return createIdsSelector(
|
||||
(state, postIds) => getMyPosts(state, postIds),
|
||||
getCurrentUser,
|
||||
(posts, currentUser) => {
|
||||
if (posts.length === 0 || !currentUser) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out = [];
|
||||
let lastDate = null;
|
||||
for (let i = 0; i < posts.length; i++) {
|
||||
const post = posts[i];
|
||||
|
||||
// give chance for the post to be loaded
|
||||
if (!post) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (post.state === Posts.POST_DELETED && post.user_id === currentUser.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Push on a date header if the last post was on a different day than the current one
|
||||
const postDate = new Date(post.create_at);
|
||||
|
||||
if (!lastDate || lastDate.toDateString() !== postDate.toDateString()) {
|
||||
out.push(DATE_LINE + postDate.toString());
|
||||
lastDate = postDate;
|
||||
}
|
||||
out.push(post.id);
|
||||
}
|
||||
|
||||
// Flip it back to newest to oldest
|
||||
return out;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -224,10 +224,19 @@ function cleanupState(action, keepCurrent = false) {
|
||||
}, []);
|
||||
|
||||
let searchResults = [];
|
||||
if (payload.entities.search && payload.entities.search.results.length) {
|
||||
const {results} = payload.entities.search;
|
||||
searchResults = results;
|
||||
postIdsToKeep.push(...results);
|
||||
let flaggedPosts = [];
|
||||
if (payload.entities.search) {
|
||||
if (payload.entities.search.results.length) {
|
||||
const {results} = payload.entities.search;
|
||||
searchResults = results;
|
||||
postIdsToKeep.push(...results);
|
||||
}
|
||||
|
||||
if (payload.entities.search.flagged.length) {
|
||||
const {flagged} = payload.entities.search;
|
||||
flaggedPosts = flagged;
|
||||
postIdsToKeep.push(...flagged);
|
||||
}
|
||||
}
|
||||
|
||||
postIdsToKeep.forEach((postId) => {
|
||||
@@ -314,6 +323,7 @@ function cleanupState(action, keepCurrent = false) {
|
||||
search: {
|
||||
...resetPayload.entities.search,
|
||||
results: searchResults,
|
||||
flagged: flaggedPosts,
|
||||
},
|
||||
teams: resetPayload.entities.teams,
|
||||
users: payload.entities.users,
|
||||
|
||||
@@ -2108,11 +2108,16 @@
|
||||
"mobile.extension.permission": "Mattermost needs access to the device storage to share files.",
|
||||
"mobile.extension.posting": "Posting...",
|
||||
"mobile.extension.title": "Share in Mattermost",
|
||||
"mobile.failed_network_action.description": "There seems to be a problem with your internet connection. Make sure you have an active connection and try again.",
|
||||
"mobile.failed_network_action.title": "No internet connection",
|
||||
"mobile.failed_network_action.retry": "Try Again",
|
||||
"mobile.file_upload.camera": "Take Photo or Video",
|
||||
"mobile.file_upload.library": "Photo Library",
|
||||
"mobile.file_upload.max_warning": "Uploads limited to 5 files maximum.",
|
||||
"mobile.file_upload.more": "More",
|
||||
"mobile.file_upload.video": "Video Library",
|
||||
"mobile.flagged_posts.empty_title": "No Flagged Posts",
|
||||
"mobile.flagged_posts.empty_description": "Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.",
|
||||
"mobile.help.title": "Help",
|
||||
"mobile.image_preview.deleted_post_message": "This post and its files have been deleted. The previewer will now be closed.",
|
||||
"mobile.image_preview.deleted_post_title": "Post Deleted",
|
||||
@@ -2199,6 +2204,8 @@
|
||||
"mobile.post_textbox.uploadFailedDesc": "Some attachments failed to upload to the server, Are you sure you want to post the message?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Attachment failure",
|
||||
"mobile.posts_view.moreMsg": "More New Messages Above",
|
||||
"mobile.recent_mentions.empty_title": "No Recent Mentions",
|
||||
"mobile.recent_mentions.empty_description": "Messages containing your username and other words that trigger mentions will appear here.",
|
||||
"mobile.rename_channel.display_name_maxLength": "Channel name must be less than {maxLength, number} characters",
|
||||
"mobile.rename_channel.display_name_minLength": "Channel name must be {minLength, number} or more characters",
|
||||
"mobile.rename_channel.display_name_required": "Channel name is required",
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@@ -2175,7 +2175,7 @@ commonmark-react-renderer@mattermost/commonmark-react-renderer:
|
||||
pascalcase "^0.1.1"
|
||||
xss-filters "^1.2.6"
|
||||
|
||||
"commonmark@mattermost/commonmark.js":
|
||||
commonmark@mattermost/commonmark.js:
|
||||
version "0.28.0"
|
||||
resolved "https://codeload.github.com/mattermost/commonmark.js/tar.gz/1496b5d11f245e00aae51f8fa1b2c4f12b4ddd7b"
|
||||
dependencies:
|
||||
@@ -3655,10 +3655,16 @@ iconv-lite@0.4.13:
|
||||
version "0.4.13"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
|
||||
|
||||
iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13:
|
||||
iconv-lite@0.4.19, iconv-lite@^0.4.17:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
|
||||
iconv-lite@~0.4.13:
|
||||
version "0.4.21"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798"
|
||||
dependencies:
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
ignore@^3.3.3:
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6"
|
||||
@@ -4637,7 +4643,7 @@ map-visit@^1.0.0:
|
||||
|
||||
mattermost-redux@mattermost/mattermost-redux:
|
||||
version "1.2.0"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/10d691f386e1dbb7669ae549bbe81b855ef4c505"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/3d058a0d98095885e4712ad6c0aadf7e3913ad1c"
|
||||
dependencies:
|
||||
deep-equal "1.0.1"
|
||||
form-data "2.3.1"
|
||||
@@ -6702,6 +6708,10 @@ safe-regex@^1.1.0:
|
||||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
safer-buffer@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
||||
sane@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sane/-/sane-2.0.0.tgz#99cb79f21f4a53a69d4d0cd957c2db04024b8eb2"
|
||||
|
||||
Reference in New Issue
Block a user