Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/containers/UIKit/MessageBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ export const messageBlockWithContext = (context: any) => (props: any) =>
</KitContext.Provider>
);

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, index) => <React.Fragment key={blocks[index]?.blockId || index}>{element}</React.Fragment>)
: renderedBlocks;
};

export const modalBlockWithContext = (context: any) => (data: any) =>
(
Expand Down
25 changes: 15 additions & 10 deletions app/containers/markdown/components/Inline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useContext } from 'react';
import { Text } from 'react-native';
import { type Paragraph as ParagraphProps } from '@rocket.chat/message-parser';

import getBlockValueString from '../../../lib/methods/getBlockValueString';
import styles from '../styles';
import { AtMention, Hashtag } from './mentions';
import { Emoji } from './emoji';
Expand Down Expand Up @@ -38,22 +39,26 @@ const Inline = ({ value, forceTrim }: IParagraphProps): React.ReactElement | nul
}
}

// key example: IMAGE-https:rocket.chat/assets/images/123img.png...-3 <upto 50 chars only>
const key = `${block.type}-${getBlockValueString(block.value)}-${index}`;

switch (block.type) {
case 'IMAGE':
return <Image value={block.value} />;
return <Image key={key} value={block.value} />;
case 'PLAIN_TEXT':
return <Plain value={block.value} />;
return <Plain key={key} value={block.value} />;
case 'BOLD':
return <Bold value={block.value} />;
return <Bold key={key} value={block.value} />;
case 'STRIKE':
return <Strike value={block.value} />;
return <Strike key={key} value={block.value} />;
case 'ITALIC':
return <Italic value={block.value} />;
return <Italic key={key} value={block.value} />;
case 'LINK':
return <Link value={block.value} />;
return <Link key={key} value={block.value} />;
case 'MENTION_USER':
return (
<AtMention
key={key}
mention={block.value.value}
useRealName={useRealName}
username={username}
Expand All @@ -62,14 +67,14 @@ const Inline = ({ value, forceTrim }: IParagraphProps): React.ReactElement | nul
/>
);
case 'EMOJI':
return <Emoji block={block} index={index} />;
return <Emoji key={key} block={block} index={index} />;
case 'MENTION_CHANNEL':
return <Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />;
return <Hashtag key={key} hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />;
case 'INLINE_CODE':
return <InlineCode value={block.value} />;
return <InlineCode key={key} value={block.value} />;
case 'INLINE_KATEX':
// return <InlineKaTeX value={block.value} />;
return <Text>{block.value}</Text>;
return <Text key={key}>{block.value}</Text>;
default:
return null;
}
Expand Down
4 changes: 2 additions & 2 deletions app/containers/markdown/components/emoji/BigEmoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const styles = StyleSheet.create({

const BigEmoji = ({ value }: IBigEmojiProps) => (
<View style={styles.container}>
{value.map(block => (
<Emoji block={block} isBigEmoji />
{value.map((block, index) => (
<Emoji key={('shortCode' in block ? block.shortCode : block.unicode) ?? index} block={block} isBigEmoji />
))}
</View>
);
Expand Down
16 changes: 9 additions & 7 deletions app/containers/markdown/components/inline/Bold.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { type Bold as BoldProps } from '@rocket.chat/message-parser';

import getBlockValueString from '../../../../lib/methods/getBlockValueString';
import { Italic, Link, Strike } from './index';
import Plain from '../Plain';
import sharedStyles from '../../../../views/Styles';
Expand All @@ -18,23 +19,24 @@ const styles = StyleSheet.create({

const Bold = ({ value }: IBoldProps) => (
<Text style={styles.text}>
{value.map(block => {
{value.map((block, index) => {
// key example: LINK-https:rocket.chat/link/123456789...-3 <upto 50 chars only>
const key = `${block.type}-${getBlockValueString(block.value)}-${index}`;
switch (block.type) {
case 'LINK':
return <Link value={block.value} />;
return <Link key={key} value={block.value} />;
case 'PLAIN_TEXT':
return <Plain value={block.value} />;
return <Plain key={key} value={block.value} />;
case 'STRIKE':
return <Strike value={block.value} />;
return <Strike key={key} value={block.value} />;
case 'ITALIC':
return <Italic value={block.value} />;
return <Italic key={key} value={block.value} />;
case 'MENTION_CHANNEL':
return <Plain value={`#${block.value.value}`} />;
return <Plain key={key} value={`#${block.value.value}`} />;
default:
return null;
}
})}
</Text>
);

export default Bold;
19 changes: 11 additions & 8 deletions app/containers/markdown/components/list/UnorderedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ const UnorderedList = ({ value }: IUnorderedListProps) => {
const { theme } = useTheme();
return (
<View>
{value.map(item => (
<View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].fontDefault }]}>{'\u2022 '}</Text>
<Text style={[styles.inline, { color: themes[theme].fontDefault }]}>
<Inline value={item.value} />
</Text>
</View>
))}
{value.map((item, index) => {
const key = `${item.value?.toString?.().slice(0, 20) || index}-${index}`;
return (
<View key={key} style={styles.row}>
<Text style={[styles.text, { color: themes[theme].fontDefault }]}>{'\u2022 '}</Text>
<Text style={[styles.inline, { color: themes[theme].fontDefault }]}>
<Inline value={item.value} />
</Text>
</View>
);
})}
</View>
);
};
Expand Down
26 changes: 15 additions & 11 deletions app/containers/markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parse } from '@rocket.chat/message-parser';
import type { Root } from '@rocket.chat/message-parser';
import isEmpty from 'lodash/isEmpty';

import getBlockValueString from '../../lib/methods/getBlockValueString';
import { type IUserMention, type IUserChannel, type TOnLinkPress } from './interfaces';
import { type TGetCustomEmoji } from '../../definitions/IEmoji';
import MarkdownContext from './contexts/MarkdownContext';
Expand Down Expand Up @@ -69,32 +70,35 @@ const Markdown: React.FC<IMarkdownProps> = ({
getCustomEmoji,
onLinkPress
}}>
{tokens?.map(block => {
{tokens?.map((block, index) => {
// sliced to avoid very long keys
// key example: PARAGRAPH-this is value upto 50 chars -3
const key = `${block.type}-${getBlockValueString(block.value)}-${index}`;
switch (block.type) {
case 'BIG_EMOJI':
return <BigEmoji value={block.value} />;
return <BigEmoji key={key} value={block.value} />;
case 'UNORDERED_LIST':
return <UnorderedList value={block.value} />;
return <UnorderedList key={key} value={block.value} />;
case 'ORDERED_LIST':
return <OrderedList value={block.value} />;
return <OrderedList key={key} value={block.value} />;
case 'TASKS':
return <TaskList value={block.value} />;
return <TaskList key={key} value={block.value} />;
case 'QUOTE':
return <Quote value={block.value} />;
return <Quote key={key} value={block.value} />;
case 'PARAGRAPH':
return <Paragraph value={block.value} />;
return <Paragraph key={key} value={block.value} />;
case 'CODE':
return <Code value={block.value} />;
return <Code key={key} value={block.value} />;
case 'HEADING':
return <Heading value={block.value} level={block.level} />;
return <Heading key={key} value={block.value} level={block.level} />;
case 'LINE_BREAK':
return <LineBreak />;
return <LineBreak key={key} />;
// 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 <View />;
case 'KATEX':
return <KaTeX value={block.value} />;
return <KaTeX key={key} value={block.value} />;
default:
return null;
}
Expand Down
19 changes: 19 additions & 0 deletions app/lib/methods/getBlockValueString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Converts a block value to a stable string for use in React keys.
* Handles strings, objects with .value property, arrays, and other types.
* @param v - The value to convert
* @returns A string representation suitable for key generation
*/

const getBlockValueString = (v: any): string => {
if (v === null || v === undefined) return 'null';
if (v === '') return 'empty';
if (v === 0 || v === false) return String(v);

if (typeof v === 'string') return v;
if (typeof v?.value === 'string') return v.value;
if (Array.isArray(v)) return v.map(getBlockValueString).join('-');

return JSON.stringify(v).slice(0, 50);
};
export default getBlockValueString;