Skip to content

Commit bfe3e93

Browse files
authored
feat: add renderMarkdown toggle to MessageBubble (#101)
Adds a toggle for whether user message text should be rendered as markdown or as plain text. Previously always rendered as Markdown. Rendering as plain text is useful when rendering logs of old conversations. J=APV-939 TEST=auto,manual Added unit tests. Modified ChatPanel locally to use renderMarkdown=false, spun up test-site, and confirmed a Markdown link was rendered as plain text.
1 parent 8d2fab2 commit bfe3e93

7 files changed

Lines changed: 86 additions & 11 deletions

docs/chat-ui-react.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
| [ChatInput({ placeholder, stream, inputAutoFocus, handleError, sendButtonIcon, customCssClasses, onSend, onRetry, })](./chat-ui-react.chatinput.md) | A component that allows user to input message and send to Chat API. |
1313
| [ChatPanel(props)](./chat-ui-react.chatpanel.md) | A component that renders a full panel for chat bot interactions. This includes the message bubbles for the conversation, input box with send button, and header (if provided). |
1414
| [ChatPopUp(props)](./chat-ui-react.chatpopup.md) | A component that renders a popup button that displays and hides a panel for chat bot interactions. |
15-
| [MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, })](./chat-ui-react.messagebubble.md) | A component that displays the provided message. |
15+
| [MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, renderMarkdown, })](./chat-ui-react.messagebubble.md) | A component that displays the provided message. |
1616
| [useComposedCssClasses(builtInClasses, customClasses)](./chat-ui-react.usecomposedcssclasses.md) | useComposedCssClasses merges a component's built-in tailwind classes with custom tailwind classes. |
1717
| [useReportAnalyticsEvent()](./chat-ui-react.usereportanalyticsevent.md) | Returns a function to send requests to Yext Analytics API. |
1818

docs/chat-ui-react.messagebubble.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ A component that displays the provided message.
99
**Signature:**
1010

1111
```typescript
12-
export declare function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, }: MessageBubbleProps): React.JSX.Element;
12+
export declare function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, renderMarkdown, }: MessageBubbleProps): React.JSX.Element;
1313
```
1414

1515
## Parameters
1616

1717
| Parameter | Type | Description |
1818
| --- | --- | --- |
19-
| { message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, } | [MessageBubbleProps](./chat-ui-react.messagebubbleprops.md) | |
19+
| { message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, renderMarkdown, } | [MessageBubbleProps](./chat-ui-react.messagebubbleprops.md) | |
2020

2121
**Returns:**
2222

docs/chat-ui-react.messagebubbleprops.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface MessageBubbleProps
2121
| [linkTarget?](./chat-ui-react.messagebubbleprops.linktarget.md) | | string | _(Optional)_ Link target open behavior on click. |
2222
| [message](./chat-ui-react.messagebubbleprops.message.md) | | Message | The message to display. |
2323
| [onLinkClick?](./chat-ui-react.messagebubbleprops.onlinkclick.md) | | (href?: string) => void | _(Optional)_ A callback which is called when user clicks a link. |
24+
| [renderMarkdown?](./chat-ui-react.messagebubbleprops.rendermarkdown.md) | | boolean | _(Optional)_ Whether to render the message text as markdown. Defaults to true. |
2425
| [showFeedbackButtons?](./chat-ui-react.messagebubbleprops.showfeedbackbuttons.md) | | boolean | _(Optional)_ Whether to show the feedback buttons on the message bubble. Defaults to true. |
2526
| [showTimestamp?](./chat-ui-react.messagebubbleprops.showtimestamp.md) | | boolean | _(Optional)_ Whether to show the timestamp of the message with the message bubble. Defaults to true. |
2627

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[Home](./index.md) &gt; [@yext/chat-ui-react](./chat-ui-react.md) &gt; [MessageBubbleProps](./chat-ui-react.messagebubbleprops.md) &gt; [renderMarkdown](./chat-ui-react.messagebubbleprops.rendermarkdown.md)
4+
5+
## MessageBubbleProps.renderMarkdown property
6+
7+
Whether to render the message text as markdown. Defaults to true.
8+
9+
**Signature:**
10+
11+
```typescript
12+
renderMarkdown?: boolean;
13+
```

etc/chat-ui-react.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export interface InitialMessagePopUpCssClasses {
178178
}
179179

180180
// @public
181-
export function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, }: MessageBubbleProps): React_2.JSX.Element;
181+
export function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, renderMarkdown, }: MessageBubbleProps): React_2.JSX.Element;
182182

183183
// @public
184184
export interface MessageBubbleCssClasses {
@@ -219,6 +219,7 @@ export interface MessageBubbleProps {
219219
linkTarget?: string;
220220
message: Message;
221221
onLinkClick?: (href?: string) => void;
222+
renderMarkdown?: boolean;
222223
showFeedbackButtons?: boolean;
223224
showTimestamp?: boolean;
224225
}

src/components/MessageBubble.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ export interface MessageBubbleProps {
7979
linkTarget?: string;
8080
/** A callback which is called when user clicks a link. */
8181
onLinkClick?: (href?: string) => void;
82+
/**
83+
* Whether to render the message text as markdown.
84+
* Defaults to true.
85+
*/
86+
renderMarkdown?: boolean;
8287
}
8388

8489
/**
@@ -96,6 +101,7 @@ export function MessageBubble({
96101
formatTimestamp = defaultFormatTimestamp,
97102
linkTarget,
98103
onLinkClick,
104+
renderMarkdown = true,
99105
}: MessageBubbleProps) {
100106
const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);
101107
const bubbleCssClasses = twMerge(
@@ -140,13 +146,17 @@ export function MessageBubble({
140146
responseId={message.responseId}
141147
/>
142148
)}
143-
<Markdown
144-
content={message.text}
145-
responseId={message.responseId}
146-
customCssClasses={markdownCssClasses}
147-
linkTarget={linkTarget}
148-
onLinkClick={onLinkClick}
149-
/>
149+
{renderMarkdown ? (
150+
<Markdown
151+
content={message.text}
152+
responseId={message.responseId}
153+
customCssClasses={markdownCssClasses}
154+
linkTarget={linkTarget}
155+
onLinkClick={onLinkClick}
156+
/>
157+
) : (
158+
<div className={textCssClasses}>{message.text}</div>
159+
)}
150160
</div>
151161
{/* fallback on empty space here to perserve the height for timestamp div */}
152162
{showTimestamp && (

tests/components/MessageBubble.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,53 @@ it("applies link target setting (parent)", async () => {
7979

8080
expect(screen.getByText("msg link")).toHaveAttribute("target", "_parent");
8181
});
82+
83+
it("renders markdown by default", () => {
84+
const linkMessage: Message = {
85+
text: "Test [msg link](msglink)",
86+
timestamp: "2023-06-01T15:26:55.362Z",
87+
source: MessageSource.USER,
88+
};
89+
90+
render(
91+
<ChatHeadlessProvider config={dummyConfig}>
92+
<MessageBubble message={linkMessage} />
93+
</ChatHeadlessProvider>
94+
);
95+
96+
expect(screen.getByText("msg link")).toHaveAttribute("href", "msglink");
97+
});
98+
99+
it("renders raw message text when renderMarkdown is false", () => {
100+
const linkMessage: Message = {
101+
text: "Test [msg link](msglink)",
102+
timestamp: "2023-06-01T15:26:55.362Z",
103+
source: MessageSource.USER,
104+
};
105+
106+
render(
107+
<ChatHeadlessProvider config={dummyConfig}>
108+
<MessageBubble message={linkMessage} renderMarkdown={false} />
109+
</ChatHeadlessProvider>
110+
);
111+
112+
expect(screen.getByText(linkMessage.text)).toBeInTheDocument();
113+
expect(screen.queryByRole("link")).not.toBeInTheDocument();
114+
});
115+
116+
it("escapes html when renderMarkdown is false", () => {
117+
const htmlMessage: Message = {
118+
text: "<strong>Test</strong>",
119+
timestamp: "2023-06-01T15:26:55.362Z",
120+
source: MessageSource.USER,
121+
};
122+
123+
render(
124+
<ChatHeadlessProvider config={dummyConfig}>
125+
<MessageBubble message={htmlMessage} renderMarkdown={false} />
126+
</ChatHeadlessProvider>
127+
);
128+
129+
expect(screen.getByText(htmlMessage.text)).toBeInTheDocument();
130+
expect(screen.queryByText("Test")).not.toBeInTheDocument();
131+
});

0 commit comments

Comments
 (0)