Chore: Migration to Hooks - Markdown (#4264)

* chore: migrate TextInput from class to functional

* changing from themes[theme] to colors

* removing markdown theme props from other files

* adding a force update and fix a stories

* adding testID and tests for markdown

* fixing some interfaces

* minor tweak

Co-authored-by: GleidsonDaniel <gleidson10daniel@hotmail.com>
This commit is contained in:
Alex Junior 2022-06-27 18:27:22 -03:00 committed by GitHub
parent 18c44178d7
commit 4fd0084bc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 531 additions and 438 deletions

View File

@ -44,7 +44,14 @@ const Avatar = React.memo(
let image;
if (emoji) {
image = (
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
<Emoji
baseUrl={server}
getCustomEmoji={getCustomEmoji}
isMessageContainsOnlyEmoji
literal={emoji}
style={avatarStyle}
testID='avatar'
/>
);
} else {
let uri = avatar;

View File

@ -1,10 +1,10 @@
import React from 'react';
import FastImage from 'react-native-fast-image';
import { ICustomEmoji } from '../../definitions/IEmoji';
import { ICustomEmoji } from '../../definitions';
const CustomEmoji = React.memo(
({ baseUrl, emoji, style }: ICustomEmoji) => (
({ baseUrl, emoji, style, testID }: ICustomEmoji) => (
<FastImage
style={style}
source={{
@ -12,6 +12,7 @@ const CustomEmoji = React.memo(
priority: FastImage.priority.high
}}
resizeMode={FastImage.resizeMode.contain}
testID={testID}
/>
),
(prevProps, nextProps) => {

View File

@ -76,7 +76,7 @@ class MessageParser extends UiKitParserMessage<React.ReactElement> {
<MarkdownPreview msg={element.text} style={[isContext && { color: themes[theme].auxiliaryText }]} numberOfLines={0} />
);
}
return <Markdown msg={element.text} theme={theme} style={[isContext && { color: themes[theme].auxiliaryText }]} />;
return <Markdown msg={element.text} style={[isContext && { color: themes[theme].auxiliaryText }]} />;
}
button(element: IButton, context: BlockContext) {

View File

@ -2,7 +2,6 @@ import React from 'react';
import { StyleProp, Text, TextStyle } from 'react-native';
import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles';
import { events, logEvent } from '../../lib/methods/helpers/log';
import { IUserMention } from './interfaces';
@ -14,20 +13,22 @@ interface IAtMention {
style?: StyleProp<TextStyle>[];
useRealName?: boolean;
mentions?: IUserMention[];
testID: string;
}
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName }: IAtMention) => {
const { theme } = useTheme();
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName, testID }: IAtMention) => {
const { colors } = useTheme();
if (mention === 'all' || mention === 'here') {
return (
<Text
style={[
styles.mention,
{
color: themes[theme].mentionGroupColor
color: colors.mentionGroupColor
},
...style
]}>
]}
testID={`${testID}-mention-all-here`}>
{mention}
</Text>
);
@ -36,11 +37,11 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
let mentionStyle = {};
if (mention === username) {
mentionStyle = {
color: themes[theme].mentionMeColor
color: colors.mentionMeColor
};
} else {
mentionStyle = {
color: themes[theme].mentionOtherColor
color: colors.mentionOtherColor
};
}
@ -59,13 +60,15 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
if (user) {
return (
<Text style={[styles.mention, mentionStyle, ...style]} onPress={handlePress}>
<Text style={[styles.mention, mentionStyle, ...style]} onPress={handlePress} testID={`${testID}-mention-users`}>
{useRealName && user.name ? user.name : user.username}
</Text>
);
}
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`@${mention}`}</Text>;
return (
<Text style={[styles.text, { color: colors.bodyText }, ...style]} testID={`${testID}-mention-unknown`}>{`@${mention}`}</Text>
);
});
export default AtMention;

View File

@ -1,20 +1,21 @@
import React from 'react';
import { View } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import styles from './styles';
interface IBlockQuote {
children: React.ReactElement | null;
theme: TSupportedThemes;
}
const BlockQuote = React.memo(({ children, theme }: IBlockQuote) => (
<View style={styles.container}>
<View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} />
<View style={styles.childContainer}>{children}</View>
</View>
));
const BlockQuote = React.memo(({ children }: IBlockQuote) => {
const { colors } = useTheme();
return (
<View style={styles.container} testID='markdown-block-quote'>
<View style={[styles.quote, { backgroundColor: colors.borderColor }]} />
<View style={styles.childContainer}>{children}</View>
</View>
);
});
export default BlockQuote;

View File

@ -15,10 +15,11 @@ interface IEmoji {
style?: object;
onEmojiSelected?: Function;
tabEmojiStyle?: object;
testID: string;
}
const Emoji = React.memo(
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {} }: IEmoji) => {
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, testID, customEmojis = true, style = {} }: IEmoji) => {
const { colors } = useTheme();
const emojiUnicode = shortnameToUnicode(literal);
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
@ -28,11 +29,14 @@ const Emoji = React.memo(
baseUrl={baseUrl}
style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]}
emoji={emoji}
testID={`${testID}-custom-emoji`}
/>
);
}
return (
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>
<Text
style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}
testID={`${testID}-unicode-emoji`}>
{emojiUnicode}
</Text>
);

View File

@ -3,7 +3,6 @@ import { StyleProp, Text, TextStyle } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { IUserChannel } from './interfaces';
import styles from './styles';
@ -17,10 +16,11 @@ interface IHashtag {
navToRoomInfo?: Function;
style?: StyleProp<TextStyle>[];
channels?: IUserChannel[];
testID: string;
}
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IHashtag) => {
const { theme } = useTheme();
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, testID, style = [] }: IHashtag) => {
const { colors } = useTheme();
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'RoomView'>>();
@ -46,16 +46,21 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
style={[
styles.mention,
{
color: themes[theme].mentionOtherColor
color: colors.mentionOtherColor
},
...style
]}
onPress={handlePress}>
onPress={handlePress}
testID={`${testID}-hashtag-channels`}>
{`#${hashtag}`}
</Text>
);
}
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`#${hashtag}`}</Text>;
return (
<Text
style={[styles.text, { color: colors.bodyText }, ...style]}
testID={`${testID}-hashtag-without-channels`}>{`#${hashtag}`}</Text>
);
});
export default Hashtag;

View File

@ -3,22 +3,23 @@ import { Text } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import styles from './styles';
import { themes } from '../../lib/constants';
import { LISTENER } from '../Toast';
import EventEmitter from '../../lib/methods/helpers/events';
import I18n from '../../i18n';
import openLink from '../../lib/methods/helpers/openLink';
import { TOnLinkPress } from './interfaces';
import { TSupportedThemes } from '../../theme';
import { useTheme } from '../../theme';
interface ILink {
children: React.ReactElement | null;
link: string;
theme: TSupportedThemes;
onLinkPress?: TOnLinkPress;
testID: string;
}
const Link = React.memo(({ children, link, theme, onLinkPress }: ILink) => {
const Link = React.memo(({ children, link, onLinkPress, testID }: ILink) => {
const { colors, theme } = useTheme();
const handlePress = () => {
if (!link) {
return;
@ -37,7 +38,11 @@ const Link = React.memo(({ children, link, theme, onLinkPress }: ILink) => {
// if you have a [](https://rocket.chat) render https://rocket.chat
return (
<Text onPress={handlePress} onLongPress={onLongPress} style={{ ...styles.link, color: themes[theme].actionTintColor }}>
<Text
onPress={handlePress}
onLongPress={onLongPress}
style={{ ...styles.link, color: colors.actionTintColor }}
testID={`${testID}-link`}>
{childLength !== 0 ? children : link}
</Text>
);

View File

@ -1,8 +1,7 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
const style = StyleSheet.create({
container: {
@ -24,11 +23,13 @@ interface IListItem {
level: number;
ordered: boolean;
continue: boolean;
theme: TSupportedThemes;
index: number;
testID: string;
}
const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue, ordered, index, theme }: IListItem) => {
const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue, ordered, index, testID }: IListItem) => {
const { colors } = useTheme();
let bullet;
if (_continue) {
bullet = '';
@ -41,9 +42,9 @@ const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue
}
return (
<View style={style.container}>
<View style={style.container} testID={`${testID}-list`}>
<View style={[{ width: bulletWidth }, style.bullet]}>
<Text style={{ color: themes[theme].bodyText }}>{bullet}</Text>
<Text style={{ color: colors.bodyText }}>{bullet}</Text>
</View>
<View style={style.contents}>{children}</View>
</View>

View File

@ -5,24 +5,25 @@ import { CELL_WIDTH } from './TableCell';
import Navigation from '../../lib/navigation/appNavigation';
import styles from './styles';
import I18n from '../../i18n';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { useAppSelector } from '../../lib/hooks';
interface ITable {
children: React.ReactElement | null;
numColumns: number;
theme: TSupportedThemes;
testID: string;
}
const MAX_HEIGHT = 300;
const Table = React.memo(({ children, numColumns, theme }: ITable) => {
const Table = React.memo(({ children, numColumns, testID }: ITable) => {
const { colors } = useTheme();
const getTableWidth = () => numColumns * CELL_WIDTH;
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const renderRows = (drawExtraBorders = true) => {
const tableStyle: ViewStyle[] = [styles.table, { borderColor: themes[theme].borderColor }];
const tableStyle: ViewStyle[] = [styles.table, { borderColor: colors.borderColor }];
if (drawExtraBorders) {
tableStyle.push(styles.tableExtraBorders);
}
@ -47,18 +48,15 @@ const Table = React.memo(({ children, numColumns, theme }: ITable) => {
};
return (
<TouchableOpacity onPress={onPress}>
<TouchableOpacity onPress={onPress} testID={`${testID}-table`}>
<ScrollView
contentContainerStyle={{ width: getTableWidth() }}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
style={[
styles.containerTable,
{ maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT, borderColor: themes[theme].borderColor }
]}>
style={[styles.containerTable, { maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT, borderColor: colors.borderColor }]}>
{renderRows(false)}
</ScrollView>
<Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Full_table')}</Text>
<Text style={[styles.textInfo, { color: colors.auxiliaryText }]}>{I18n.t('Full_table')}</Text>
</TouchableOpacity>
);
});

View File

@ -1,21 +1,21 @@
import React from 'react';
import { Text, View, ViewStyle } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import styles from './styles';
interface ITableCell {
export interface ITableCell {
align: '' | 'left' | 'center' | 'right';
children: React.ReactElement | null;
isLastCell: boolean;
theme: TSupportedThemes;
}
export const CELL_WIDTH = 100;
const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell) => {
const cellStyle: ViewStyle[] = [styles.cell, { borderColor: themes[theme].borderColor }];
const TableCell = React.memo(({ isLastCell, align, children }: ITableCell) => {
const { colors } = useTheme();
const cellStyle: ViewStyle[] = [styles.cell, { borderColor: colors.borderColor }];
if (!isLastCell) {
cellStyle.push(styles.cellRightBorder);
}
@ -29,7 +29,7 @@ const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell
return (
<View style={[...cellStyle, { width: CELL_WIDTH }]}>
<Text style={[textStyle, { color: themes[theme].bodyText }]}>{children}</Text>
<Text style={[textStyle, { color: colors.bodyText }]}>{children}</Text>
</View>
);
});

View File

@ -1,18 +1,18 @@
import React from 'react';
import { View, ViewStyle } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import styles from './styles';
interface ITableRow {
export interface ITableRow {
children: React.ReactElement | null;
isLastRow: boolean;
theme: TSupportedThemes;
}
const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => {
const rowStyle: ViewStyle[] = [styles.row, { borderColor: themes[theme].borderColor }];
const TableRow = React.memo(({ isLastRow, children: _children }: ITableRow) => {
const { colors } = useTheme();
const rowStyle: ViewStyle[] = [styles.row, { borderColor: colors.borderColor }];
if (!isLastRow) {
rowStyle.push(styles.rowBottomBorder);
}

View File

@ -1,4 +1,4 @@
import React, { PureComponent } from 'react';
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
import { Image, StyleProp, Text, TextStyle } from 'react-native';
import { Parser } from 'commonmark';
import Renderer from 'commonmark-react-renderer';
@ -12,24 +12,23 @@ import MarkdownHashtag from './Hashtag';
import MarkdownBlockQuote from './BlockQuote';
import MarkdownEmoji from './Emoji';
import MarkdownTable from './Table';
import MarkdownTableRow from './TableRow';
import MarkdownTableCell from './TableCell';
import MarkdownTableRow, { ITableRow } from './TableRow';
import MarkdownTableCell, { ITableCell } from './TableCell';
import mergeTextNodes from './mergeTextNodes';
import styles from './styles';
import { isValidURL } from '../../lib/methods/helpers/url';
import { isValidURL } from '../../lib/methods/helpers';
import NewMarkdown from './new';
import { formatText } from './formatText';
import { IUserMention, IUserChannel, TOnLinkPress } from './interfaces';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { TGetCustomEmoji } from '../../definitions';
import { formatHyperlink } from './formatHyperlink';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { IRoomInfoParam } from '../../views/SearchMessagesView';
export { default as MarkdownPreview } from './Preview';
interface IMarkdownProps {
export interface IMarkdownProps {
msg?: string | null;
theme: TSupportedThemes;
md?: MarkdownAST;
mentions?: IUserMention[];
getCustomEmoji?: TGetCustomEmoji;
@ -41,8 +40,7 @@ interface IMarkdownProps {
useRealName?: boolean;
channels?: IUserChannel[];
enableMessageParser?: boolean;
// TODO: Refactor when migrate Room
navToRoomInfo?: Function;
navToRoomInfo?: (params: IRoomInfoParam) => void;
testID?: string;
style?: StyleProp<TextStyle>[];
onLinkPress?: TOnLinkPress;
@ -86,270 +84,243 @@ const emojiCount = (str: string) => {
};
const parser = new Parser();
export const markdownTestID = 'markdown';
class Markdown extends PureComponent<IMarkdownProps, any> {
private renderer: any;
const Markdown = ({
msg,
md,
mentions,
getCustomEmoji,
baseUrl,
username,
tmid,
numberOfLines,
customEmojis,
useRealName,
channels,
enableMessageParser,
navToRoomInfo,
style,
onLinkPress
}: IMarkdownProps) => {
const { colors } = useTheme();
const renderer = useRef<any>();
const isMessageContainsOnlyEmoji = useRef(false);
const [, forceUpdate] = useReducer(x => x + 1, 0);
private isMessageContainsOnlyEmoji!: boolean;
const isNewMarkdown = useCallback(() => !!enableMessageParser && !!md, [enableMessageParser, md]);
const createRenderer = useCallback(
() =>
new Renderer({
renderers: {
text: renderText,
constructor(props: IMarkdownProps) {
super(props);
if (!this.isNewMarkdown) {
this.renderer = this.createRenderer();
emph: Renderer.forwardChildren,
strong: Renderer.forwardChildren,
del: Renderer.forwardChildren,
code: renderCodeInline,
link: renderLink,
image: renderImage,
atMention: renderAtMention,
emoji: renderEmoji,
hashtag: renderHashtag,
paragraph: renderParagraph,
heading: renderHeading,
codeBlock: renderCodeBlock,
blockQuote: renderBlockQuote,
list: renderList,
item: renderListItem,
hardBreak: renderBreak,
thematicBreak: renderBreak,
softBreak: renderBreak,
htmlBlock: renderText,
htmlInline: renderText,
table: renderTable,
table_row: renderTableRow,
table_cell: renderTableCell
},
renderParagraphsInLists: true
}),
[]
);
useEffect(() => {
if (!isNewMarkdown() && msg) {
renderer.current = createRenderer();
forceUpdate();
}
}, [createRenderer, isNewMarkdown, msg]);
if (!msg) {
return null;
}
createRenderer = () =>
new Renderer({
renderers: {
text: this.renderText,
emph: Renderer.forwardChildren,
strong: Renderer.forwardChildren,
del: Renderer.forwardChildren,
code: this.renderCodeInline,
link: this.renderLink,
image: this.renderImage,
atMention: this.renderAtMention,
emoji: this.renderEmoji,
hashtag: this.renderHashtag,
paragraph: this.renderParagraph,
heading: this.renderHeading,
codeBlock: this.renderCodeBlock,
blockQuote: this.renderBlockQuote,
list: this.renderList,
item: this.renderListItem,
hardBreak: this.renderBreak,
thematicBreak: this.renderBreak,
softBreak: this.renderBreak,
htmlBlock: this.renderText,
htmlInline: this.renderText,
table: this.renderTable,
table_row: this.renderTableRow,
table_cell: this.renderTableCell
},
renderParagraphsInLists: true
});
get isNewMarkdown(): boolean {
const { md, enableMessageParser } = this.props;
return !!enableMessageParser && !!md;
}
renderText = ({ context, literal }: { context: []; literal: string }) => {
const { numberOfLines, style = [] } = this.props;
const defaultStyle = [this.isMessageContainsOnlyEmoji ? styles.textBig : {}, ...context.map(type => styles[type])];
const renderText = ({ context, literal }: { context: []; literal: string }) => {
const defaultStyle = [isMessageContainsOnlyEmoji.current ? styles.textBig : {}, ...context.map(type => styles[type])];
return (
<Text accessibilityLabel={literal} style={[styles.text, defaultStyle, ...style]} numberOfLines={numberOfLines}>
<Text accessibilityLabel={literal} style={[styles.text, defaultStyle, ...(style || [])]} numberOfLines={numberOfLines}>
{literal}
</Text>
);
};
renderCodeInline = ({ literal }: TLiteral) => {
const { theme, style = [] } = this.props;
return (
<Text
style={[
{
...styles.codeInline,
color: themes[theme].bodyText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].bannerBackground
},
...style
]}>
{literal}
</Text>
);
};
const renderCodeInline = ({ literal }: TLiteral) => (
<Text
testID={`${markdownTestID}-code-in-line`}
style={[
{
...styles.codeInline,
color: colors.bodyText,
backgroundColor: colors.bannerBackground,
borderColor: colors.bannerBackground
},
...(style || [])
]}>
{literal}
</Text>
);
renderCodeBlock = ({ literal }: TLiteral) => {
const { theme, style = [] } = this.props;
return (
<Text
style={[
{
...styles.codeBlock,
color: themes[theme].bodyText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].bannerBackground
},
...style
]}>
{literal}
</Text>
);
};
const renderCodeBlock = ({ literal }: TLiteral) => (
<Text
testID={`${markdownTestID}-code-block`}
style={[
{
...styles.codeBlock,
color: colors.bodyText,
backgroundColor: colors.bannerBackground,
borderColor: colors.bannerBackground
},
...(style || [])
]}>
{literal}
</Text>
);
renderBreak = () => {
const { tmid } = this.props;
return <Text>{tmid ? ' ' : '\n'}</Text>;
};
const renderBreak = () => <Text>{tmid ? ' ' : '\n'}</Text>;
renderParagraph = ({ children }: any) => {
const { numberOfLines, style, theme } = this.props;
const renderParagraph = ({ children }: { children: React.ReactElement[] }) => {
if (!children || children.length === 0) {
return null;
}
return (
<Text style={[styles.text, style, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>
<Text style={[styles.text, style, { color: colors.bodyText }]} numberOfLines={numberOfLines}>
{children}
</Text>
);
};
renderLink = ({ children, href }: any) => {
const { theme, onLinkPress } = this.props;
return (
<MarkdownLink link={href} theme={theme} onLinkPress={onLinkPress}>
{children}
</MarkdownLink>
);
};
const renderLink = ({ children, href }: { children: React.ReactElement | null; href: string }) => (
<MarkdownLink link={href} onLinkPress={onLinkPress} testID={markdownTestID}>
{children}
</MarkdownLink>
);
renderHashtag = ({ hashtag }: { hashtag: string }) => {
const { channels, navToRoomInfo, style } = this.props;
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} style={style} />;
};
const renderHashtag = ({ hashtag }: { hashtag: string }) => (
<MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} style={style} testID={markdownTestID} />
);
renderAtMention = ({ mentionName }: { mentionName: string }) => {
const { username = '', mentions, navToRoomInfo, useRealName, style } = this.props;
return (
<MarkdownAtMention
mentions={mentions}
mention={mentionName}
useRealName={useRealName}
username={username}
navToRoomInfo={navToRoomInfo}
style={style}
/>
);
};
const renderAtMention = ({ mentionName }: { mentionName: string }) => (
<MarkdownAtMention
mentions={mentions}
mention={mentionName}
useRealName={useRealName}
username={username}
navToRoomInfo={navToRoomInfo}
style={style}
testID={markdownTestID}
/>
);
renderEmoji = ({ literal }: TLiteral) => {
const { getCustomEmoji, baseUrl = '', customEmojis, style } = this.props;
return (
<MarkdownEmoji
literal={literal}
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji}
getCustomEmoji={getCustomEmoji}
baseUrl={baseUrl}
customEmojis={customEmojis}
style={style}
/>
);
};
const renderEmoji = ({ literal }: TLiteral) => (
<MarkdownEmoji
literal={literal}
isMessageContainsOnlyEmoji={isMessageContainsOnlyEmoji.current}
getCustomEmoji={getCustomEmoji}
baseUrl={baseUrl || ''}
customEmojis={customEmojis}
style={style}
testID={markdownTestID}
/>
);
renderImage = ({ src }: { src: string }) => {
const renderImage = ({ src }: { src: string }) => {
if (!isValidURL(src)) {
return null;
}
return <Image style={styles.inlineImage} source={{ uri: encodeURI(src) }} />;
return <Image style={styles.inlineImage} source={{ uri: encodeURI(src) }} testID={`${markdownTestID}-image`} />;
};
renderHeading = ({ children, level }: any) => {
const { numberOfLines, theme } = this.props;
const renderHeading = ({ children, level }: { children: React.ReactElement; level: string }) => {
// @ts-ignore
const textStyle = styles[`heading${level}Text`];
return (
<Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}>
<Text testID={`${markdownTestID}-header`} numberOfLines={numberOfLines} style={[textStyle, { color: colors.bodyText }]}>
{children}
</Text>
);
};
renderList = ({ children, start, tight, type }: any) => {
const { numberOfLines } = this.props;
return (
<MarkdownList ordered={type !== 'bullet'} start={start} tight={tight} numberOfLines={numberOfLines}>
{children}
</MarkdownList>
);
};
const renderList = ({ children, start, tight, type }: any) => (
<MarkdownList ordered={type !== 'bullet'} start={start} tight={tight} numberOfLines={numberOfLines}>
{children}
</MarkdownList>
);
renderListItem = ({ children, context, ...otherProps }: any) => {
const { theme } = this.props;
const renderListItem = ({ children, context, ...otherProps }: any) => {
const level = context.filter((type: string) => type === 'list').length;
return (
<MarkdownListItem level={level} theme={theme} {...otherProps}>
<MarkdownListItem level={level} {...otherProps}>
{children}
</MarkdownListItem>
);
};
renderBlockQuote = ({ children }: { children: JSX.Element }) => {
const { theme } = this.props;
return <MarkdownBlockQuote theme={theme}>{children}</MarkdownBlockQuote>;
};
const renderBlockQuote = ({ children }: { children: React.ReactElement }) => (
<MarkdownBlockQuote>{children}</MarkdownBlockQuote>
);
renderTable = ({ children, numColumns }: { children: JSX.Element; numColumns: number }) => {
const { theme } = this.props;
const renderTable = ({ children, numColumns }: { children: React.ReactElement; numColumns: number }) => (
<MarkdownTable numColumns={numColumns} testID={markdownTestID}>
{children}
</MarkdownTable>
);
const renderTableRow = ({ children, isLastRow }: ITableRow) => (
<MarkdownTableRow isLastRow={isLastRow}>{children}</MarkdownTableRow>
);
const renderTableCell = ({ align, children, isLastCell }: ITableCell) => (
<MarkdownTableCell align={align} isLastCell={isLastCell}>
{children}
</MarkdownTableCell>
);
if (isNewMarkdown()) {
return (
<MarkdownTable numColumns={numColumns} theme={theme}>
{children}
</MarkdownTable>
<NewMarkdown
username={username || ''}
baseUrl={baseUrl || ''}
getCustomEmoji={getCustomEmoji}
useRealName={useRealName}
tokens={md}
mentions={mentions}
channels={channels}
navToRoomInfo={navToRoomInfo}
onLinkPress={onLinkPress}
/>
);
};
renderTableRow = (args: any) => {
const { theme } = this.props;
return <MarkdownTableRow {...args} theme={theme} />;
};
renderTableCell = (args: any) => {
const { theme } = this.props;
return <MarkdownTableCell {...args} theme={theme} />;
};
render() {
const {
msg,
md,
mentions,
channels,
navToRoomInfo,
useRealName,
username = '',
getCustomEmoji,
baseUrl = '',
onLinkPress
} = this.props;
if (!msg) {
return null;
}
if (this.isNewMarkdown) {
return (
<NewMarkdown
username={username}
baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji}
useRealName={useRealName}
tokens={md}
mentions={mentions}
channels={channels}
navToRoomInfo={navToRoomInfo}
onLinkPress={onLinkPress}
/>
);
}
let m = formatText(msg);
m = formatHyperlink(m);
let ast = parser.parse(m);
ast = mergeTextNodes(ast);
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
return this.renderer.render(ast);
}
}
const formattedMessage = formatHyperlink(formatText(msg));
const ast = mergeTextNodes(parser.parse(formattedMessage));
isMessageContainsOnlyEmoji.current = isOnlyEmoji(formattedMessage) && emojiCount(formattedMessage) <= 3;
return renderer?.current?.render(ast) || null;
};
export default Markdown;

View File

@ -0,0 +1,140 @@
import React from 'react';
import { render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import { combineReducers, createStore } from 'redux';
import Markdown, { IMarkdownProps, markdownTestID } from './index';
import { IEmoji, TGetCustomEmoji } from '../../definitions';
const getCustomEmoji: TGetCustomEmoji = content =>
({
marioparty: { name: content, extension: 'gif' },
react_rocket: { name: content, extension: 'png' },
nyan_rocket: { name: content, extension: 'png' }
}[content] as IEmoji);
const Render = ({ msg, baseUrl, getCustomEmoji, mentions, username }: IMarkdownProps) => {
const reducers = combineReducers({
app: () => ({ isMasterDetail: false })
});
const store = createStore(reducers);
return (
<Provider store={store}>
<Markdown msg={msg} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} mentions={mentions} username={username} />
</Provider>
);
};
describe('Markdown', () => {
test('should render header', () => {
const header1 = render(<Render msg='# Header 1' />);
const header2 = render(<Render msg='## Header 2' />);
const header3 = render(<Render msg='### Header 3' />);
const header4 = render(<Render msg='#### Header 4' />);
const header5 = render(<Render msg='##### Header 5' />);
const header6 = render(<Render msg='###### Header 6' />);
expect(header1.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header2.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header3.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header4.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header5.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header6.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
});
test('should render code in line', async () => {
const { findByTestId } = render(<Render msg='This is `inline code`' />);
const component = await findByTestId(`${markdownTestID}-code-in-line`);
expect(component).toBeTruthy();
});
test('should render code block', async () => {
const { findByTestId } = render(
<Render
msg='
```
Code block
```'
/>
);
const component = await findByTestId(`${markdownTestID}-code-block`);
expect(component).toBeTruthy();
});
test('should render image', async () => {
const { findByTestId } = render(<Render msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' />);
const component = await findByTestId(`${markdownTestID}-image`);
expect(component).toBeTruthy();
});
test('should render link', () => {
const markdownLink = render(<Render msg='[Markdown link](https://rocket.chat): `[description](url)`' />);
const link = render(<Render msg='<https://rocket.chat|Formatted Link>: `<url|description>`' />);
expect(markdownLink.findByTestId(`${markdownTestID}-link`)).toBeTruthy();
expect(link.findByTestId(`${markdownTestID}-link`)).toBeTruthy();
});
test('should render block quote', async () => {
const { findByTestId } = render(
<Render
msg={`> This is block quote
this is a normal line`}
/>
);
const component = await findByTestId(`${markdownTestID}-block-quote`);
expect(component).toBeTruthy();
});
test('should render list', () => {
const markdownList = render(<Render msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} />);
const markdownNumberList = render(<Render msg={'1. Open Source\n2. Rocket.Chat'} />);
expect(markdownList.findByTestId(`${markdownTestID}-list`)).toBeTruthy();
expect(markdownNumberList.findByTestId(`${markdownTestID}-list`)).toBeTruthy();
});
test('should render emojis', () => {
const markdownCustomEmoji = render(
<Render msg='😃 :+1: :marioparty:' getCustomEmoji={getCustomEmoji} baseUrl='https://open.rocket.chat' />
);
const markdownUnicodeEmoji = render(<Render msg='Unicode: 😃😇👍' />);
expect(markdownCustomEmoji.findByTestId(`${markdownTestID}-custom-emoji`)).toBeTruthy();
expect(markdownUnicodeEmoji.findByTestId(`${markdownTestID}-unicode-emoji`)).toBeTruthy();
});
test('should render hashtags', () => {
const markdownHashtagChannels = render(<Render msg='#test-channel' channels={[{ _id: '123', name: 'test-channel' }]} />);
const markdownHashtagWithoutChannels = render(<Render msg='#unknown' />);
expect(markdownHashtagChannels.findByTestId(`${markdownTestID}-hashtag-channels`)).toBeTruthy();
expect(markdownHashtagWithoutChannels.findByTestId(`${markdownTestID}-hashtag-without-channels`)).toBeTruthy();
});
test('should render mentions', async () => {
const markdownMentionsAllAndHere = render(<Render msg='@all @here' username='rocket.cat' />);
const markdownMentionsUnknown = render(<Render msg='@unknown' username='rocket.cat' />);
const markdownMentionsUsers = render(
<Render
msg='@rocket.cat '
mentions={[{ _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' }]}
username='rocket.cat'
/>
);
expect(await markdownMentionsAllAndHere.findAllByTestId(`${markdownTestID}-mention-all-here`)).toBeTruthy();
expect(await markdownMentionsUnknown.findByTestId(`${markdownTestID}-mention-unknown`)).toBeTruthy();
expect(await markdownMentionsUsers.findByTestId(`${markdownTestID}-mention-users`)).toBeTruthy();
});
test('should render table', async () => {
const { findByTestId } = render(
<Render
msg='First Header | Second Header
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column'
/>
);
const component = await findByTestId(`${markdownTestID}-table`);
expect(component).toBeTruthy();
});
});

View File

@ -3,7 +3,6 @@ import { Text } from 'react-native';
import { Code as CodeProps } from '@rocket.chat/message-parser';
import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import CodeLine from './CodeLine';
@ -12,16 +11,16 @@ interface ICodeProps {
}
const Code = ({ value }: ICodeProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<Text
style={[
styles.codeBlock,
{
color: themes[theme].bodyText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].borderColor
color: colors.bodyText,
backgroundColor: colors.bannerBackground,
borderColor: colors.borderColor
}
]}>
{value.map(block => {

View File

@ -3,7 +3,6 @@ import { Text } from 'react-native';
import { Emoji as EmojiProps } from '@rocket.chat/message-parser';
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import styles from '../styles';
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
@ -15,7 +14,7 @@ interface IEmojiProps {
}
const Emoji = ({ value, isBigEmoji }: IEmojiProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext);
const emojiUnicode = shortnameToUnicode(`:${value.value}:`);
const emoji = getCustomEmoji?.(value.value);
@ -23,7 +22,7 @@ const Emoji = ({ value, isBigEmoji }: IEmojiProps) => {
if (emoji) {
return <CustomEmoji baseUrl={baseUrl} style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
}
return <Text style={[{ color: themes[theme].bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{emojiUnicode}</Text>;
return <Text style={[{ color: colors.bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{emojiUnicode}</Text>;
};
export default Emoji;

View File

@ -2,7 +2,6 @@ import React from 'react';
import { Text } from 'react-native';
import { Heading as HeadingProps } from '@rocket.chat/message-parser';
import { themes } from '../../../lib/constants';
import styles from '../styles';
import { useTheme } from '../../../theme';
@ -12,11 +11,11 @@ interface IHeadingProps {
}
const Heading = ({ value, level }: IHeadingProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
const textStyle = styles[`heading${level}`];
return (
<Text style={[textStyle, { color: themes[theme].bodyText }]}>
<Text style={[textStyle, { color: colors.bodyText }]}>
{value.map(block => {
switch (block.type) {
case 'PLAIN_TEXT':

View File

@ -4,38 +4,30 @@ import { createImageProgress } from 'react-native-image-progress';
import * as Progress from 'react-native-progress';
import FastImage from 'react-native-fast-image';
import { TSupportedThemes, useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import styles from '../../message/styles';
interface IImageProps {
value: ImageProps['value'];
}
type TMessageImage = {
img: string;
theme: TSupportedThemes;
};
const ImageProgress = createImageProgress(FastImage);
const MessageImage = ({ img, theme }: TMessageImage) => (
<ImageProgress
style={[styles.inlineImage, { borderColor: themes[theme].borderColor }]}
source={{ uri: encodeURI(img) }}
resizeMode={FastImage.resizeMode.cover}
indicator={Progress.Pie}
indicatorProps={{
color: themes[theme].actionTintColor
}}
/>
);
const MessageImage = ({ img }: { img: string }) => {
const { colors } = useTheme();
return (
<ImageProgress
style={[styles.inlineImage, { borderColor: colors.borderColor }]}
source={{ uri: encodeURI(img) }}
resizeMode={FastImage.resizeMode.cover}
indicator={Progress.Pie}
indicatorProps={{
color: colors.actionTintColor
}}
/>
);
};
const Image = ({ value }: IImageProps) => {
const { theme } = useTheme();
const Image = ({ value }: { value: ImageProps['value'] }) => {
const { src } = value;
return <MessageImage img={src.value} theme={theme} />;
return <MessageImage img={src.value} />;
};
export default Image;

View File

@ -45,12 +45,15 @@ const Inline = ({ value }: IParagraphProps) => {
username={username}
navToRoomInfo={navToRoomInfo}
mentions={mentions}
testID='new-markdown'
/>
);
case 'EMOJI':
return <Emoji value={block.value} />;
case 'MENTION_CHANNEL':
return <Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />;
return (
<Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} testID='new-markdown' />
);
case 'INLINE_CODE':
return <InlineCode value={block.value} />;
default:

View File

@ -3,7 +3,6 @@ import { Text } from 'react-native';
import { InlineCode as InlineCodeProps } from '@rocket.chat/message-parser';
import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface IInlineCodeProps {
@ -11,16 +10,16 @@ interface IInlineCodeProps {
}
const InlineCode = ({ value }: IInlineCodeProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<Text
style={[
styles.codeInline,
{
color: themes[theme].bodyText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].borderColor
color: colors.bodyText,
backgroundColor: colors.bannerBackground,
borderColor: colors.borderColor
}
]}>
{(block => {

View File

@ -9,7 +9,6 @@ import { LISTENER } from '../../Toast';
import { useTheme } from '../../../theme';
import openLink from '../../../lib/methods/helpers/openLink';
import EventEmitter from '../../../lib/methods/helpers/events';
import { themes } from '../../../lib/constants';
import Strike from './Strike';
import Italic from './Italic';
import Bold from './Bold';
@ -20,7 +19,7 @@ interface ILinkProps {
}
const Link = ({ value }: ILinkProps) => {
const { theme } = useTheme();
const { theme, colors } = useTheme();
const { onLinkPress } = useContext(MarkdownContext);
const { src, label } = value;
const handlePress = () => {
@ -39,7 +38,7 @@ const Link = ({ value }: ILinkProps) => {
};
return (
<Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: themes[theme].actionTintColor }]}>
<Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: colors.actionTintColor }]}>
{(block => {
switch (block.type) {
case 'PLAIN_TEXT':

View File

@ -4,7 +4,6 @@ import { OrderedList as OrderedListProps } from '@rocket.chat/message-parser';
import Inline from './Inline';
import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface IOrderedListProps {
@ -12,12 +11,12 @@ interface IOrderedListProps {
}
const OrderedList = ({ value }: IOrderedListProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<View>
{value.map((item, index) => (
<View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{index + 1}. </Text>
<Text style={[styles.text, { color: colors.bodyText }]}>{index + 1}. </Text>
<Inline value={item.value} />
</View>
))}

View File

@ -5,16 +5,15 @@ import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
import Inline from './Inline';
import styles from '../styles';
import { useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
interface IParagraphProps {
value: ParagraphProps['value'];
}
const Paragraph = ({ value }: IParagraphProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<Text style={[styles.text, { color: themes[theme].bodyText }]}>
<Text style={[styles.text, { color: colors.bodyText }]}>
<Inline value={value} />
</Text>
);

View File

@ -4,16 +4,15 @@ import { Plain as PlainProps } from '@rocket.chat/message-parser';
import styles from '../styles';
import { useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
interface IPlainProps {
value: PlainProps['value'];
}
const Plain = ({ value }: IPlainProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<Text accessibilityLabel={value} style={[styles.plainText, { color: themes[theme].bodyText }]}>
<Text accessibilityLabel={value} style={[styles.plainText, { color: colors.bodyText }]}>
{value}
</Text>
);

View File

@ -2,7 +2,6 @@ import React from 'react';
import { View } from 'react-native';
import { Quote as QuoteProps } from '@rocket.chat/message-parser';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import styles from '../styles';
import Paragraph from './Paragraph';
@ -12,10 +11,10 @@ interface IQuoteProps {
}
const Quote = ({ value }: IQuoteProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<View style={styles.container}>
<View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} />
<View style={[styles.quote, { backgroundColor: colors.borderColor }]} />
<View style={styles.childContainer}>
{value.map(item => (
<Paragraph value={item.value} />

View File

@ -4,7 +4,6 @@ import { Tasks as TasksProps } from '@rocket.chat/message-parser';
import Inline from './Inline';
import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface ITasksProps {
@ -12,12 +11,12 @@ interface ITasksProps {
}
const TaskList = ({ value = [] }: ITasksProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<View>
{value.map(item => (
<View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
<Text style={[styles.text, { color: colors.bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
<Inline value={item.value} />
</View>
))}

View File

@ -4,7 +4,6 @@ import { View, Text } from 'react-native';
import Inline from './Inline';
import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface IUnorderedListProps {
@ -12,12 +11,12 @@ interface IUnorderedListProps {
}
const UnorderedList = ({ value }: IUnorderedListProps) => {
const { theme } = useTheme();
const { colors } = useTheme();
return (
<View>
{value.map(item => (
<View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>- </Text>
<Text style={[styles.text, { color: colors.bodyText }]}>- </Text>
<Inline value={item.value} />
</View>
))}

View File

@ -280,7 +280,6 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<View
style={[

View File

@ -100,7 +100,6 @@ const Fields = React.memo(
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
style={[styles.markdownFontSize]}
/>
</View>

View File

@ -63,7 +63,6 @@ const Content = React.memo(
navToRoomInfo={props.navToRoomInfo}
tmid={props.tmid}
useRealName={props.useRealName}
theme={theme}
onLinkPress={onLinkPress}
/>
);

View File

@ -83,7 +83,6 @@ const ImageContainer = React.memo(
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<MessageImage imgUri={img} theme={theme} />
</View>

View File

@ -131,7 +131,6 @@ const Description = React.memo(
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
);
},
@ -184,13 +183,7 @@ const Fields = React.memo(
{attachment.fields.map(field => (
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
<Markdown
msg={field?.value || ''}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<Markdown msg={field?.value || ''} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} />
</View>
))}
</View>
@ -271,13 +264,7 @@ const Reply = React.memo(
) : null}
</View>
</Touchable>
<Markdown
msg={attachment.description}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<Markdown msg={attachment.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} />
</>
);
},

View File

@ -83,7 +83,6 @@ const Video = React.memo(
username={user.username}
getCustomEmoji={getCustomEmoji}
style={[isReply && style]}
theme={theme}
/>
<Touchable
disabled={isReply}

View File

@ -18,6 +18,7 @@ export interface ICustomEmoji {
baseUrl?: string;
emoji: IEmoji;
style: StyleProp<ImageStyle>;
testID?: string;
}
export interface ICustomEmojiModel {

View File

@ -84,7 +84,7 @@ const Item = ({ label, content, theme, testID }: IItem) =>
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>
{label}
</Text>
<Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} theme={theme} />
<Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} />
</View>
) : null;

View File

@ -40,10 +40,10 @@ class E2EHowItWorksView extends React.Component<TE2EHowItWorksViewProps, any> {
return (
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-how-it-works-view'>
<Markdown msg={I18n.t('E2E_How_It_Works_info1')} style={infoStyle} theme={theme} />
<Markdown msg={I18n.t('E2E_How_It_Works_info2')} style={infoStyle} theme={theme} />
<Markdown msg={I18n.t('E2E_How_It_Works_info3')} style={infoStyle} theme={theme} />
<Markdown msg={I18n.t('E2E_How_It_Works_info4')} style={infoStyle} theme={theme} />
<Markdown msg={I18n.t('E2E_How_It_Works_info1')} style={infoStyle} />
<Markdown msg={I18n.t('E2E_How_It_Works_info2')} style={infoStyle} />
<Markdown msg={I18n.t('E2E_How_It_Works_info3')} style={infoStyle} />
<Markdown msg={I18n.t('E2E_How_It_Works_info4')} style={infoStyle} />
</SafeAreaView>
);
}

View File

@ -23,7 +23,7 @@ const InviteUsersView = ({ route, navigation }: IInviteUsersViewProps): React.Re
const rid = route.params?.rid;
const timeDateFormat = useSelector((state: IApplicationState) => state.settings.Message_TimeAndDateFormat as string);
const invite = useSelector((state: IApplicationState) => state.inviteLinks.invite);
const { colors, theme } = useTheme();
const { colors } = useTheme();
const dispatch = useDispatch();
useEffect(() => {
@ -81,7 +81,7 @@ const InviteUsersView = ({ route, navigation }: IInviteUsersViewProps): React.Re
const renderExpiration = () => {
const expirationMessage = linkExpirationText();
return <Markdown msg={expirationMessage} theme={theme} />;
return <Markdown msg={expirationMessage} />;
};
return (

View File

@ -24,7 +24,7 @@ const Item = ({ label, content, testID }: IItem) => {
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>
{label}
</Text>
<Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} theme={theme} />
<Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} />
</View>
);
};

View File

@ -45,7 +45,7 @@ const Banner = React.memo(
<View style={[styles.modalView, { backgroundColor: themes[theme].bannerBackground }]}>
<Text style={[styles.bannerModalTitle, { color: themes[theme].auxiliaryText }]}>{title}</Text>
<ScrollView style={styles.modalScrollView}>
<Markdown msg={text} theme={theme} />
<Markdown msg={text} />
</ScrollView>
</View>
</Modal>

View File

@ -6,7 +6,7 @@ import { storiesOf } from '@storybook/react-native';
import { longText } from '../../../../storybook/utils';
import { ThemeContext } from '../../../theme';
import { Message, MessageDecorator, StoryProvider } from '../../../../storybook/stories/Message';
import { MessageTypeLoad, themes } from '../../../lib/constants';
import { colors, MessageTypeLoad, themes } from '../../../lib/constants';
import LoadMore from './index';
const stories = storiesOf('LoadMore', module);
@ -24,7 +24,7 @@ stories.add('basic', () => (
));
const ThemeStory = ({ theme }) => (
<ThemeContext.Provider value={{ theme }}>
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
<LoadMore load={load} type={MessageTypeLoad.PREVIOUS_CHUNK} />
<Message msg='Hey!' theme={theme} />

View File

@ -334,7 +334,7 @@ class SearchMessagesView extends React.Component<ISearchMessagesViewProps, ISear
placeholder={I18n.t('Search_Messages')}
testID='search-message-view-input'
/>
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} theme={theme} />
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} />
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
</View>
{this.renderList()}

View File

@ -48,11 +48,11 @@ const stories = storiesOf('Markdown', module).addDecorator(story => <Provider st
stories.add('Text', () => (
<View style={styles.container}>
<Markdown msg='This is Rocket.Chat' theme={theme} />
<Markdown msg={longText} theme={theme} />
<Markdown msg={lineBreakText} theme={theme} />
<Markdown msg={sequentialEmptySpacesText} theme={theme} />
<Markdown msg='Strong emphasis, aka bold, with **asterisks** or __underscores__' theme={theme} />
<Markdown msg='This is Rocket.Chat' />
<Markdown msg={longText} />
<Markdown msg={lineBreakText} />
<Markdown msg={sequentialEmptySpacesText} />
<Markdown msg='Strong emphasis, aka bold, with **asterisks** or __underscores__' />
</View>
));
@ -71,7 +71,6 @@ stories.add('Mentions', () => (
<ScrollView style={styles.container}>
<Markdown
msg='@rocket.cat @name1 @all @here @unknown'
theme={theme}
mentions={[
{ _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' },
{ _id: 'random2', name: 'Name', username: 'name1' },
@ -82,7 +81,6 @@ stories.add('Mentions', () => (
/>
<Markdown
msg='@rocket.cat @name1 @all @here @unknown'
theme={theme}
mentions={[
{ _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' },
{ _id: 'random2', name: 'Name', username: 'name1' },
@ -97,21 +95,16 @@ stories.add('Mentions', () => (
stories.add('Hashtag', () => (
<View style={styles.container}>
<Markdown msg='#test-channel #unknown' theme={theme} channels={[{ _id: '123', name: 'test-channel' }]} />
<Markdown msg='#test-channel #unknown' channels={[{ _id: '123', name: 'test-channel' }]} />
</View>
));
stories.add('Emoji', () => (
<View style={styles.container}>
<Markdown msg='Unicode: 😃😇👍' theme={theme} />
<Markdown msg='Shortnames: :joy::+1:' theme={theme} />
<Markdown
msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:'
theme={theme}
getCustomEmoji={getCustomEmoji}
baseUrl={baseUrl}
/>
<Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
<Markdown msg='Unicode: 😃😇👍' />
<Markdown msg='Shortnames: :joy::+1:' />
<Markdown msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:' getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
<Markdown msg='😃 :+1: :marioparty:' getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
</View>
));
@ -120,52 +113,50 @@ stories.add('Block quote', () => (
<Markdown
msg={`> This is block quote
this is a normal line`}
theme={theme}
/>
</View>
));
stories.add('Links', () => (
<View style={styles.container}>
<Markdown msg='[Markdown link](https://rocket.chat): `[description](url)`' theme={theme} />
<Markdown msg='<https://rocket.chat|Formatted Link>: `<url|description>`' theme={theme} />
<Markdown msg='[Markdown link](https://rocket.chat): `[description](url)`' />
<Markdown msg='<https://rocket.chat|Formatted Link>: `<url|description>`' />
</View>
));
stories.add('Image', () => (
<View style={styles.container}>
<Markdown msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' theme={theme} />
<Markdown msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' />
</View>
));
stories.add('Headers', () => (
<View style={styles.container}>
<Markdown msg='# Header 1' theme={theme} />
<Markdown msg='## Header 2' theme={theme} />
<Markdown msg='### Header 3' theme={theme} />
<Markdown msg='#### Header 4' theme={theme} />
<Markdown msg='##### Header 5' theme={theme} />
<Markdown msg='###### Header 6' theme={theme} />
<Markdown msg='# Header 1' />
<Markdown msg='## Header 2' />
<Markdown msg='### Header 3' />
<Markdown msg='#### Header 4' />
<Markdown msg='##### Header 5' />
<Markdown msg='###### Header 6' />
</View>
));
stories.add('Code', () => (
<View style={styles.container}>
<Markdown msg='This is `inline code`' theme={theme} />
<Markdown msg='This is `inline code`' />
<Markdown
msg='Inline `code` has `back-ticks around` it.
```
Code block
```'
theme={theme}
/>
</View>
));
stories.add('Lists', () => (
<View style={styles.container}>
<Markdown msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} theme={theme} />
<Markdown msg={'1. Open Source\n2. Rocket.Chat'} theme={theme} />
<Markdown msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} />
<Markdown msg={'1. Open Source\n2. Rocket.Chat'} />
</View>
));
@ -176,7 +167,6 @@ stories.add('Table', () => (
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column'
theme={theme}
/>
</View>
));

View File

@ -18,7 +18,7 @@ exports[`Storyshots Avatar Custom style 1`] = `"{\\"type\\":\\"View\\",\\"props\
exports[`Storyshots Avatar Direct 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/diego.mello?format=png&size=112\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Emoji 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},[{\\"width\\":30,\\"height\\":30},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/emoji-custom/troll.jpg\\",\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"contain\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Emoji 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},[{\\"width\\":30,\\"height\\":30},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"testID\\":\\"avatar-custom-emoji\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/emoji-custom/troll.jpg\\",\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"contain\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Static 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long