[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 { Text } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { themes } from '../../constants/colors';
|
||||
import styles from './styles';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
|
@ -9,20 +10,20 @@ interface IAtMention {
|
|||
mention: string;
|
||||
username: string;
|
||||
navToRoomInfo: Function;
|
||||
style: any;
|
||||
style?: any;
|
||||
useRealName: boolean;
|
||||
theme: string;
|
||||
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') {
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.mention,
|
||||
{
|
||||
color: themes[theme].mentionGroupColor
|
||||
color: themes[theme!].mentionGroupColor
|
||||
},
|
||||
...style
|
||||
]}>
|
||||
|
@ -34,11 +35,11 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
|
|||
let mentionStyle = {};
|
||||
if (mention === username) {
|
||||
mentionStyle = {
|
||||
color: themes[theme].mentionMeColor
|
||||
color: themes[theme!].mentionMeColor
|
||||
};
|
||||
} else {
|
||||
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;
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Text, TextStyle } from 'react-native';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
import { useTheme } from '../../theme';
|
||||
import styles from './styles';
|
||||
|
||||
interface IHashtag {
|
||||
hashtag: string;
|
||||
navToRoomInfo: Function;
|
||||
style: [];
|
||||
theme: string;
|
||||
style?: TextStyle[];
|
||||
channels: {
|
||||
[index: number]: string | number;
|
||||
name: string;
|
||||
_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 index = channels.findIndex(channel => channel.name === hashtag);
|
||||
const index = channels?.findIndex(channel => channel.name === hashtag);
|
||||
const navParam = {
|
||||
t: 'c',
|
||||
rid: channels[index]._id
|
||||
|
@ -31,7 +34,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them
|
|||
style={[
|
||||
styles.mention,
|
||||
{
|
||||
color: themes[theme].mentionOtherColor
|
||||
color: themes[theme!].mentionOtherColor
|
||||
},
|
||||
...style
|
||||
]}
|
||||
|
@ -40,7 +43,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them
|
|||
</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;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Image, Text } from 'react-native';
|
|||
import { Node, Parser } from 'commonmark';
|
||||
import Renderer from 'commonmark-react-renderer';
|
||||
import removeMarkdown from 'remove-markdown';
|
||||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -20,9 +21,20 @@ import MarkdownTableCell from './TableCell';
|
|||
import mergeTextNodes from './mergeTextNodes';
|
||||
import styles from './styles';
|
||||
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 {
|
||||
msg: string;
|
||||
md: MarkdownAST;
|
||||
mentions: UserMention[];
|
||||
getCustomEmoji: Function;
|
||||
baseUrl: string;
|
||||
username: string;
|
||||
|
@ -35,7 +47,7 @@ interface IMarkdownProps {
|
|||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
mentions: object[];
|
||||
enableMessageParser: boolean;
|
||||
navToRoomInfo: Function;
|
||||
preview: boolean;
|
||||
theme: string;
|
||||
|
@ -97,7 +109,9 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
|
||||
constructor(props: IMarkdownProps) {
|
||||
super(props);
|
||||
this.renderer = this.createRenderer();
|
||||
if (!this.isNewMarkdown) {
|
||||
this.renderer = this.createRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
createRenderer = () =>
|
||||
|
@ -139,6 +153,11 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
renderParagraphsInLists: true
|
||||
});
|
||||
|
||||
get isNewMarkdown(): boolean {
|
||||
const { md, enableMessageParser } = this.props;
|
||||
return enableMessageParser && !!md;
|
||||
}
|
||||
|
||||
editedMessage = (ast: any) => {
|
||||
const { isEdited } = this.props;
|
||||
if (isEdited) {
|
||||
|
@ -227,12 +246,12 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
};
|
||||
|
||||
renderHashtag = ({ hashtag }: { hashtag: string }) => {
|
||||
const { channels, navToRoomInfo, style, theme } = this.props;
|
||||
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} theme={theme} style={style} />;
|
||||
const { channels, navToRoomInfo, style } = this.props;
|
||||
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} style={style} />;
|
||||
};
|
||||
|
||||
renderAtMention = ({ mentionName }: { mentionName: string }) => {
|
||||
const { username, mentions, navToRoomInfo, useRealName, style, theme } = this.props;
|
||||
const { username, mentions, navToRoomInfo, useRealName, style } = this.props;
|
||||
return (
|
||||
<MarkdownAtMention
|
||||
mentions={mentions}
|
||||
|
@ -240,7 +259,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
useRealName={useRealName}
|
||||
username={username}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
theme={theme}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
|
@ -329,12 +347,44 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
};
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
// 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: {
|
||||
textDecorationLine: 'line-through'
|
||||
},
|
||||
plainText: {
|
||||
fontSize: 16,
|
||||
flexShrink: 1
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
|
@ -70,12 +74,16 @@ export default StyleSheet.create<any>({
|
|||
resizeMode: 'contain'
|
||||
},
|
||||
codeInline: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular,
|
||||
...codeFontFamily,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
borderRadius: 4,
|
||||
paddingLeft: 2,
|
||||
paddingTop: 2
|
||||
},
|
||||
codeBlock: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular,
|
||||
...codeFontFamily,
|
||||
borderWidth: 1,
|
||||
|
|
|
@ -51,8 +51,10 @@ const Content = React.memo(
|
|||
// @ts-ignore
|
||||
<Markdown
|
||||
msg={props.msg}
|
||||
md={props.md}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={props.getCustomEmoji}
|
||||
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||
username={user.username}
|
||||
isEdited={props.isEdited}
|
||||
numberOfLines={isPreview ? 1 : 0}
|
||||
|
@ -103,6 +105,9 @@ const Content = React.memo(
|
|||
if (prevProps.isIgnored !== nextProps.isIgnored) {
|
||||
return false;
|
||||
}
|
||||
if (!dequal(prevProps.md, nextProps.md)) {
|
||||
return false;
|
||||
}
|
||||
if (!dequal(prevProps.mentions, nextProps.mentions)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -357,7 +357,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
|
|||
unread,
|
||||
blocks,
|
||||
autoTranslate: autoTranslateMessage,
|
||||
replies
|
||||
replies,
|
||||
md
|
||||
} = item;
|
||||
|
||||
let message = msg;
|
||||
|
@ -391,6 +392,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
|
|||
<Message
|
||||
id={id}
|
||||
msg={message}
|
||||
md={md}
|
||||
rid={rid}
|
||||
author={u}
|
||||
ts={ts}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
export interface IMessageAttachments {
|
||||
attachments: any;
|
||||
timeFormat: string;
|
||||
|
@ -48,12 +50,21 @@ export interface IMessageCallButton {
|
|||
callJitsi: Function;
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
_id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
|
||||
export interface IMessageContent {
|
||||
isTemp: boolean;
|
||||
isInfo: boolean;
|
||||
tmid: string;
|
||||
isThreadRoom: boolean;
|
||||
msg: string;
|
||||
md: MarkdownAST;
|
||||
theme: string;
|
||||
isEdited: boolean;
|
||||
isEncrypted: boolean;
|
||||
|
@ -62,7 +73,7 @@ export interface IMessageContent {
|
|||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
mentions: object[];
|
||||
mentions: UserMention[];
|
||||
navToRoomInfo: Function;
|
||||
useRealName: boolean;
|
||||
isIgnored: boolean;
|
||||
|
|
|
@ -106,7 +106,6 @@ export default StyleSheet.create<any>({
|
|||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
// maxWidth: 400,
|
||||
minHeight: isTablet ? 300 : 200,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
|
|
|
@ -779,5 +779,6 @@
|
|||
"Shortcut": "Shortcut",
|
||||
"Content": "Content",
|
||||
"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('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('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 }]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
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';
|
||||
|
||||
export default appSchema({
|
||||
version: 13,
|
||||
version: 14,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'subscriptions',
|
||||
|
@ -115,7 +115,8 @@ export default appSchema({
|
|||
{ name: 'tmsg', type: 'string', isOptional: true },
|
||||
{ name: 'blocks', 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({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 11,
|
||||
version: 12,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'users',
|
||||
|
@ -16,7 +16,8 @@ export default appSchema({
|
|||
{ name: 'login_email_password', type: 'boolean', isOptional: true },
|
||||
{ name: 'show_message_in_main_thread', type: 'boolean', 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({
|
||||
|
|
|
@ -622,7 +622,8 @@ const RocketChat = {
|
|||
roles: result.me.roles,
|
||||
avatarETag: result.me.avatarETag,
|
||||
isFromWebView,
|
||||
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true
|
||||
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true,
|
||||
enableMessageParserEarlyAdoption: result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true
|
||||
};
|
||||
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 { useSelector } from 'react-redux';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
import log, { logEvent, events } from '../../utils/log';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
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 {
|
||||
static navigationOptions = () => ({
|
||||
title: I18n.t('Preferences')
|
||||
});
|
||||
const UserPreferencesView = ({ navigation }) => {
|
||||
const user = useSelector(state => getUserSelector(state));
|
||||
const [enableParser, setEnableParser] = useState(user.enableMessageParserEarlyAdoption);
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object
|
||||
};
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Preferences')
|
||||
});
|
||||
}, []);
|
||||
|
||||
navigateToScreen = (screen, params) => {
|
||||
const navigateToScreen = (screen, params) => {
|
||||
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate(screen, params);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView testID='preferences-view'>
|
||||
<StatusBar />
|
||||
<List.Container>
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Notifications'
|
||||
onPress={() => this.navigateToScreen('UserNotificationPrefView')}
|
||||
showActionIndicator
|
||||
testID='preferences-view-notifications'
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Section>
|
||||
</List.Container>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
const toggleMessageParser = async value => {
|
||||
try {
|
||||
await RocketChat.saveUserPreferences({ id: user.id, enableMessageParserEarlyAdoption: value });
|
||||
setEnableParser(value);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const renderMessageParserSwitch = () => (
|
||||
<Switch value={enableParser} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleMessageParser} />
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='preferences-view'>
|
||||
<StatusBar />
|
||||
<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;
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
"@react-navigation/drawer": "5.12.5",
|
||||
"@react-navigation/native": "5.9.4",
|
||||
"@react-navigation/stack": "5.14.5",
|
||||
"@rocket.chat/message-parser": "0.30.0",
|
||||
"@rocket.chat/react-native-fast-image": "^8.2.0",
|
||||
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
|
||||
"@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 '../../app/views/ThreadMessagesView/Item.stories.js';
|
||||
import './Avatar';
|
||||
import './NewMarkdown';
|
||||
import '../../app/containers/BackgroundContainer/index.stories.js';
|
||||
import '../../app/containers/RoomHeader/RoomHeader.stories.js';
|
||||
import '../../app/views/RoomView/LoadMore/LoadMore.stories';
|
||||
|
|
|
@ -3748,6 +3748,11 @@
|
|||
dependencies:
|
||||
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":
|
||||
version "8.2.0"
|
||||
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