Skip to content

Commit f3176a3

Browse files
feat(Message): Allow further customization of Markdown behavior (#636)
Allow turning off markdown parsing (going to text-only) or passing additional props to parser react-markdown, such as disallowedElements.
1 parent 3a52a7e commit f3176a3

File tree

2 files changed

+81
-46
lines changed

2 files changed

+81
-46
lines changed

packages/module/src/Message/Message.test.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,4 +962,23 @@ describe('Message', () => {
962962
const form = container.querySelector('form');
963963
expect(form).toHaveClass('test');
964964
});
965+
it('should be able to disable markdown parsing', () => {
966+
render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} isMarkdownDisabled />);
967+
// this is looking for markdown syntax that is ordinarily stripped
968+
expect(screen.getByText(/~~~yaml/i)).toBeTruthy();
969+
});
970+
it('should be able to pass props to react-markdown, such as disabling tags', () => {
971+
render(
972+
<Message
973+
avatar="./img"
974+
role="user"
975+
name="User"
976+
content={CODE_MESSAGE}
977+
reactMarkdownProps={{ disallowedElements: ['code'] }}
978+
/>
979+
);
980+
expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
981+
// code block isn't rendering
982+
expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
983+
});
965984
});

packages/module/src/Message/Message.tsx

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// ============================================================================
44
import { forwardRef, ReactNode, useEffect, useState } from 'react';
55
import type { FunctionComponent, HTMLProps, MouseEvent as ReactMouseEvent, Ref } from 'react';
6-
import Markdown from 'react-markdown';
6+
import Markdown, { Options } from 'react-markdown';
77
import remarkGfm from 'remark-gfm';
88
import {
99
AlertProps,
@@ -185,6 +185,10 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
185185
editFormProps?: FormProps;
186186
/** Sets message to compact styling. */
187187
isCompact?: boolean;
188+
/** Disables markdown parsing for message, allowing only text input */
189+
isMarkdownDisabled?: boolean;
190+
/** Allows passing additional props down to markdown parser react-markdown, such as allowedElements and disallowedElements. See https://github.com/remarkjs/react-markdown?tab=readme-ov-file#options for options */
191+
reactMarkdownProps?: Options;
188192
}
189193

190194
export const MessageBase: FunctionComponent<MessageProps> = ({
@@ -224,6 +228,8 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
224228
inputRef,
225229
editFormProps,
226230
isCompact,
231+
isMarkdownDisabled,
232+
reactMarkdownProps,
227233
...props
228234
}: MessageProps) => {
229235
const [messageText, setMessageText] = useState(content);
@@ -250,6 +256,60 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
250256
const date = new Date();
251257
const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
252258

259+
const handleMarkdown = () => {
260+
if (isMarkdownDisabled) {
261+
return (
262+
<TextMessage component={ContentVariants.p} {...props}>
263+
{messageText}
264+
</TextMessage>
265+
);
266+
}
267+
return (
268+
<Markdown
269+
components={{
270+
p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
271+
code: ({ children, ...props }) => (
272+
<CodeBlockMessage {...props} {...codeBlockProps}>
273+
{children}
274+
</CodeBlockMessage>
275+
),
276+
h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
277+
h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
278+
h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
279+
h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
280+
h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
281+
h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
282+
blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
283+
ul: (props) => <UnorderedListMessage {...props} />,
284+
ol: (props) => <OrderedListMessage {...props} />,
285+
li: (props) => <ListItemMessage {...props} />,
286+
table: (props) => <TableMessage {...props} {...tableProps} />,
287+
tbody: (props) => <TbodyMessage {...props} />,
288+
thead: (props) => <TheadMessage {...props} />,
289+
tr: (props) => <TrMessage {...props} />,
290+
td: (props) => {
291+
// Conflicts with Td type
292+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
293+
const { width, ...rest } = props;
294+
return <TdMessage {...rest} />;
295+
},
296+
th: (props) => <ThMessage {...props} />,
297+
img: (props) => <ImageMessage {...props} />,
298+
a: (props) => (
299+
<LinkMessage href={props.href} rel={props.rel} target={props.target} {...linkProps}>
300+
{props.children}
301+
</LinkMessage>
302+
)
303+
}}
304+
remarkPlugins={[remarkGfm]}
305+
rehypePlugins={rehypePlugins}
306+
{...reactMarkdownProps}
307+
>
308+
{messageText}
309+
</Markdown>
310+
);
311+
};
312+
253313
const renderMessage = () => {
254314
if (isLoading) {
255315
return <MessageLoading loadingWord={loadingWord} />;
@@ -277,51 +337,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
277337
return (
278338
<>
279339
{beforeMainContent && <>{beforeMainContent}</>}
280-
{error ? (
281-
<ErrorMessage {...error} />
282-
) : (
283-
<Markdown
284-
components={{
285-
p: (props) => <TextMessage component={ContentVariants.p} {...props} />,
286-
code: ({ children, ...props }) => (
287-
<CodeBlockMessage {...props} {...codeBlockProps}>
288-
{children}
289-
</CodeBlockMessage>
290-
),
291-
h1: (props) => <TextMessage component={ContentVariants.h1} {...props} />,
292-
h2: (props) => <TextMessage component={ContentVariants.h2} {...props} />,
293-
h3: (props) => <TextMessage component={ContentVariants.h3} {...props} />,
294-
h4: (props) => <TextMessage component={ContentVariants.h4} {...props} />,
295-
h5: (props) => <TextMessage component={ContentVariants.h5} {...props} />,
296-
h6: (props) => <TextMessage component={ContentVariants.h6} {...props} />,
297-
blockquote: (props) => <TextMessage component={ContentVariants.blockquote} {...props} />,
298-
ul: (props) => <UnorderedListMessage {...props} />,
299-
ol: (props) => <OrderedListMessage {...props} />,
300-
li: (props) => <ListItemMessage {...props} />,
301-
table: (props) => <TableMessage {...props} {...tableProps} />,
302-
tbody: (props) => <TbodyMessage {...props} />,
303-
thead: (props) => <TheadMessage {...props} />,
304-
tr: (props) => <TrMessage {...props} />,
305-
td: (props) => {
306-
// Conflicts with Td type
307-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
308-
const { width, ...rest } = props;
309-
return <TdMessage {...rest} />;
310-
},
311-
th: (props) => <ThMessage {...props} />,
312-
img: (props) => <ImageMessage {...props} />,
313-
a: (props) => (
314-
<LinkMessage href={props.href} rel={props.rel} target={props.target} {...linkProps}>
315-
{props.children}
316-
</LinkMessage>
317-
)
318-
}}
319-
remarkPlugins={[remarkGfm]}
320-
rehypePlugins={rehypePlugins}
321-
>
322-
{messageText}
323-
</Markdown>
324-
)}
340+
{error ? <ErrorMessage {...error} /> : handleMarkdown()}
325341
</>
326342
);
327343
};

0 commit comments

Comments
 (0)