[NEW] Redesign quoted messages (#3883)
This commit is contained in:
parent
82acb917fc
commit
b0580bf547
|
@ -37,6 +37,7 @@ export const themes: any = {
|
|||
infoText: '#6d6d72',
|
||||
tintColor: '#1d74f5',
|
||||
tintActive: '#549df9',
|
||||
tintDisabled: '#88B4F5',
|
||||
auxiliaryTintColor: '#6C727A',
|
||||
actionTintColor: '#1d74f5',
|
||||
separatorColor: '#cbcbcc',
|
||||
|
@ -87,6 +88,7 @@ export const themes: any = {
|
|||
infoText: '#6D6D72',
|
||||
tintColor: '#1d74f5',
|
||||
tintActive: '#549df9',
|
||||
tintDisabled: '#88B4F5',
|
||||
auxiliaryTintColor: '#f9f9f9',
|
||||
actionTintColor: '#1d74f5',
|
||||
separatorColor: '#2b2b2d',
|
||||
|
@ -137,6 +139,7 @@ export const themes: any = {
|
|||
infoText: '#6d6d72',
|
||||
tintColor: '#1e9bfe',
|
||||
tintActive: '#76b7fc',
|
||||
tintDisabled: '#88B4F5', // TODO: Evaluate this with design team
|
||||
auxiliaryTintColor: '#f9f9f9',
|
||||
actionTintColor: '#1e9bfe',
|
||||
separatorColor: '#272728',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
|
||||
|
||||
import { CELL_WIDTH } from './TableCell';
|
||||
import styles from './styles';
|
||||
|
@ -19,7 +19,7 @@ const Table = React.memo(({ children, numColumns, theme }: ITable) => {
|
|||
const getTableWidth = () => numColumns * CELL_WIDTH;
|
||||
|
||||
const renderRows = (drawExtraBorders = true) => {
|
||||
const tableStyle = [styles.table, { borderColor: themes[theme].borderColor }];
|
||||
const tableStyle: ViewStyle[] = [styles.table, { borderColor: themes[theme].borderColor }];
|
||||
if (drawExtraBorders) {
|
||||
tableStyle.push(styles.tableExtraBorders);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Text, View, ViewStyle } from 'react-native';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
import styles from './styles';
|
||||
|
@ -14,7 +14,7 @@ interface ITableCell {
|
|||
export const CELL_WIDTH = 100;
|
||||
|
||||
const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell) => {
|
||||
const cellStyle = [styles.cell, { borderColor: themes[theme].borderColor }];
|
||||
const cellStyle: ViewStyle[] = [styles.cell, { borderColor: themes[theme].borderColor }];
|
||||
if (!isLastCell) {
|
||||
cellStyle.push(styles.cellRightBorder);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
import styles from './styles';
|
||||
|
@ -11,7 +11,7 @@ interface ITableRow {
|
|||
}
|
||||
|
||||
const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => {
|
||||
const rowStyle = [styles.row, { borderColor: themes[theme].borderColor }];
|
||||
const rowStyle: ViewStyle[] = [styles.row, { borderColor: themes[theme].borderColor }];
|
||||
if (!isLastRow) {
|
||||
rowStyle.push(styles.rowBottomBorder);
|
||||
}
|
||||
|
|
|
@ -280,6 +280,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
|
||||
renderHeading = ({ children, level }: any) => {
|
||||
const { numberOfLines, theme } = this.props;
|
||||
// @ts-ignore
|
||||
const textStyle = styles[`heading${level}Text`];
|
||||
return (
|
||||
<Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}>
|
||||
|
|
|
@ -7,7 +7,7 @@ const codeFontFamily = Platform.select({
|
|||
android: { fontFamily: 'monospace' }
|
||||
});
|
||||
|
||||
export default StyleSheet.create<any>({
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'row'
|
||||
|
|
|
@ -10,10 +10,16 @@ import Reply from './Reply';
|
|||
import Button from '../Button';
|
||||
import styles from './styles';
|
||||
import MessageContext from './Context';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IAttachment } from '../../definitions';
|
||||
import CollapsibleQuote from './Components/CollapsibleQuote';
|
||||
|
||||
const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => {
|
||||
const AttachedActions = ({ attachment }: IMessageAttachedActions) => {
|
||||
if (!attachment.actions) {
|
||||
return null;
|
||||
}
|
||||
const { onAnswerButtonPress } = useContext(MessageContext);
|
||||
const { theme } = useTheme();
|
||||
|
||||
const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => {
|
||||
if (element.type === 'button') {
|
||||
|
@ -30,46 +36,62 @@ const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => {
|
|||
};
|
||||
|
||||
const Attachments = React.memo(
|
||||
({ attachments, timeFormat, showAttachment, getCustomEmoji, theme }: IMessageAttachments) => {
|
||||
// @ts-ignore
|
||||
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
||||
if (!attachments || attachments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return attachments.map((file: any, index: number) => {
|
||||
if (file.image_url) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return attachments.map((file: IAttachment, index: number) => {
|
||||
if (file && file.image_url) {
|
||||
return (
|
||||
<Image key={file.image_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<Image
|
||||
key={file.image_url}
|
||||
file={file}
|
||||
showAttachment={showAttachment}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
style={style}
|
||||
isReply={isReply}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (file.audio_url) {
|
||||
return <Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||
|
||||
if (file && file.audio_url) {
|
||||
return (
|
||||
<Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} isReply={isReply} style={style} theme={theme} />
|
||||
);
|
||||
}
|
||||
|
||||
if (file.video_url) {
|
||||
return (
|
||||
<Video key={file.video_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<Video
|
||||
key={file.video_url}
|
||||
file={file}
|
||||
showAttachment={showAttachment}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
style={style}
|
||||
isReply={isReply}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (file.actions && file.actions.length > 0) {
|
||||
return <AttachedActions attachment={file} theme={theme} />;
|
||||
|
||||
if (file && file.actions && file.actions.length > 0) {
|
||||
return <AttachedActions attachment={file} />;
|
||||
}
|
||||
if (file.title)
|
||||
if (file.title) {
|
||||
return (
|
||||
<CollapsibleQuote key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Reply
|
||||
key={index}
|
||||
index={index}
|
||||
attachment={file}
|
||||
timeFormat={timeFormat}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
|
||||
});
|
||||
},
|
||||
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme
|
||||
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments)
|
||||
);
|
||||
|
||||
Attachments.displayName = 'MessageAttachments';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Easing, StyleSheet, Text, View } from 'react-native';
|
||||
import { Easing, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
|
||||
import { Audio } from 'expo-av';
|
||||
import Slider from '@react-native-community/slider';
|
||||
import moment from 'moment';
|
||||
|
@ -16,19 +16,20 @@ import MessageContext from './Context';
|
|||
import ActivityIndicator from '../ActivityIndicator';
|
||||
import { withDimensions } from '../../dimensions';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IAttachment } from '../../definitions';
|
||||
|
||||
interface IButton {
|
||||
loading: boolean;
|
||||
paused: boolean;
|
||||
theme: string;
|
||||
disabled?: boolean;
|
||||
onPress: Function;
|
||||
}
|
||||
|
||||
interface IMessageAudioProps {
|
||||
file: {
|
||||
audio_url: string;
|
||||
description: string;
|
||||
};
|
||||
file: IAttachment;
|
||||
isReply?: boolean;
|
||||
style?: StyleProp<TextStyle>[];
|
||||
theme: string;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
scale?: number;
|
||||
|
@ -89,16 +90,21 @@ const sliderAnimationConfig = {
|
|||
delay: 0
|
||||
};
|
||||
|
||||
const Button = React.memo(({ loading, paused, onPress, theme }: IButton) => (
|
||||
const Button = React.memo(({ loading, paused, onPress, disabled, theme }: IButton) => (
|
||||
<Touchable
|
||||
style={styles.playPauseButton}
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
background={Touchable.SelectableBackgroundBorderless()}>
|
||||
{loading ? (
|
||||
<ActivityIndicator style={[styles.playPauseButton, styles.audioLoading]} />
|
||||
) : (
|
||||
<CustomIcon name={paused ? 'play-filled' : 'pause-filled'} size={36} color={themes[theme].tintColor} />
|
||||
<CustomIcon
|
||||
name={paused ? 'play-filled' : 'pause-filled'}
|
||||
size={36}
|
||||
color={disabled ? themes[theme].tintDisabled : themes[theme].tintColor}
|
||||
/>
|
||||
)}
|
||||
</Touchable>
|
||||
));
|
||||
|
@ -128,7 +134,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
const { baseUrl, user } = this.context;
|
||||
|
||||
let url = file.audio_url;
|
||||
if (!url.startsWith('http')) {
|
||||
if (url && !url.startsWith('http')) {
|
||||
url = `${baseUrl}${file.audio_url}`;
|
||||
}
|
||||
|
||||
|
@ -249,7 +255,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
|
||||
render() {
|
||||
const { loading, paused, currentTime, duration } = this.state;
|
||||
const { file, getCustomEmoji, theme, scale } = this.props;
|
||||
const { file, getCustomEmoji, theme, scale, isReply, style } = this.props;
|
||||
const { description } = file;
|
||||
const { baseUrl, user } = this.context;
|
||||
|
||||
|
@ -259,29 +265,37 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
|
||||
return (
|
||||
<>
|
||||
<Markdown
|
||||
msg={description}
|
||||
style={[isReply && style]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
styles.audioContainer,
|
||||
{ backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor }
|
||||
]}>
|
||||
<Button loading={loading} paused={paused} onPress={this.togglePlayPause} theme={theme} />
|
||||
<Button disabled={isReply} loading={loading} paused={paused} onPress={this.togglePlayPause} theme={theme} />
|
||||
<Slider
|
||||
disabled={isReply}
|
||||
style={styles.slider}
|
||||
value={currentTime}
|
||||
maximumValue={duration}
|
||||
minimumValue={0}
|
||||
// @ts-ignore
|
||||
animateTransitions
|
||||
animationConfig={sliderAnimationConfig}
|
||||
thumbTintColor={isAndroid && themes[theme].tintColor}
|
||||
thumbTintColor={isReply ? themes[theme].tintDisabled : isAndroid && themes[theme].tintColor}
|
||||
minimumTrackTintColor={themes[theme].tintColor}
|
||||
maximumTrackTintColor={themes[theme].auxiliaryText}
|
||||
onValueChange={this.onValueChange}
|
||||
/* @ts-ignore*/
|
||||
thumbImage={isIOS && { uri: 'audio_thumb', scale }}
|
||||
thumbImage={isIOS ? { uri: 'audio_thumb', scale } : undefined}
|
||||
/>
|
||||
<Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{this.duration}</Text>
|
||||
</View>
|
||||
<Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { StyleProp, TextStyle, View } from 'react-native';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
import { dequal } from 'dequal';
|
||||
import { createImageProgress } from 'react-native-image-progress';
|
||||
|
@ -12,9 +12,11 @@ import { formatAttachmentUrl } from '../../lib/utils';
|
|||
import { themes } from '../../constants/colors';
|
||||
import MessageContext from './Context';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IAttachment } from '../../definitions';
|
||||
|
||||
type TMessageButton = {
|
||||
children: JSX.Element;
|
||||
disabled?: boolean;
|
||||
onPress: Function;
|
||||
theme: string;
|
||||
};
|
||||
|
@ -25,17 +27,23 @@ type TMessageImage = {
|
|||
};
|
||||
|
||||
interface IMessageImage {
|
||||
file: { image_url: string; description?: string };
|
||||
file: IAttachment;
|
||||
imageUrl?: string;
|
||||
showAttachment: Function;
|
||||
showAttachment?: Function;
|
||||
style?: StyleProp<TextStyle>[];
|
||||
isReply?: boolean;
|
||||
theme: string;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
}
|
||||
|
||||
const ImageProgress = createImageProgress(FastImage);
|
||||
|
||||
const Button = React.memo(({ children, onPress, theme }: TMessageButton) => (
|
||||
<Touchable onPress={onPress} style={styles.imageContainer} background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
||||
const Button = React.memo(({ children, onPress, disabled, theme }: TMessageButton) => (
|
||||
<Touchable
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
style={styles.imageContainer}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
||||
{children}
|
||||
</Touchable>
|
||||
));
|
||||
|
@ -53,34 +61,41 @@ export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
|
|||
));
|
||||
|
||||
const ImageContainer = React.memo(
|
||||
({ file, imageUrl, showAttachment, getCustomEmoji, theme }: IMessageImage) => {
|
||||
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageImage) => {
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
||||
if (!img) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onPress = () => showAttachment(file);
|
||||
const onPress = () => {
|
||||
if (!showAttachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
return showAttachment(file);
|
||||
};
|
||||
|
||||
if (file.description) {
|
||||
return (
|
||||
<Button theme={theme} onPress={onPress}>
|
||||
<Button disabled={isReply} theme={theme} onPress={onPress}>
|
||||
<View>
|
||||
<MessageImage img={img} theme={theme} />
|
||||
<Markdown
|
||||
msg={file.description}
|
||||
style={[isReply && style]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
/>
|
||||
<MessageImage img={img} theme={theme} />
|
||||
</View>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button theme={theme} onPress={onPress}>
|
||||
<Button disabled={isReply} theme={theme} onPress={onPress}>
|
||||
<MessageImage img={img} theme={theme} />
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -21,6 +21,9 @@ import { themes } from '../../constants/colors';
|
|||
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
|
||||
|
||||
const MessageInner = React.memo((props: IMessageInner) => {
|
||||
const { attachments } = props;
|
||||
const isCollapsible = attachments ? attachments[0] && attachments[0].collapsed : false;
|
||||
|
||||
if (props.type === 'discussion-created') {
|
||||
return (
|
||||
<>
|
||||
|
@ -29,6 +32,7 @@ const MessageInner = React.memo((props: IMessageInner) => {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.type === 'jitsi_call_started') {
|
||||
return (
|
||||
<>
|
||||
|
@ -38,6 +42,7 @@ const MessageInner = React.memo((props: IMessageInner) => {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.blocks && props.blocks.length) {
|
||||
return (
|
||||
<>
|
||||
|
@ -48,11 +53,22 @@ const MessageInner = React.memo((props: IMessageInner) => {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<User {...props} />
|
||||
<Content {...props} />
|
||||
<Attachments {...props} />
|
||||
{isCollapsible ? (
|
||||
<>
|
||||
<Content {...props} />
|
||||
<Attachments {...props} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Attachments {...props} />
|
||||
<Content {...props} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Urls {...props} />
|
||||
<Thread {...props} />
|
||||
<Reactions {...props} />
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useContext, useState } from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import moment from 'moment';
|
||||
import { transparentize } from 'color2k';
|
||||
import { dequal } from 'dequal';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
|
@ -16,22 +15,24 @@ import { formatAttachmentUrl } from '../../lib/utils';
|
|||
import { IAttachment } from '../../definitions/IAttachment';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import RCActivityIndicator from '../ActivityIndicator';
|
||||
import Attachments from './Attachments';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 6,
|
||||
marginVertical: 4,
|
||||
alignSelf: 'flex-start',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
borderLeftWidth: 2
|
||||
},
|
||||
attachmentContainer: {
|
||||
flex: 1,
|
||||
borderRadius: 4,
|
||||
flexDirection: 'column',
|
||||
padding: 15
|
||||
paddingVertical: 4,
|
||||
paddingLeft: 8
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFillObject
|
||||
|
@ -39,7 +40,8 @@ const styles = StyleSheet.create({
|
|||
authorContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
marginBottom: 8
|
||||
},
|
||||
author: {
|
||||
flex: 1,
|
||||
|
@ -48,9 +50,8 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
time: {
|
||||
fontSize: 12,
|
||||
marginLeft: 10,
|
||||
...sharedStyles.textRegular,
|
||||
fontWeight: '300'
|
||||
marginLeft: 8,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
fieldsContainer: {
|
||||
flex: 1,
|
||||
|
@ -114,7 +115,6 @@ interface IMessageReply {
|
|||
attachment: IAttachment;
|
||||
timeFormat?: string;
|
||||
index: number;
|
||||
theme: string;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
}
|
||||
|
||||
|
@ -123,10 +123,10 @@ const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
|
|||
return (
|
||||
<View style={styles.authorContainer}>
|
||||
{attachment.author_name ? (
|
||||
<Text style={[styles.author, { color: themes[theme].bodyText }]}>{attachment.author_name}</Text>
|
||||
<Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
|
||||
) : null}
|
||||
{attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null}
|
||||
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> : null}
|
||||
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
@ -138,7 +138,16 @@ const Description = React.memo(
|
|||
return null;
|
||||
}
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
return <Markdown msg={text} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||
return (
|
||||
<Markdown
|
||||
msg={text}
|
||||
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14 }]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
if (prevProps.attachment.text !== nextProps.attachment.text) {
|
||||
|
@ -195,12 +204,14 @@ const Fields = React.memo(
|
|||
);
|
||||
|
||||
const Reply = React.memo(
|
||||
({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => {
|
||||
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
if (!attachment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
||||
|
||||
const onPress = async () => {
|
||||
|
@ -221,14 +232,9 @@ const Reply = React.memo(
|
|||
openLink(url, theme);
|
||||
};
|
||||
|
||||
let { borderColor, chatComponentBackground: backgroundColor } = themes[theme];
|
||||
try {
|
||||
if (attachment.color) {
|
||||
backgroundColor = transparentize(attachment.color, 0.8);
|
||||
borderColor = attachment.color;
|
||||
}
|
||||
} catch (e) {
|
||||
// fallback to default
|
||||
let { borderColor } = themes[theme];
|
||||
if (attachment.color) {
|
||||
borderColor = attachment.color;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -240,7 +246,6 @@ const Reply = React.memo(
|
|||
index > 0 && styles.marginTop,
|
||||
attachment.description && styles.marginBottom,
|
||||
{
|
||||
backgroundColor,
|
||||
borderColor
|
||||
}
|
||||
]}
|
||||
|
@ -248,6 +253,13 @@ const Reply = React.memo(
|
|||
disabled={loading}>
|
||||
<View style={styles.attachmentContainer}>
|
||||
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
|
||||
<Attachments
|
||||
attachments={attachment.attachments}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
timeFormat={timeFormat}
|
||||
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14, marginBottom: 8 }]}
|
||||
isReply
|
||||
/>
|
||||
<UrlImage image={attachment.thumb_url} />
|
||||
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
|
@ -273,7 +285,7 @@ const Reply = React.memo(
|
|||
</>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme
|
||||
(prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment)
|
||||
);
|
||||
|
||||
Reply.displayName = 'MessageReply';
|
||||
|
|
|
@ -97,7 +97,7 @@ const User = React.memo(
|
|||
{textContent}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text>
|
||||
{hasError && <MessageError hasError={hasError} theme={theme} {...props} />}
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useContext, useState } from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { StyleProp, StyleSheet, TextStyle } from 'react-native';
|
||||
import { dequal } from 'dequal';
|
||||
|
||||
import Touchable from './Touchable';
|
||||
|
@ -33,13 +33,15 @@ const styles = StyleSheet.create({
|
|||
|
||||
interface IMessageVideo {
|
||||
file: IAttachment;
|
||||
showAttachment: Function;
|
||||
showAttachment?: Function;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
style?: StyleProp<TextStyle>[];
|
||||
isReply?: boolean;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
const Video = React.memo(
|
||||
({ file, showAttachment, getCustomEmoji, theme }: IMessageVideo) => {
|
||||
({ file, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageVideo) => {
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
|
@ -47,7 +49,7 @@ const Video = React.memo(
|
|||
return null;
|
||||
}
|
||||
const onPress = async () => {
|
||||
if (isTypeSupported(file.video_type)) {
|
||||
if (isTypeSupported(file.video_type) && showAttachment) {
|
||||
return showAttachment(file);
|
||||
}
|
||||
|
||||
|
@ -73,19 +75,21 @@ const Video = React.memo(
|
|||
|
||||
return (
|
||||
<>
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
||||
{loading ? <RCActivityIndicator /> : <CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />}
|
||||
</Touchable>
|
||||
<Markdown
|
||||
msg={file.description}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
style={[isReply && style]}
|
||||
theme={theme}
|
||||
/>
|
||||
<Touchable
|
||||
disabled={isReply}
|
||||
onPress={onPress}
|
||||
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
||||
{loading ? <RCActivityIndicator /> : <CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />}
|
||||
</Touchable>
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
import { StyleProp, TextStyle } from 'react-native';
|
||||
|
||||
import { IUserChannel, IUserMention } from '../markdown/interfaces';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IAttachment } from '../../definitions';
|
||||
|
||||
export type TMessageType = 'discussion-created' | 'jitsi_call_started';
|
||||
|
||||
export interface IMessageAttachments {
|
||||
attachments: any;
|
||||
attachments?: IAttachment[];
|
||||
timeFormat?: string;
|
||||
showAttachment: Function;
|
||||
style?: StyleProp<TextStyle>[];
|
||||
isReply?: boolean;
|
||||
showAttachment?: Function;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface IMessageAttachedActions {
|
||||
attachment: {
|
||||
actions: [];
|
||||
text: string;
|
||||
};
|
||||
theme: string;
|
||||
attachment: IAttachment;
|
||||
}
|
||||
|
||||
export interface IMessageAvatar {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
|
|||
import sharedStyles from '../../views/Styles';
|
||||
import { isTablet } from '../../utils/deviceInfo';
|
||||
|
||||
export default StyleSheet.create<any>({
|
||||
export default StyleSheet.create({
|
||||
root: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
@ -28,7 +28,6 @@ export default StyleSheet.create<any>({
|
|||
},
|
||||
flex: {
|
||||
flexDirection: 'row'
|
||||
// flex: 1
|
||||
},
|
||||
temp: { opacity: 0.3 },
|
||||
marginTop: {
|
||||
|
@ -100,7 +99,6 @@ export default StyleSheet.create<any>({
|
|||
...sharedStyles.textSemibold
|
||||
},
|
||||
imageContainer: {
|
||||
// flex: 1,
|
||||
flexDirection: 'column',
|
||||
borderRadius: 4
|
||||
},
|
||||
|
@ -141,7 +139,6 @@ export default StyleSheet.create<any>({
|
|||
},
|
||||
repliedThread: {
|
||||
flexDirection: 'row',
|
||||
// flex: 1,
|
||||
alignItems: 'center',
|
||||
marginTop: 6,
|
||||
marginBottom: 12
|
||||
|
|
|
@ -10,13 +10,16 @@ export interface IAttachment {
|
|||
image_type?: string;
|
||||
video_url?: string;
|
||||
video_type?: string;
|
||||
audio_url?: string;
|
||||
title_link_download?: boolean;
|
||||
attachments?: IAttachment[];
|
||||
fields?: IAttachment[];
|
||||
image_dimensions?: { width?: number; height?: number };
|
||||
image_preview?: string;
|
||||
image_size?: number;
|
||||
author_name?: string;
|
||||
author_icon?: string;
|
||||
actions?: [];
|
||||
message_link?: string;
|
||||
text?: string;
|
||||
short?: boolean;
|
||||
|
@ -24,7 +27,6 @@ export interface IAttachment {
|
|||
author_link?: string;
|
||||
color?: string;
|
||||
thumb_url?: string;
|
||||
attachments?: any[];
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { coerce, gt, gte, lt, lte, SemVer } from 'semver';
|
||||
|
||||
export const formatAttachmentUrl = (attachmentUrl: string, userId: string, token: string, server: string): string => {
|
||||
if (attachmentUrl.startsWith('http')) {
|
||||
export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: string, token: string, server: string): string => {
|
||||
if (attachmentUrl && attachmentUrl.startsWith('http')) {
|
||||
if (attachmentUrl.includes('rc_token')) {
|
||||
return encodeURI(attachmentUrl);
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -485,6 +485,24 @@ stories.add('Message with reply', () => (
|
|||
}
|
||||
]}
|
||||
/>
|
||||
<Message
|
||||
msg='Looks cool!'
|
||||
attachments={[
|
||||
{
|
||||
author_name: 'rocket.cat',
|
||||
attachments: [
|
||||
{
|
||||
author_name: 'rocket.cat',
|
||||
ts: date,
|
||||
timeFormat: 'LT',
|
||||
description: 'What you think about this one?',
|
||||
image_url: 'https://octodex.github.com/images/yaktocat.png'
|
||||
}
|
||||
],
|
||||
text: ''
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
));
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue