[NEW] Support new message parser (#3313)
* Add message parser to profile view and db * Add md to db * Remove changes to Xcode project * Remove message-parser lib and add enable message parser field to User model * Fix message parser * Remove admin enableMessageParserEarlyAdoption * Add NewMarkdown component * Remove NewMarkdown component and add specific components for new message parser * Add new parser components * Fix BigEmoji * Updated components and added more Code components * update components and add storybooks * Update Code component and add it to storybooks * Update Mention component * Minor tweaks * Add server message parser validation * Renamed folder, add @rocket.chat/message-parser, migrate some files to TypeScript * Migrate components to TypeScript and fix styling * Change interfaces and add TaskListComponent and styles * Fix new markdown and styles * Fix inlinecode * Stop using server setting * Use enableMessageParserEarlyAdoption on mapStateToProps * Remove React.FC * add link to bold, italic and strike * Update parser components * Fix missing components * Minor tweak * Fix lint and add getCustomEmojis * Fix customEmojis * Update emojis * Minor tweak * disconnect markdown from store * Use @rocket.chat/message-parser@0.30.0 * Fix link style * Unify lists and styles * Remove style prop * Use big emoji as a normal token * Remove unnecessary memo * Fix code styles * Update tests * Conditionally create renderer * Use Context instead of prop drill * Fix Link component * Fix plain text regression and update tests Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
2d392425ff
commit
76a99519ec
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { events, logEvent } from '../../utils/log';
|
import { events, logEvent } from '../../utils/log';
|
||||||
|
@ -9,20 +10,20 @@ interface IAtMention {
|
||||||
mention: string;
|
mention: string;
|
||||||
username: string;
|
username: string;
|
||||||
navToRoomInfo: Function;
|
navToRoomInfo: Function;
|
||||||
style: any;
|
style?: any;
|
||||||
useRealName: boolean;
|
useRealName: boolean;
|
||||||
theme: string;
|
|
||||||
mentions: any;
|
mentions: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName, theme }: IAtMention) => {
|
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName }: IAtMention) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
if (mention === 'all' || mention === 'here') {
|
if (mention === 'all' || mention === 'here') {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.mention,
|
styles.mention,
|
||||||
{
|
{
|
||||||
color: themes[theme].mentionGroupColor
|
color: themes[theme!].mentionGroupColor
|
||||||
},
|
},
|
||||||
...style
|
...style
|
||||||
]}>
|
]}>
|
||||||
|
@ -34,11 +35,11 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
|
||||||
let mentionStyle = {};
|
let mentionStyle = {};
|
||||||
if (mention === username) {
|
if (mention === username) {
|
||||||
mentionStyle = {
|
mentionStyle = {
|
||||||
color: themes[theme].mentionMeColor
|
color: themes[theme!].mentionMeColor
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
mentionStyle = {
|
mentionStyle = {
|
||||||
color: themes[theme].mentionOtherColor
|
color: themes[theme!].mentionOtherColor
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`@${mention}`}</Text>;
|
return <Text style={[styles.text, { color: themes[theme!].bodyText }, ...style]}>{`@${mention}`}</Text>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default AtMention;
|
export default AtMention;
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text, TextStyle } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IHashtag {
|
interface IHashtag {
|
||||||
hashtag: string;
|
hashtag: string;
|
||||||
navToRoomInfo: Function;
|
navToRoomInfo: Function;
|
||||||
style: [];
|
style?: TextStyle[];
|
||||||
theme: string;
|
|
||||||
channels: {
|
channels: {
|
||||||
|
[index: number]: string | number;
|
||||||
name: string;
|
name: string;
|
||||||
_id: number;
|
_id: number;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], theme }: IHashtag) => {
|
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IHashtag) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
const index = channels.findIndex(channel => channel.name === hashtag);
|
const index = channels?.findIndex(channel => channel.name === hashtag);
|
||||||
const navParam = {
|
const navParam = {
|
||||||
t: 'c',
|
t: 'c',
|
||||||
rid: channels[index]._id
|
rid: channels[index]._id
|
||||||
|
@ -31,7 +34,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them
|
||||||
style={[
|
style={[
|
||||||
styles.mention,
|
styles.mention,
|
||||||
{
|
{
|
||||||
color: themes[theme].mentionOtherColor
|
color: themes[theme!].mentionOtherColor
|
||||||
},
|
},
|
||||||
...style
|
...style
|
||||||
]}
|
]}
|
||||||
|
@ -40,7 +43,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`#${hashtag}`}</Text>;
|
return <Text style={[styles.text, { color: themes[theme!].bodyText }, ...style]}>{`#${hashtag}`}</Text>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Hashtag;
|
export default Hashtag;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Image, Text } from 'react-native';
|
||||||
import { Node, Parser } from 'commonmark';
|
import { Node, Parser } from 'commonmark';
|
||||||
import Renderer from 'commonmark-react-renderer';
|
import Renderer from 'commonmark-react-renderer';
|
||||||
import removeMarkdown from 'remove-markdown';
|
import removeMarkdown from 'remove-markdown';
|
||||||
|
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -20,9 +21,20 @@ import MarkdownTableCell from './TableCell';
|
||||||
import mergeTextNodes from './mergeTextNodes';
|
import mergeTextNodes from './mergeTextNodes';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { isValidURL } from '../../utils/url';
|
import { isValidURL } from '../../utils/url';
|
||||||
|
import NewMarkdown from './new';
|
||||||
|
|
||||||
|
interface IUser {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
|
||||||
|
|
||||||
interface IMarkdownProps {
|
interface IMarkdownProps {
|
||||||
msg: string;
|
msg: string;
|
||||||
|
md: MarkdownAST;
|
||||||
|
mentions: UserMention[];
|
||||||
getCustomEmoji: Function;
|
getCustomEmoji: Function;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -35,7 +47,7 @@ interface IMarkdownProps {
|
||||||
name: string;
|
name: string;
|
||||||
_id: number;
|
_id: number;
|
||||||
}[];
|
}[];
|
||||||
mentions: object[];
|
enableMessageParser: boolean;
|
||||||
navToRoomInfo: Function;
|
navToRoomInfo: Function;
|
||||||
preview: boolean;
|
preview: boolean;
|
||||||
theme: string;
|
theme: string;
|
||||||
|
@ -97,7 +109,9 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
||||||
|
|
||||||
constructor(props: IMarkdownProps) {
|
constructor(props: IMarkdownProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.renderer = this.createRenderer();
|
if (!this.isNewMarkdown) {
|
||||||
|
this.renderer = this.createRenderer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createRenderer = () =>
|
createRenderer = () =>
|
||||||
|
@ -139,6 +153,11 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
||||||
renderParagraphsInLists: true
|
renderParagraphsInLists: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
get isNewMarkdown(): boolean {
|
||||||
|
const { md, enableMessageParser } = this.props;
|
||||||
|
return enableMessageParser && !!md;
|
||||||
|
}
|
||||||
|
|
||||||
editedMessage = (ast: any) => {
|
editedMessage = (ast: any) => {
|
||||||
const { isEdited } = this.props;
|
const { isEdited } = this.props;
|
||||||
if (isEdited) {
|
if (isEdited) {
|
||||||
|
@ -227,12 +246,12 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderHashtag = ({ hashtag }: { hashtag: string }) => {
|
renderHashtag = ({ hashtag }: { hashtag: string }) => {
|
||||||
const { channels, navToRoomInfo, style, theme } = this.props;
|
const { channels, navToRoomInfo, style } = this.props;
|
||||||
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} theme={theme} style={style} />;
|
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} style={style} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderAtMention = ({ mentionName }: { mentionName: string }) => {
|
renderAtMention = ({ mentionName }: { mentionName: string }) => {
|
||||||
const { username, mentions, navToRoomInfo, useRealName, style, theme } = this.props;
|
const { username, mentions, navToRoomInfo, useRealName, style } = this.props;
|
||||||
return (
|
return (
|
||||||
<MarkdownAtMention
|
<MarkdownAtMention
|
||||||
mentions={mentions}
|
mentions={mentions}
|
||||||
|
@ -240,7 +259,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
username={username}
|
username={username}
|
||||||
navToRoomInfo={navToRoomInfo}
|
navToRoomInfo={navToRoomInfo}
|
||||||
theme={theme}
|
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -329,12 +347,44 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { msg, numberOfLines, preview = false, theme, style = [], testID } = this.props;
|
const {
|
||||||
|
msg,
|
||||||
|
md,
|
||||||
|
numberOfLines,
|
||||||
|
preview = false,
|
||||||
|
theme,
|
||||||
|
style = [],
|
||||||
|
testID,
|
||||||
|
mentions,
|
||||||
|
channels,
|
||||||
|
navToRoomInfo,
|
||||||
|
useRealName,
|
||||||
|
username,
|
||||||
|
getCustomEmoji,
|
||||||
|
baseUrl,
|
||||||
|
onLinkPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return null;
|
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);
|
let m = formatText(msg);
|
||||||
|
|
||||||
// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
|
// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import { BigEmoji as BigEmojiProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Emoji from './Emoji';
|
||||||
|
|
||||||
|
interface IBigEmojiProps {
|
||||||
|
value: BigEmojiProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const BigEmoji = ({ value }: IBigEmojiProps): JSX.Element => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{value.map(block => (
|
||||||
|
<Emoji value={block.value} isBigEmoji />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BigEmoji;
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text } from 'react-native';
|
||||||
|
import { Bold as BoldProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import sharedStyles from '../../../views/Styles';
|
||||||
|
import Strike from './Strike';
|
||||||
|
import Italic from './Italic';
|
||||||
|
import Plain from './Plain';
|
||||||
|
import Link from './Link';
|
||||||
|
|
||||||
|
interface IBoldProps {
|
||||||
|
value: BoldProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
...sharedStyles.textBold
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Bold = ({ value }: IBoldProps): JSX.Element => (
|
||||||
|
<Text style={styles.text}>
|
||||||
|
{value.map(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'LINK':
|
||||||
|
return <Link value={block.value} />;
|
||||||
|
case 'PLAIN_TEXT':
|
||||||
|
return <Plain value={block.value} />;
|
||||||
|
case 'STRIKE':
|
||||||
|
return <Strike value={block.value} />;
|
||||||
|
case 'ITALIC':
|
||||||
|
return <Italic value={block.value} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Bold;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { Code as CodeProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import styles from '../styles';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import CodeLine from './CodeLine';
|
||||||
|
|
||||||
|
interface ICodeProps {
|
||||||
|
value: CodeProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Code = ({ value }: ICodeProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.codeBlock,
|
||||||
|
{
|
||||||
|
color: themes[theme!].bodyText,
|
||||||
|
backgroundColor: themes[theme!].bannerBackground,
|
||||||
|
borderColor: themes[theme!].borderColor
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
{value.map(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'CODE_LINE':
|
||||||
|
return <CodeLine value={block.value} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Code;
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { CodeLine as CodeLineProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
interface ICodeLineProps {
|
||||||
|
value: CodeLineProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeLine = ({ value }: ICodeLineProps): JSX.Element | null => {
|
||||||
|
if (value.type !== 'PLAIN_TEXT') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Text>{value.value}</Text>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeLine;
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { Emoji as EmojiProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import styles from '../styles';
|
||||||
|
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||||
|
import MarkdownContext from './MarkdownContext';
|
||||||
|
|
||||||
|
interface IEmojiProps {
|
||||||
|
value: EmojiProps['value'];
|
||||||
|
isBigEmoji?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Emoji = ({ value, isBigEmoji }: IEmojiProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext);
|
||||||
|
const emojiUnicode = shortnameToUnicode(`:${value.value}:`);
|
||||||
|
const emoji = getCustomEmoji?.(value.value);
|
||||||
|
|
||||||
|
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>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Emoji;
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { Heading as HeadingProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import styles from '../styles';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
|
interface IHeadingProps {
|
||||||
|
value: HeadingProps['value'];
|
||||||
|
level: HeadingProps['level'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Heading = ({ value, level }: IHeadingProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const textStyle = styles[`heading${level}`];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text style={[textStyle, { color: themes[theme!].bodyText }]}>
|
||||||
|
{value.map(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'PLAIN_TEXT':
|
||||||
|
return block.value;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Heading;
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Image as ImageProps } from '@rocket.chat/message-parser';
|
||||||
|
import { createImageProgress } from 'react-native-image-progress';
|
||||||
|
import * as Progress from 'react-native-progress';
|
||||||
|
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
|
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import styles from '../../message/styles';
|
||||||
|
|
||||||
|
interface IImageProps {
|
||||||
|
value: ImageProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
type TMessageImage = {
|
||||||
|
img: string;
|
||||||
|
theme: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 Image = ({ value }: IImageProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const { src } = value;
|
||||||
|
|
||||||
|
return <MessageImage img={src.value} theme={theme!} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Image;
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Hashtag from '../Hashtag';
|
||||||
|
import AtMention from '../AtMention';
|
||||||
|
import Link from './Link';
|
||||||
|
import Plain from './Plain';
|
||||||
|
import Bold from './Bold';
|
||||||
|
import Strike from './Strike';
|
||||||
|
import Italic from './Italic';
|
||||||
|
import Emoji from './Emoji';
|
||||||
|
import InlineCode from './InlineCode';
|
||||||
|
import Image from './Image';
|
||||||
|
import MarkdownContext from './MarkdownContext';
|
||||||
|
|
||||||
|
interface IParagraphProps {
|
||||||
|
value: ParagraphProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Inline = ({ value }: IParagraphProps): JSX.Element => {
|
||||||
|
const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{value.map(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'IMAGE':
|
||||||
|
return <Image value={block.value} />;
|
||||||
|
case 'PLAIN_TEXT':
|
||||||
|
return <Plain value={block.value} />;
|
||||||
|
case 'BOLD':
|
||||||
|
return <Bold value={block.value} />;
|
||||||
|
case 'STRIKE':
|
||||||
|
return <Strike value={block.value} />;
|
||||||
|
case 'ITALIC':
|
||||||
|
return <Italic value={block.value} />;
|
||||||
|
case 'LINK':
|
||||||
|
return <Link value={block.value} />;
|
||||||
|
case 'MENTION_USER':
|
||||||
|
return (
|
||||||
|
<AtMention
|
||||||
|
mention={block.value.value}
|
||||||
|
useRealName={useRealName}
|
||||||
|
username={username}
|
||||||
|
navToRoomInfo={navToRoomInfo}
|
||||||
|
mentions={mentions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'EMOJI':
|
||||||
|
return <Emoji value={block.value} />;
|
||||||
|
case 'MENTION_CHANNEL':
|
||||||
|
return <Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />;
|
||||||
|
case 'INLINE_CODE':
|
||||||
|
return <InlineCode value={block.value} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Inline;
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { InlineCode as InlineCodeProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import styles from '../styles';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
|
interface IInlineCodeProps {
|
||||||
|
value: InlineCodeProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const InlineCode = ({ value }: IInlineCodeProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.codeInline,
|
||||||
|
{
|
||||||
|
color: themes[theme!].bodyText,
|
||||||
|
backgroundColor: themes[theme!].bannerBackground,
|
||||||
|
borderColor: themes[theme!].borderColor
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
{(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'PLAIN_TEXT':
|
||||||
|
return <Text>{block.value}</Text>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})(value)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InlineCode;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text } from 'react-native';
|
||||||
|
import { Italic as ItalicProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Strike from './Strike';
|
||||||
|
import Bold from './Bold';
|
||||||
|
import Plain from './Plain';
|
||||||
|
import Link from './Link';
|
||||||
|
|
||||||
|
interface IItalicProps {
|
||||||
|
value: ItalicProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
fontStyle: 'italic'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Italic = ({ value }: IItalicProps): JSX.Element => (
|
||||||
|
<Text style={styles.text}>
|
||||||
|
{value.map(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'LINK':
|
||||||
|
return <Link value={block.value} />;
|
||||||
|
case 'PLAIN_TEXT':
|
||||||
|
return <Plain value={block.value} />;
|
||||||
|
case 'STRIKE':
|
||||||
|
return <Strike value={block.value} />;
|
||||||
|
case 'BOLD':
|
||||||
|
return <Bold value={block.value} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Italic;
|
|
@ -0,0 +1,60 @@
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { Text, Clipboard } from 'react-native';
|
||||||
|
import { Link as LinkProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import styles from '../styles';
|
||||||
|
import I18n from '../../../i18n';
|
||||||
|
import { LISTENER } from '../../Toast';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import openLink from '../../../utils/openLink';
|
||||||
|
import EventEmitter from '../../../utils/events';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import Strike from './Strike';
|
||||||
|
import Italic from './Italic';
|
||||||
|
import Bold from './Bold';
|
||||||
|
import MarkdownContext from './MarkdownContext';
|
||||||
|
|
||||||
|
interface ILinkProps {
|
||||||
|
value: LinkProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Link = ({ value }: ILinkProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const { onLinkPress } = useContext(MarkdownContext);
|
||||||
|
const { src, label } = value;
|
||||||
|
const handlePress = () => {
|
||||||
|
if (!src.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (onLinkPress) {
|
||||||
|
return onLinkPress(src.value);
|
||||||
|
}
|
||||||
|
openLink(src.value, theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLongPress = () => {
|
||||||
|
Clipboard.setString(src.value);
|
||||||
|
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: themes[theme!].actionTintColor }]}>
|
||||||
|
{(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'PLAIN_TEXT':
|
||||||
|
return block.value;
|
||||||
|
case 'STRIKE':
|
||||||
|
return <Strike value={block.value} />;
|
||||||
|
case 'ITALIC':
|
||||||
|
return <Italic value={block.value} />;
|
||||||
|
case 'BOLD':
|
||||||
|
return <Bold value={block.value} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})(label)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Link;
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { UserMention } from '../../message/interfaces';
|
||||||
|
|
||||||
|
interface IMarkdownContext {
|
||||||
|
mentions: UserMention[];
|
||||||
|
channels: {
|
||||||
|
name: string;
|
||||||
|
_id: number;
|
||||||
|
}[];
|
||||||
|
useRealName: boolean;
|
||||||
|
username: string;
|
||||||
|
baseUrl: string;
|
||||||
|
navToRoomInfo: Function;
|
||||||
|
getCustomEmoji?: Function;
|
||||||
|
onLinkPress?: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
mentions: [],
|
||||||
|
channels: [],
|
||||||
|
useRealName: false,
|
||||||
|
username: '',
|
||||||
|
baseUrl: '',
|
||||||
|
navToRoomInfo: () => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MarkdownContext = React.createContext<IMarkdownContext>(defaultState);
|
||||||
|
export default MarkdownContext;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
import { OrderedList as OrderedListProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Inline from './Inline';
|
||||||
|
import styles from '../styles';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
|
interface IOrderedListProps {
|
||||||
|
value: OrderedListProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderedList = ({ value }: IOrderedListProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{value.map((item, index) => (
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>{index + 1}. </Text>
|
||||||
|
<Inline value={item.value} />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderedList;
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Inline from './Inline';
|
||||||
|
import styles from '../styles';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
|
||||||
|
interface IParagraphProps {
|
||||||
|
value: ParagraphProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Paragraph = ({ value }: IParagraphProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>
|
||||||
|
<Inline value={value} />
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Paragraph;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { Plain as PlainProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import styles from '../styles';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
|
||||||
|
interface IPlainProps {
|
||||||
|
value: PlainProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Plain = ({ value }: IPlainProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<Text accessibilityLabel={value} style={[styles.plainText, { color: themes[theme!].bodyText }]}>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Plain;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { Quote as QuoteProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import styles from '../styles';
|
||||||
|
import Paragraph from './Paragraph';
|
||||||
|
|
||||||
|
interface IQuoteProps {
|
||||||
|
value: QuoteProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Quote = ({ value }: IQuoteProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={[styles.quote, { backgroundColor: themes[theme!].borderColor }]} />
|
||||||
|
<View style={styles.childContainer}>
|
||||||
|
{value.map(item => (
|
||||||
|
<Paragraph value={item.value} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Quote;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text } from 'react-native';
|
||||||
|
import { Strike as StrikeProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Bold from './Bold';
|
||||||
|
import Italic from './Italic';
|
||||||
|
import Plain from './Plain';
|
||||||
|
import Link from './Link';
|
||||||
|
|
||||||
|
interface IStrikeProps {
|
||||||
|
value: StrikeProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
textDecorationLine: 'line-through'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Strike = ({ value }: IStrikeProps): JSX.Element => (
|
||||||
|
<Text style={styles.text}>
|
||||||
|
{value.map(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'LINK':
|
||||||
|
return <Link value={block.value} />;
|
||||||
|
case 'PLAIN_TEXT':
|
||||||
|
return <Plain value={block.value} />;
|
||||||
|
case 'BOLD':
|
||||||
|
return <Bold value={block.value} />;
|
||||||
|
case 'ITALIC':
|
||||||
|
return <Italic value={block.value} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Strike;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import { Tasks as TasksProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Inline from './Inline';
|
||||||
|
import styles from '../styles';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
|
interface ITasksProps {
|
||||||
|
value: TasksProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaskList = ({ value = [] }: ITasksProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{value.map(item => (
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
|
||||||
|
<Inline value={item.value} />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskList;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { UnorderedList as UnorderedListProps } from '@rocket.chat/message-parser';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
|
||||||
|
import Inline from './Inline';
|
||||||
|
import styles from '../styles';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
|
interface IUnorderedListProps {
|
||||||
|
value: UnorderedListProps['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const UnorderedList = ({ value }: IUnorderedListProps): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{value.map(item => (
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>- </Text>
|
||||||
|
<Inline value={item.value} />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UnorderedList;
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
import Quote from './Quote';
|
||||||
|
import Paragraph from './Paragraph';
|
||||||
|
import Heading from './Heading';
|
||||||
|
import Code from './Code';
|
||||||
|
import BigEmoji from './BigEmoji';
|
||||||
|
import OrderedList from './OrderedList';
|
||||||
|
import UnorderedList from './UnorderedList';
|
||||||
|
import { UserMention } from '../../message/interfaces';
|
||||||
|
import TaskList from './TaskList';
|
||||||
|
import MarkdownContext from './MarkdownContext';
|
||||||
|
|
||||||
|
interface IBodyProps {
|
||||||
|
tokens: MarkdownAST;
|
||||||
|
mentions: UserMention[];
|
||||||
|
channels: {
|
||||||
|
name: string;
|
||||||
|
_id: number;
|
||||||
|
}[];
|
||||||
|
getCustomEmoji?: Function;
|
||||||
|
onLinkPress?: Function;
|
||||||
|
navToRoomInfo: Function;
|
||||||
|
useRealName: boolean;
|
||||||
|
username: string;
|
||||||
|
baseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Body = ({
|
||||||
|
tokens,
|
||||||
|
mentions,
|
||||||
|
channels,
|
||||||
|
useRealName,
|
||||||
|
username,
|
||||||
|
navToRoomInfo,
|
||||||
|
getCustomEmoji,
|
||||||
|
baseUrl,
|
||||||
|
onLinkPress
|
||||||
|
}: IBodyProps): JSX.Element => (
|
||||||
|
<MarkdownContext.Provider
|
||||||
|
value={{
|
||||||
|
mentions,
|
||||||
|
channels,
|
||||||
|
useRealName,
|
||||||
|
username,
|
||||||
|
navToRoomInfo,
|
||||||
|
getCustomEmoji,
|
||||||
|
baseUrl,
|
||||||
|
onLinkPress
|
||||||
|
}}>
|
||||||
|
{tokens.map(block => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'BIG_EMOJI':
|
||||||
|
return <BigEmoji value={block.value} />;
|
||||||
|
case 'UNORDERED_LIST':
|
||||||
|
return <UnorderedList value={block.value} />;
|
||||||
|
case 'ORDERED_LIST':
|
||||||
|
return <OrderedList value={block.value} />;
|
||||||
|
case 'TASKS':
|
||||||
|
return <TaskList value={block.value} />;
|
||||||
|
case 'QUOTE':
|
||||||
|
return <Quote value={block.value} />;
|
||||||
|
case 'PARAGRAPH':
|
||||||
|
return <Paragraph value={block.value} />;
|
||||||
|
case 'CODE':
|
||||||
|
return <Code value={block.value} />;
|
||||||
|
case 'HEADING':
|
||||||
|
return <Heading value={block.value} level={block.level} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</MarkdownContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Body;
|
|
@ -30,6 +30,10 @@ export default StyleSheet.create<any>({
|
||||||
del: {
|
del: {
|
||||||
textDecorationLine: 'line-through'
|
textDecorationLine: 'line-through'
|
||||||
},
|
},
|
||||||
|
plainText: {
|
||||||
|
fontSize: 16,
|
||||||
|
flexShrink: 1
|
||||||
|
},
|
||||||
text: {
|
text: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
|
@ -70,12 +74,16 @@ export default StyleSheet.create<any>({
|
||||||
resizeMode: 'contain'
|
resizeMode: 'contain'
|
||||||
},
|
},
|
||||||
codeInline: {
|
codeInline: {
|
||||||
|
fontSize: 16,
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textRegular,
|
||||||
...codeFontFamily,
|
...codeFontFamily,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 4
|
borderRadius: 4,
|
||||||
|
paddingLeft: 2,
|
||||||
|
paddingTop: 2
|
||||||
},
|
},
|
||||||
codeBlock: {
|
codeBlock: {
|
||||||
|
fontSize: 16,
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textRegular,
|
||||||
...codeFontFamily,
|
...codeFontFamily,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
|
|
@ -51,8 +51,10 @@ const Content = React.memo(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={props.msg}
|
msg={props.msg}
|
||||||
|
md={props.md}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
getCustomEmoji={props.getCustomEmoji}
|
getCustomEmoji={props.getCustomEmoji}
|
||||||
|
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||||
username={user.username}
|
username={user.username}
|
||||||
isEdited={props.isEdited}
|
isEdited={props.isEdited}
|
||||||
numberOfLines={isPreview ? 1 : 0}
|
numberOfLines={isPreview ? 1 : 0}
|
||||||
|
@ -103,6 +105,9 @@ const Content = React.memo(
|
||||||
if (prevProps.isIgnored !== nextProps.isIgnored) {
|
if (prevProps.isIgnored !== nextProps.isIgnored) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!dequal(prevProps.md, nextProps.md)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!dequal(prevProps.mentions, nextProps.mentions)) {
|
if (!dequal(prevProps.mentions, nextProps.mentions)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,7 +357,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
|
||||||
unread,
|
unread,
|
||||||
blocks,
|
blocks,
|
||||||
autoTranslate: autoTranslateMessage,
|
autoTranslate: autoTranslateMessage,
|
||||||
replies
|
replies,
|
||||||
|
md
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
let message = msg;
|
let message = msg;
|
||||||
|
@ -391,6 +392,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
|
||||||
<Message
|
<Message
|
||||||
id={id}
|
id={id}
|
||||||
msg={message}
|
msg={message}
|
||||||
|
md={md}
|
||||||
rid={rid}
|
rid={rid}
|
||||||
author={u}
|
author={u}
|
||||||
ts={ts}
|
ts={ts}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
export interface IMessageAttachments {
|
export interface IMessageAttachments {
|
||||||
attachments: any;
|
attachments: any;
|
||||||
timeFormat: string;
|
timeFormat: string;
|
||||||
|
@ -48,12 +50,21 @@ export interface IMessageCallButton {
|
||||||
callJitsi: Function;
|
callJitsi: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
|
||||||
|
|
||||||
export interface IMessageContent {
|
export interface IMessageContent {
|
||||||
isTemp: boolean;
|
isTemp: boolean;
|
||||||
isInfo: boolean;
|
isInfo: boolean;
|
||||||
tmid: string;
|
tmid: string;
|
||||||
isThreadRoom: boolean;
|
isThreadRoom: boolean;
|
||||||
msg: string;
|
msg: string;
|
||||||
|
md: MarkdownAST;
|
||||||
theme: string;
|
theme: string;
|
||||||
isEdited: boolean;
|
isEdited: boolean;
|
||||||
isEncrypted: boolean;
|
isEncrypted: boolean;
|
||||||
|
@ -62,7 +73,7 @@ export interface IMessageContent {
|
||||||
name: string;
|
name: string;
|
||||||
_id: number;
|
_id: number;
|
||||||
}[];
|
}[];
|
||||||
mentions: object[];
|
mentions: UserMention[];
|
||||||
navToRoomInfo: Function;
|
navToRoomInfo: Function;
|
||||||
useRealName: boolean;
|
useRealName: boolean;
|
||||||
isIgnored: boolean;
|
isIgnored: boolean;
|
||||||
|
|
|
@ -106,7 +106,6 @@ export default StyleSheet.create<any>({
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
// maxWidth: 400,
|
|
||||||
minHeight: isTablet ? 300 : 200,
|
minHeight: isTablet ? 300 : 200,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
|
|
@ -779,5 +779,6 @@
|
||||||
"Shortcut": "Shortcut",
|
"Shortcut": "Shortcut",
|
||||||
"Content": "Content",
|
"Content": "Content",
|
||||||
"Sharing": "Sharing",
|
"Sharing": "Sharing",
|
||||||
"No_canned_responses": "No canned responses"
|
"No_canned_responses": "No canned responses",
|
||||||
|
"Enable_Message_Parser": "Enable Message Parser"
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,4 +81,6 @@ export default class Message extends Model {
|
||||||
@field('e2e') e2e;
|
@field('e2e') e2e;
|
||||||
|
|
||||||
@field('tshow') tshow;
|
@field('tshow') tshow;
|
||||||
|
|
||||||
|
@json('md', sanitizer) md;
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,6 +190,15 @@ export default schemaMigrations({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 14,
|
||||||
|
steps: [
|
||||||
|
addColumns({
|
||||||
|
table: 'messages',
|
||||||
|
columns: [{ name: 'md', type: 'string', isOptional: true }]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,4 +25,6 @@ export default class User extends Model {
|
||||||
@field('show_message_in_main_thread') showMessageInMainThread;
|
@field('show_message_in_main_thread') showMessageInMainThread;
|
||||||
|
|
||||||
@field('is_from_webview') isFromWebView;
|
@field('is_from_webview') isFromWebView;
|
||||||
|
|
||||||
|
@field('enable_message_parser_early_adoption') enableMessageParserEarlyAdoption;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,15 @@ export default schemaMigrations({
|
||||||
columns: [{ name: 'is_from_webview', type: 'boolean', isOptional: true }]
|
columns: [{ name: 'is_from_webview', type: 'boolean', isOptional: true }]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 12,
|
||||||
|
steps: [
|
||||||
|
addColumns({
|
||||||
|
table: 'users',
|
||||||
|
columns: [{ name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true }]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 13,
|
version: 14,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'subscriptions',
|
name: 'subscriptions',
|
||||||
|
@ -115,7 +115,8 @@ export default appSchema({
|
||||||
{ name: 'tmsg', type: 'string', isOptional: true },
|
{ name: 'tmsg', type: 'string', isOptional: true },
|
||||||
{ name: 'blocks', type: 'string', isOptional: true },
|
{ name: 'blocks', type: 'string', isOptional: true },
|
||||||
{ name: 'e2e', type: 'string', isOptional: true },
|
{ name: 'e2e', type: 'string', isOptional: true },
|
||||||
{ name: 'tshow', type: 'boolean', isOptional: true }
|
{ name: 'tshow', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'md', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 11,
|
version: 12,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
|
@ -16,7 +16,8 @@ export default appSchema({
|
||||||
{ name: 'login_email_password', type: 'boolean', isOptional: true },
|
{ name: 'login_email_password', type: 'boolean', isOptional: true },
|
||||||
{ name: 'show_message_in_main_thread', type: 'boolean', isOptional: true },
|
{ name: 'show_message_in_main_thread', type: 'boolean', isOptional: true },
|
||||||
{ name: 'avatar_etag', type: 'string', isOptional: true },
|
{ name: 'avatar_etag', type: 'string', isOptional: true },
|
||||||
{ name: 'is_from_webview', type: 'boolean', isOptional: true }
|
{ name: 'is_from_webview', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
|
|
@ -622,7 +622,8 @@ const RocketChat = {
|
||||||
roles: result.me.roles,
|
roles: result.me.roles,
|
||||||
avatarETag: result.me.avatarETag,
|
avatarETag: result.me.avatarETag,
|
||||||
isFromWebView,
|
isFromWebView,
|
||||||
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true
|
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true,
|
||||||
|
enableMessageParserEarlyAdoption: result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true
|
||||||
};
|
};
|
||||||
return user;
|
return user;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,46 +1,75 @@
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Switch } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { events, logEvent } from '../../utils/log';
|
import log, { logEvent, events } from '../../utils/log';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
|
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
|
||||||
class UserPreferencesView extends React.Component {
|
const UserPreferencesView = ({ navigation }) => {
|
||||||
static navigationOptions = () => ({
|
const user = useSelector(state => getUserSelector(state));
|
||||||
title: I18n.t('Preferences')
|
const [enableParser, setEnableParser] = useState(user.enableMessageParserEarlyAdoption);
|
||||||
});
|
|
||||||
|
|
||||||
static propTypes = {
|
useEffect(() => {
|
||||||
navigation: PropTypes.object
|
navigation.setOptions({
|
||||||
};
|
title: I18n.t('Preferences')
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
navigateToScreen = (screen, params) => {
|
const navigateToScreen = (screen, params) => {
|
||||||
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
|
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
|
||||||
const { navigation } = this.props;
|
|
||||||
navigation.navigate(screen, params);
|
navigation.navigate(screen, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
const toggleMessageParser = async value => {
|
||||||
return (
|
try {
|
||||||
<SafeAreaView testID='preferences-view'>
|
await RocketChat.saveUserPreferences({ id: user.id, enableMessageParserEarlyAdoption: value });
|
||||||
<StatusBar />
|
setEnableParser(value);
|
||||||
<List.Container>
|
} catch (e) {
|
||||||
<List.Section>
|
log(e);
|
||||||
<List.Separator />
|
}
|
||||||
<List.Item
|
};
|
||||||
title='Notifications'
|
|
||||||
onPress={() => this.navigateToScreen('UserNotificationPrefView')}
|
const renderMessageParserSwitch = () => (
|
||||||
showActionIndicator
|
<Switch value={enableParser} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleMessageParser} />
|
||||||
testID='preferences-view-notifications'
|
);
|
||||||
/>
|
|
||||||
<List.Separator />
|
return (
|
||||||
</List.Section>
|
<SafeAreaView testID='preferences-view'>
|
||||||
</List.Container>
|
<StatusBar />
|
||||||
</SafeAreaView>
|
<List.Container>
|
||||||
);
|
<List.Section>
|
||||||
}
|
<List.Separator />
|
||||||
}
|
<List.Item
|
||||||
|
title='Notifications'
|
||||||
|
onPress={() => navigateToScreen('UserNotificationPrefView')}
|
||||||
|
showActionIndicator
|
||||||
|
testID='preferences-view-notifications'
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
</List.Section>
|
||||||
|
<List.Section>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item
|
||||||
|
title='Enable_Message_Parser'
|
||||||
|
testID='preferences-view-enable-message-parser'
|
||||||
|
right={() => renderMessageParserSwitch()}
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
</List.Section>
|
||||||
|
</List.Container>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
UserPreferencesView.propTypes = {
|
||||||
|
navigation: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
export default UserPreferencesView;
|
export default UserPreferencesView;
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
"@react-navigation/drawer": "5.12.5",
|
"@react-navigation/drawer": "5.12.5",
|
||||||
"@react-navigation/native": "5.9.4",
|
"@react-navigation/native": "5.9.4",
|
||||||
"@react-navigation/stack": "5.14.5",
|
"@react-navigation/stack": "5.14.5",
|
||||||
|
"@rocket.chat/message-parser": "0.30.0",
|
||||||
"@rocket.chat/react-native-fast-image": "^8.2.0",
|
"@rocket.chat/react-native-fast-image": "^8.2.0",
|
||||||
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
|
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
|
||||||
"@rocket.chat/ui-kit": "0.13.0",
|
"@rocket.chat/ui-kit": "0.13.0",
|
||||||
|
|
|
@ -0,0 +1,627 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
|
||||||
|
import NewMarkdown from '../../app/containers/markdown/new';
|
||||||
|
import { themes } from '../../app/constants/colors';
|
||||||
|
|
||||||
|
const stories = storiesOf('NewMarkdown', module);
|
||||||
|
|
||||||
|
const theme = 'light';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
marginHorizontal: 15,
|
||||||
|
backgroundColor: themes[theme].backgroundColor,
|
||||||
|
marginVertical: 50
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
marginHorizontal: 10,
|
||||||
|
marginVertical: 10
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCustomEmoji = content => {
|
||||||
|
const customEmoji = {
|
||||||
|
marioparty: { name: content, extension: 'gif' },
|
||||||
|
nyan_rocket: { name: content, extension: 'png' }
|
||||||
|
}[content];
|
||||||
|
return customEmoji;
|
||||||
|
};
|
||||||
|
const baseUrl = 'https://open.rocket.chat';
|
||||||
|
|
||||||
|
const simpleTextMsg = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'This is Rocket.Chat'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const longTextMsg = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const lineBreakMsg = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'a'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'b'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'c'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'e'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const sequentialEmptySpacesMsg = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'a b c'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const boldOrUnderscoreMsg = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'BOLD',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'This is bold'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ' and '
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ITALIC',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'this is italic'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Text', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={simpleTextMsg} />
|
||||||
|
<NewMarkdown tokens={longTextMsg} />
|
||||||
|
<NewMarkdown tokens={lineBreakMsg} />
|
||||||
|
<NewMarkdown tokens={sequentialEmptySpacesMsg} />
|
||||||
|
<NewMarkdown tokens={boldOrUnderscoreMsg} />
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
const allMentionTokens = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'MENTION_USER',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'rocket.cat'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const multipleMentionTokens = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'MENTION_USER',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'name'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ' '
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'MENTION_USER',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'rocket.cat'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ' '
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'MENTION_USER',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'here'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ' '
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'MENTION_USER',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'all'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const allMentions = [
|
||||||
|
{
|
||||||
|
_id: 'rocket.cat',
|
||||||
|
username: 'rocket.cat'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const multipleMentions = [
|
||||||
|
{
|
||||||
|
_id: 'name',
|
||||||
|
username: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'rocket.cat',
|
||||||
|
username: 'rocket.cat'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'here',
|
||||||
|
username: 'here'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'all',
|
||||||
|
username: 'all'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Mentions', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={allMentionTokens} mentions={allMentions} navToRoomInfo={() => {}} style={[]} />
|
||||||
|
<NewMarkdown
|
||||||
|
tokens={multipleMentionTokens}
|
||||||
|
mentions={multipleMentions}
|
||||||
|
navToRoomInfo={() => {}}
|
||||||
|
style={[]}
|
||||||
|
username='rocket.cat'
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
const channelTokens = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'MENTION_CHANNEL',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'text_channel'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: ' and '
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'MENTION_CHANNEL',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'not_a_channel'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const channelMention = [
|
||||||
|
{
|
||||||
|
_id: 'text_channel',
|
||||||
|
name: 'text_channel'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Hashtag', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={channelTokens} channels={channelMention} navToRoomInfo={() => {}} />
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
const bigEmojiTokens = [
|
||||||
|
{
|
||||||
|
type: 'BIG_EMOJI',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'EMOJI',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'green_heart'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'EMOJI',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'joy'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'EMOJI',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'grin'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const emojiTokens = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'EMOJI',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'rocket'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'EMOJI',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'facepalm'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'EMOJI',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'nyan_rocket'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'EMOJI',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'marioparty'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Emoji', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={bigEmojiTokens} />
|
||||||
|
<NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
const blockQuoteTokens = [
|
||||||
|
{
|
||||||
|
type: 'QUOTE',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Rocket.Chat to the moon'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Block quote', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={blockQuoteTokens} />
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
const rocketChatLink = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'LINK',
|
||||||
|
value: {
|
||||||
|
src: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'https://rocket.chat'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'https://rocket.chat'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const markdownLink = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'LINK',
|
||||||
|
value: {
|
||||||
|
src: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'https://rocket.chat'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Markdown link'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Links', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={rocketChatLink} />
|
||||||
|
<NewMarkdown tokens={markdownLink} />
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
stories.add('Headers', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown
|
||||||
|
tokens={[
|
||||||
|
{
|
||||||
|
type: 'HEADING',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: '# Header 1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
level: 1
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<NewMarkdown
|
||||||
|
tokens={[
|
||||||
|
{
|
||||||
|
type: 'HEADING',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: '## Header 2'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
level: 2
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<NewMarkdown
|
||||||
|
tokens={[
|
||||||
|
{
|
||||||
|
type: 'HEADING',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: '### Header 3'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
level: 3
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<NewMarkdown
|
||||||
|
tokens={[
|
||||||
|
{
|
||||||
|
type: 'HEADING',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: '#### Header 4'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
level: 4
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<NewMarkdown
|
||||||
|
tokens={[
|
||||||
|
{
|
||||||
|
type: 'HEADING',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: '##### Header 5'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
level: 5
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<NewMarkdown
|
||||||
|
tokens={[
|
||||||
|
{
|
||||||
|
type: 'HEADING',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: '###### Header 6'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
level: 6
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
const inlineCodeToken = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'INLINE_CODE',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'inline code'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const multilineCodeToken = [
|
||||||
|
{
|
||||||
|
type: 'CODE',
|
||||||
|
language: 'none',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'CODE_LINE',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Multi line '
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'CODE_LINE',
|
||||||
|
value: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Code'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Code', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={inlineCodeToken} style={[]} />
|
||||||
|
<NewMarkdown tokens={multilineCodeToken} style={[]} />
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
|
||||||
|
const unorederedListToken = [
|
||||||
|
{
|
||||||
|
type: 'UNORDERED_LIST',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'LIST_ITEM',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Open Source'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'LIST_ITEM',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Rocket.Chat'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const orderedListToken = [
|
||||||
|
{
|
||||||
|
type: 'ORDERED_LIST',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'LIST_ITEM',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Open Source'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'LIST_ITEM',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'Rocket.Chat'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.add('Lists', () => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={unorederedListToken} />
|
||||||
|
<NewMarkdown tokens={orderedListToken} />
|
||||||
|
</View>
|
||||||
|
));
|
|
@ -12,6 +12,7 @@ import './HeaderButtons';
|
||||||
import './UnreadBadge';
|
import './UnreadBadge';
|
||||||
import '../../app/views/ThreadMessagesView/Item.stories.js';
|
import '../../app/views/ThreadMessagesView/Item.stories.js';
|
||||||
import './Avatar';
|
import './Avatar';
|
||||||
|
import './NewMarkdown';
|
||||||
import '../../app/containers/BackgroundContainer/index.stories.js';
|
import '../../app/containers/BackgroundContainer/index.stories.js';
|
||||||
import '../../app/containers/RoomHeader/RoomHeader.stories.js';
|
import '../../app/containers/RoomHeader/RoomHeader.stories.js';
|
||||||
import '../../app/views/RoomView/LoadMore/LoadMore.stories';
|
import '../../app/views/RoomView/LoadMore/LoadMore.stories';
|
||||||
|
|
|
@ -3748,6 +3748,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-plugin-import "^2.17.2"
|
eslint-plugin-import "^2.17.2"
|
||||||
|
|
||||||
|
"@rocket.chat/message-parser@0.30.0":
|
||||||
|
version "0.30.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.30.0.tgz#63a25aa7fa17724d55db80f95f7f8d6a99ae42ff"
|
||||||
|
integrity sha512-pI7ajaojv+GqhQBMnFiBOWerE7zIlJywWFaLzJlIC/wsJ9LgX6YaKY2wqc909nkr+E4qZY1luJ61ErXGGSF9Zw==
|
||||||
|
|
||||||
"@rocket.chat/react-native-fast-image@^8.2.0":
|
"@rocket.chat/react-native-fast-image@^8.2.0":
|
||||||
version "8.2.0"
|
version "8.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51"
|
resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51"
|
||||||
|
|
Loading…
Reference in New Issue