* Add automatic and manual timezone support Update users timezone and scrollTo selected timezone in SelectTimezone coponent Clean styles for SelectTimezone screen Add auto-timezone update when login or enter app Hide timezone feature behind config Fix requested changes Parse SupportedTimezone from config Add localTime and localized post stamps Add trailing commas Include all timezone util methods from redux Remove EnableTimezoneSelection flag WIP get timezones from server Pull supportedTimezones from api Minor fixes Remove wrapWithPreventDoubleTap Revert back to react-intl formatDate Include timeZone prop in FormattedTime Refactor Timezone row into component Minor cosmetic changes Add minimum server support for the timezone feature Move getSupportedTimezones to componentWillMount Move autoUpdateTimezone function to handleSuccessfulLogin Specify user timezone in profile_header Remove format props from FormattedTime Add ExperimentalTimezone flag Replace Client().getServerVersion() with entities.general.serverVersion Add isTimezoneEnabled helper function Move isMinimumServerVersion to utils/timezone.js * Fix style errors * Remove date-time-format-timezone polyfill * Feedback changes * Use timezone selector from redux * Explicitly pass hour12 props to intl.formatDate * Update package-lock * Revert iOS project file changes * Fix license header * Include timezone related paths in modulePaths * Fix license header * Fix minor issue with rebasing * Fix issue with getconfig in GeneralSettings * Update package-lock
337 lines
10 KiB
JavaScript
337 lines
10 KiB
JavaScript
// 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 {
|
|
Platform,
|
|
Text,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native';
|
|
|
|
import FormattedText from 'app/components/formatted_text';
|
|
import FormattedTime from 'app/components/formatted_time';
|
|
import FormattedDate from 'app/components/formatted_date';
|
|
import ReplyIcon from 'app/components/reply_icon';
|
|
import FlagIcon from 'app/components/flag_icon';
|
|
import {emptyFunction} from 'app/utils/general';
|
|
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|
|
|
export default class PostHeader extends PureComponent {
|
|
static propTypes = {
|
|
commentCount: PropTypes.number,
|
|
commentedOnDisplayName: PropTypes.string,
|
|
createAt: PropTypes.number.isRequired,
|
|
displayName: PropTypes.string,
|
|
enablePostUsernameOverride: PropTypes.bool,
|
|
fromWebHook: PropTypes.bool,
|
|
isPendingOrFailedPost: PropTypes.bool,
|
|
isSearchResult: PropTypes.bool,
|
|
isSystemMessage: PropTypes.bool,
|
|
fromAutoResponder: PropTypes.bool,
|
|
militaryTime: PropTypes.bool,
|
|
onPress: PropTypes.func,
|
|
onUsernamePress: PropTypes.func,
|
|
overrideUsername: PropTypes.string,
|
|
renderReplies: PropTypes.bool,
|
|
shouldRenderReplyButton: PropTypes.bool,
|
|
showFullDate: PropTypes.bool,
|
|
theme: PropTypes.object.isRequired,
|
|
username: PropTypes.string,
|
|
userTimezone: PropTypes.string,
|
|
isFlagged: PropTypes.bool,
|
|
enableTimezone: PropTypes.bool,
|
|
};
|
|
|
|
static defaultProps = {
|
|
commentCount: 0,
|
|
onPress: emptyFunction,
|
|
onUsernamePress: emptyFunction,
|
|
};
|
|
|
|
handleUsernamePress = () => {
|
|
if (this.props.username) {
|
|
this.props.onUsernamePress(this.props.username);
|
|
}
|
|
}
|
|
|
|
getDisplayName = (style) => {
|
|
const {
|
|
enablePostUsernameOverride,
|
|
fromWebHook,
|
|
isSystemMessage,
|
|
fromAutoResponder,
|
|
overrideUsername,
|
|
} = this.props;
|
|
|
|
if (fromWebHook) {
|
|
let name = this.props.displayName;
|
|
if (overrideUsername && enablePostUsernameOverride) {
|
|
name = overrideUsername;
|
|
}
|
|
|
|
return (
|
|
<View style={style.indicatorContainer}>
|
|
<Text style={style.displayName}>
|
|
{name}
|
|
</Text>
|
|
<FormattedText
|
|
id='post_info.bot'
|
|
defaultMessage='BOT'
|
|
style={style.bot}
|
|
/>
|
|
</View>
|
|
);
|
|
} else if (fromAutoResponder) {
|
|
let name = this.props.displayName;
|
|
if (overrideUsername && enablePostUsernameOverride) {
|
|
name = overrideUsername;
|
|
}
|
|
|
|
return (
|
|
<View style={style.indicatorContainer}>
|
|
<Text style={style.displayName}>
|
|
{name}
|
|
</Text>
|
|
<FormattedText
|
|
id='post_info.auto_responder'
|
|
defaultMessage='AUTOMATIC REPLY'
|
|
style={style.bot}
|
|
/>
|
|
</View>
|
|
);
|
|
} else if (isSystemMessage) {
|
|
return (
|
|
<FormattedText
|
|
id='post_info.system'
|
|
defaultMessage='System'
|
|
style={style.displayName}
|
|
/>
|
|
);
|
|
} else if (this.props.displayName) {
|
|
return (
|
|
<TouchableOpacity onPress={this.handleUsernamePress}>
|
|
<Text style={style.displayName}>
|
|
{this.props.displayName}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<FormattedText
|
|
id='channel_loader.someone'
|
|
defaultMessage='Someone'
|
|
style={style.displayName}
|
|
/>
|
|
);
|
|
};
|
|
|
|
renderCommentedOnMessage = (style) => {
|
|
if (!this.props.renderReplies || !this.props.commentedOnDisplayName) {
|
|
return null;
|
|
}
|
|
|
|
const displayName = this.props.commentedOnDisplayName;
|
|
|
|
let name;
|
|
if (displayName) {
|
|
name = displayName;
|
|
} else {
|
|
name = (
|
|
<FormattedText
|
|
id='channel_loader.someone'
|
|
defaultMessage='Someone'
|
|
/>
|
|
);
|
|
}
|
|
|
|
let apostrophe;
|
|
if (displayName && displayName.slice(-1) === 's') {
|
|
apostrophe = '\'';
|
|
} else {
|
|
apostrophe = '\'s';
|
|
}
|
|
|
|
return (
|
|
<FormattedText
|
|
id='post_body.commentedOn'
|
|
defaultMessage='Commented on {name}{apostrophe} message: '
|
|
values={{
|
|
name,
|
|
apostrophe,
|
|
}}
|
|
style={style.commentedOn}
|
|
/>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
commentedOnDisplayName,
|
|
commentCount,
|
|
createAt,
|
|
isPendingOrFailedPost,
|
|
isSearchResult,
|
|
userTimezone,
|
|
militaryTime,
|
|
onPress,
|
|
renderReplies,
|
|
shouldRenderReplyButton,
|
|
showFullDate,
|
|
theme,
|
|
isFlagged,
|
|
} = this.props;
|
|
const style = getStyleSheet(theme);
|
|
const showReply = shouldRenderReplyButton || (!commentedOnDisplayName && commentCount > 0 && renderReplies);
|
|
|
|
let dateComponent;
|
|
if (showFullDate) {
|
|
dateComponent = (
|
|
<View style={style.datetime}>
|
|
<Text style={style.time}>
|
|
<FormattedDate value={createAt}/>
|
|
</Text>
|
|
<Text style={style.time}>
|
|
<FormattedTime
|
|
timeZone={userTimezone}
|
|
hour12={!militaryTime}
|
|
value={createAt}
|
|
/>
|
|
</Text>
|
|
</View>
|
|
);
|
|
} else {
|
|
dateComponent = (
|
|
<Text style={style.time}>
|
|
<FormattedTime
|
|
timeZone={userTimezone}
|
|
hour12={!militaryTime}
|
|
value={createAt}
|
|
/>
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View>
|
|
<View style={[style.postInfoContainer, (isPendingOrFailedPost && style.pendingPost)]}>
|
|
<View style={{flexDirection: 'row', flex: 1}}>
|
|
{this.getDisplayName(style)}
|
|
<View style={style.timeContainer}>
|
|
{dateComponent}
|
|
</View>
|
|
{isFlagged &&
|
|
<View style={style.flagContainer}>
|
|
<FlagIcon
|
|
height={11}
|
|
width={11}
|
|
color={theme.linkColor}
|
|
/>
|
|
</View>
|
|
}
|
|
</View>
|
|
{showReply &&
|
|
<TouchableOpacity
|
|
onPress={onPress}
|
|
style={style.replyIconContainer}
|
|
>
|
|
<ReplyIcon
|
|
height={15}
|
|
width={15}
|
|
color={theme.linkColor}
|
|
/>
|
|
{!isSearchResult &&
|
|
<Text style={style.replyText}>{commentCount}</Text>
|
|
}
|
|
</TouchableOpacity>
|
|
}
|
|
</View>
|
|
{commentedOnDisplayName !== '' &&
|
|
<View>
|
|
{this.renderCommentedOnMessage(style)}
|
|
</View>
|
|
}
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|
return {
|
|
commentedOn: {
|
|
color: changeOpacity(theme.centerChannelColor, 0.65),
|
|
marginBottom: 3,
|
|
lineHeight: 21,
|
|
},
|
|
postInfoContainer: {
|
|
alignItems: 'center',
|
|
flexDirection: 'row',
|
|
marginTop: 10,
|
|
},
|
|
pendingPost: {
|
|
opacity: 0.5,
|
|
},
|
|
timeContainer: {
|
|
justifyContent: 'center',
|
|
},
|
|
datetime: {
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
},
|
|
time: {
|
|
color: theme.centerChannelColor,
|
|
fontSize: 13,
|
|
marginLeft: 5,
|
|
marginBottom: 1,
|
|
opacity: 0.5,
|
|
},
|
|
replyIconContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
height: 35,
|
|
minWidth: 40,
|
|
paddingVertical: 10,
|
|
},
|
|
replyText: {
|
|
fontSize: 15,
|
|
marginLeft: 3,
|
|
color: theme.linkColor,
|
|
},
|
|
indicatorContainer: {
|
|
flexDirection: 'row',
|
|
},
|
|
bot: {
|
|
alignSelf: 'center',
|
|
backgroundColor: changeOpacity(theme.centerChannelColor, 0.15),
|
|
borderRadius: 2,
|
|
color: theme.centerChannelColor,
|
|
fontSize: 10,
|
|
fontWeight: '600',
|
|
marginRight: 5,
|
|
paddingVertical: 2,
|
|
paddingHorizontal: 4,
|
|
},
|
|
displayName: {
|
|
color: theme.centerChannelColor,
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
marginRight: 5,
|
|
marginBottom: 3,
|
|
},
|
|
flagContainer: {
|
|
marginLeft: 10,
|
|
alignSelf: 'center',
|
|
...Platform.select({
|
|
ios: {
|
|
marginBottom: 2,
|
|
},
|
|
android: {
|
|
marginBottom: 1,
|
|
},
|
|
}),
|
|
},
|
|
};
|
|
});
|