diff --git a/app/containers/UIKit/MessageBlock.tsx b/app/containers/UIKit/MessageBlock.tsx index 3039d580539..6ca408e8015 100644 --- a/app/containers/UIKit/MessageBlock.tsx +++ b/app/containers/UIKit/MessageBlock.tsx @@ -10,7 +10,13 @@ export const messageBlockWithContext = (context: any) => (props: any) => ); -const MessageBlock = ({ blocks }: any) => UiKitMessage(blocks); +const MessageBlock = ({ blocks }: any) => { + if (!blocks) return null; + const renderedBlocks = UiKitMessage(blocks); + return Array.isArray(renderedBlocks) + ? renderedBlocks.map(element => {element}) + : renderedBlocks; +}; export const ModalBlockWithContext = (props: any) => ( diff --git a/app/containers/markdown/components/Inline.tsx b/app/containers/markdown/components/Inline.tsx index 4811bcabdcc..c6f36100994 100644 --- a/app/containers/markdown/components/Inline.tsx +++ b/app/containers/markdown/components/Inline.tsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import { Text } from 'react-native'; -import { type Paragraph as ParagraphProps } from '@rocket.chat/message-parser'; +import { type Inlines as InlinesType, type Paragraph as ParagraphProps } from '@rocket.chat/message-parser'; import styles from '../styles'; import { AtMention, Hashtag } from './mentions'; @@ -18,11 +18,14 @@ interface IParagraphProps { forceTrim?: boolean; } +type TInlineWithID = InlinesType & { _id: string }; + const Inline = ({ value, forceTrim }: IParagraphProps): React.ReactElement | null => { const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext); return ( - {value.map((block, index) => { + {value.map((b, index) => { + const block = b as TInlineWithID; // We are forcing trim when is a `[ ](https://https://open.rocket.chat/) plain_text` // to clean the empty spaces if (forceTrim) { @@ -41,20 +44,21 @@ const Inline = ({ value, forceTrim }: IParagraphProps): React.ReactElement | nul switch (block.type) { case 'IMAGE': - return ; + return ; case 'PLAIN_TEXT': - return ; + return ; case 'BOLD': - return ; + return ; case 'STRIKE': - return ; + return ; case 'ITALIC': - return ; + return ; case 'LINK': - return ; + return ; case 'MENTION_USER': return ( ); case 'EMOJI': - return ; + return ; case 'MENTION_CHANNEL': - return ; + return ; case 'INLINE_CODE': - return ; + return ; case 'INLINE_KATEX': // return ; - return {block.value}; + return {block.value}; case 'TIMESTAMP': - return ; + return ; default: return null; } diff --git a/app/containers/markdown/components/Quote.tsx b/app/containers/markdown/components/Quote.tsx index e8efbb86957..642dc65ae0e 100644 --- a/app/containers/markdown/components/Quote.tsx +++ b/app/containers/markdown/components/Quote.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View } from 'react-native'; -import { type Quote as QuoteProps } from '@rocket.chat/message-parser'; +import { type Paragraph as ParagraphType, type Quote as QuoteProps } from '@rocket.chat/message-parser'; import { themes } from '../../../lib/constants/colors'; import { useTheme } from '../../../theme'; @@ -11,15 +11,18 @@ interface IQuoteProps { value: QuoteProps['value']; } +type TParagraphWithID = ParagraphType & { _id: string }; + const Quote = ({ value }: IQuoteProps) => { const { theme } = useTheme(); return ( - {value.map(item => ( - - ))} + {value.map(i => { + const item = i as TParagraphWithID; + return ; + })} ); diff --git a/app/containers/markdown/components/code/Code.tsx b/app/containers/markdown/components/code/Code.tsx index aaf524f067d..4b000967e44 100644 --- a/app/containers/markdown/components/code/Code.tsx +++ b/app/containers/markdown/components/code/Code.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View } from 'react-native'; -import { type Code as CodeProps } from '@rocket.chat/message-parser'; +import { type CodeLine as CodeLineType, type Code as CodeProps } from '@rocket.chat/message-parser'; import styles from '../../styles'; import { useTheme } from '../../../../theme'; @@ -10,6 +10,8 @@ interface ICodeProps { value: CodeProps['value']; } +type TCodeLineWithID = CodeLineType & { _id: string }; + const Code = ({ value }: ICodeProps): React.ReactElement => { const { colors } = useTheme(); @@ -22,10 +24,11 @@ const Code = ({ value }: ICodeProps): React.ReactElement => { borderColor: colors.strokeLight } ]}> - {value.map(block => { + {value.map(b => { + const block = b as TCodeLineWithID; switch (block.type) { case 'CODE_LINE': - return ; + return ; default: return null; } diff --git a/app/containers/markdown/components/emoji/BigEmoji.tsx b/app/containers/markdown/components/emoji/BigEmoji.tsx index 817e8b92346..f97cfd05c61 100644 --- a/app/containers/markdown/components/emoji/BigEmoji.tsx +++ b/app/containers/markdown/components/emoji/BigEmoji.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { type BigEmoji as BigEmojiProps } from '@rocket.chat/message-parser'; +import { type Emoji as EmojiType, type BigEmoji as BigEmojiProps } from '@rocket.chat/message-parser'; import Emoji from './Emoji'; @@ -8,6 +8,8 @@ interface IBigEmojiProps { value: BigEmojiProps['value']; } +type TEmojiWithId = EmojiType & { _id: string }; + const styles = StyleSheet.create({ container: { flexDirection: 'row' @@ -16,9 +18,10 @@ const styles = StyleSheet.create({ const BigEmoji = ({ value }: IBigEmojiProps) => ( - {value.map(block => ( - - ))} + {value.map(b => { + const block = b as TEmojiWithId; + return ; + })} ); diff --git a/app/containers/markdown/components/inline/Bold.tsx b/app/containers/markdown/components/inline/Bold.tsx index 543296290c4..9dbbf305914 100644 --- a/app/containers/markdown/components/inline/Bold.tsx +++ b/app/containers/markdown/components/inline/Bold.tsx @@ -16,25 +16,27 @@ const styles = StyleSheet.create({ } }); +type TBoldWithID = T & { _id: string }; + const Bold = ({ value }: IBoldProps) => ( - {value.map(block => { + {value.map(b => { + const block = b as TBoldWithID; switch (block.type) { case 'LINK': - return ; + return ; case 'PLAIN_TEXT': - return ; + return ; case 'STRIKE': - return ; + return ; case 'ITALIC': - return ; + return ; case 'MENTION_CHANNEL': - return ; + return ; default: return null; } })} ); - export default Bold; diff --git a/app/containers/markdown/components/inline/Italic.tsx b/app/containers/markdown/components/inline/Italic.tsx index 0817465c7dd..d71fdcad9e5 100644 --- a/app/containers/markdown/components/inline/Italic.tsx +++ b/app/containers/markdown/components/inline/Italic.tsx @@ -15,20 +15,23 @@ const styles = StyleSheet.create({ } }); +type TItalicWithID = T & { _id: string }; + const Italic = ({ value }: IItalicProps) => ( - {value.map(block => { + {value.map(b => { + const block = b as TItalicWithID; switch (block.type) { case 'LINK': - return ; + return ; case 'PLAIN_TEXT': - return ; + return ; case 'STRIKE': - return ; + return ; case 'BOLD': - return ; + return ; case 'MENTION_CHANNEL': - return ; + return ; default: return null; } diff --git a/app/containers/markdown/components/inline/Strike.tsx b/app/containers/markdown/components/inline/Strike.tsx index 552b2fd3a72..e2ea2cf94fd 100644 --- a/app/containers/markdown/components/inline/Strike.tsx +++ b/app/containers/markdown/components/inline/Strike.tsx @@ -15,20 +15,23 @@ const styles = StyleSheet.create({ } }); +type TStrikeWithID = T & { _id: string }; + const Strike = ({ value }: IStrikeProps) => ( - {value.map(block => { + {value.map(b => { + const block = b as TStrikeWithID; switch (block.type) { case 'LINK': - return ; + return ; case 'PLAIN_TEXT': - return ; + return ; case 'BOLD': - return ; + return ; case 'ITALIC': - return ; + return ; case 'MENTION_CHANNEL': - return ; + return ; default: return null; } diff --git a/app/containers/markdown/components/list/TaskList.tsx b/app/containers/markdown/components/list/TaskList.tsx index 059d02cb2d6..8b6e5805801 100644 --- a/app/containers/markdown/components/list/TaskList.tsx +++ b/app/containers/markdown/components/list/TaskList.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { type Tasks as TasksProps } from '@rocket.chat/message-parser'; +import { type Task as TaskType, type Tasks as TasksProps } from '@rocket.chat/message-parser'; import Inline from '../Inline'; import styles from '../../styles'; @@ -11,24 +11,29 @@ interface ITasksProps { value: TasksProps['value']; } +type TTaskWithID = TaskType & { _id: string }; + const TaskList = ({ value = [] }: ITasksProps) => { const { colors } = useTheme(); return ( - {value.map(item => ( - - - - - - - - - ))} + {value.map(i => { + const item = i as TTaskWithID; + return ( + + + + + + + + + ); + })} ); }; diff --git a/app/containers/markdown/components/list/UnorderedList.tsx b/app/containers/markdown/components/list/UnorderedList.tsx index 71e11f351f7..d7ca10114f5 100644 --- a/app/containers/markdown/components/list/UnorderedList.tsx +++ b/app/containers/markdown/components/list/UnorderedList.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { type UnorderedList as UnorderedListProps } from '@rocket.chat/message-parser'; +import { type ListItem, type UnorderedList as UnorderedListProps } from '@rocket.chat/message-parser'; import { View, Text } from 'react-native'; import Inline from '../Inline'; @@ -11,18 +11,23 @@ interface IUnorderedListProps { value: UnorderedListProps['value']; } +type TListItemWithID = ListItem & { _id: string }; + const UnorderedList = ({ value }: IUnorderedListProps) => { const { theme } = useTheme(); return ( - {value.map(item => ( - - {'\u2022 '} - - - - - ))} + {value.map(i => { + const item = i as TListItemWithID; + return ( + + {'\u2022 '} + + + + + ); + })} ); }; diff --git a/app/containers/markdown/index.tsx b/app/containers/markdown/index.tsx index c641b3f4cf8..9aca7bcc049 100644 --- a/app/containers/markdown/index.tsx +++ b/app/containers/markdown/index.tsx @@ -35,6 +35,28 @@ interface IMarkdownProps { textStyle?: StyleProp; } +const generateId = (size = 16) => { + const rand = Math.random().toString(36).slice(2); + const time = Date.now().toString(36); + return (rand + time).slice(-size); // use from end +}; + +const assignIds = (block: any) => { + if (!block._id) { + const id = generateId(); + block._id = id; + } + + // add to nested + if (Array.isArray(block.value)) { + block.value = block.value.map((child: any) => assignIds(child)); + } + + if (Array.isArray(block.blocks)) { + block.blocks = block.blocks.map((child: any) => assignIds(child)); + } + return block; +}; const Markdown: React.FC = ({ msg, md, @@ -58,6 +80,8 @@ const Markdown: React.FC = ({ return null; } + tokens = tokens.map(assignIds); + if (isEmpty(tokens)) return null; return ( @@ -75,29 +99,29 @@ const Markdown: React.FC = ({ {tokens?.map(block => { switch (block.type) { case 'BIG_EMOJI': - return ; + return ; case 'UNORDERED_LIST': - return ; + return ; case 'ORDERED_LIST': - return ; + return ; case 'TASKS': - return ; + return ; case 'QUOTE': - return ; + return ; case 'PARAGRAPH': - return ; + return ; case 'CODE': - return ; + return ; case 'HEADING': - return ; + return ; case 'LINE_BREAK': - return ; + return ; // This prop exists, but not even on the web it is treated, so... // https://github.com/RocketChat/Rocket.Chat/blob/develop/packages/gazzodown/src/Markup.tsx // case 'LIST_ITEM': // return ; case 'KATEX': - return ; + return ; default: return null; }