Skip to content

Commit a332dfc

Browse files
authored
Merge pull request #446 from rebeccaalpert/links
feat(Message): Add ability to handle external links
2 parents 24d9e94 + 88031c0 commit a332dfc

File tree

13 files changed

+3190
-5378
lines changed

13 files changed

+3190
-5378
lines changed

package-lock.json

Lines changed: 3095 additions & 5366 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@
9191
"puppeteer-cluster": "^0.24.0"
9292
},
9393
"dependencies": {
94-
"dompurify": "^3.2.0",
9594
"react-dropzone": "^14.2.3"
9695
},
9796
"packageManager": "[email protected]+sha512.837566d24eec14ec0f5f1411adb544e892b3454255e61fdef8fd05f3429480102806bac7446bc9daff3896b01ae4b62d00096c7e989f1596f2af10b927532f39"

packages/module/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
"react-syntax-highlighter": "^15.5.0",
4141
"remark-gfm": "^4.0.0",
4242
"rehype-unwrap-images": "^1.0.0",
43+
"rehype-external-links": "^3.0.0",
44+
"rehype-sanitize": "^6.0.0",
4345
"path-browserify": "^1.0.1"
4446
},
4547
"peerDependencies": {

packages/module/src/Chatbot/Chatbot.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
background-color: var(--pf-t--chatbot--background);
1313
border-radius: var(--pf-t--global--border--radius--medium);
1414
box-shadow: var(--pf-t--global--box-shadow--lg);
15-
font-size: var(--pf-t--chatbot--font-size);
15+
font-size: var(--pf-t--global--font--size--md);
1616
z-index: var(--pf-t--global--z-index--md);
1717
-webkit-font-smoothing: antialiased;
1818
-moz-osx-font-smoothing: grayscale;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// ============================================================================
2+
// Chatbot Main - Message - Content - Link
3+
// ============================================================================
4+
5+
import React from 'react';
6+
import { Button, ButtonProps } from '@patternfly/react-core';
7+
import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
8+
9+
const LinkMessage = ({ children, target, href, ...props }: ButtonProps) => {
10+
if (target === '_blank') {
11+
return (
12+
<Button
13+
component="a"
14+
variant="link"
15+
href={href}
16+
icon={<ExternalLinkSquareAltIcon />}
17+
iconPosition="end"
18+
isInline
19+
target={target}
20+
{...props}
21+
>
22+
{children}
23+
</Button>
24+
);
25+
}
26+
27+
return (
28+
<Button isInline component="a" href={href} variant="link" {...props}>
29+
{children}
30+
</Button>
31+
);
32+
};
33+
34+
export default LinkMessage;

packages/module/src/Message/ListMessage/ListMessage.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
.pf-v6-c-list,
1212
ul,
1313
li {
14-
font-size: var(--pf-t--chatbot--font-size);
14+
font-size: var(--pf-t--global--font--size--md);
1515
}
1616
}
1717

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Message from './Message';
55
import userEvent from '@testing-library/user-event';
66
import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quickstart';
77
import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
8+
import rehypeExternalLinks from '../__mocks__/rehype-external-links';
89

910
const ALL_ACTIONS = [
1011
{ label: /Good response/i },
@@ -150,6 +151,9 @@ const checkListItemsRendered = () => {
150151
};
151152

152153
describe('Message', () => {
154+
beforeEach(() => {
155+
jest.clearAllMocks();
156+
});
153157
it('should render user messages correctly', () => {
154158
render(<Message avatar="./img" role="user" name="User" content="Hi" />);
155159
expect(screen.getByText('User')).toBeTruthy();
@@ -747,4 +751,22 @@ describe('Message', () => {
747751
render(<Message avatar="./img" role="user" name="User" content={IMAGE} />);
748752
expect(screen.getByRole('img', { name: /Multi-colored wavy lines on a black background/i })).toBeTruthy();
749753
});
754+
it('should handle external links correctly', () => {
755+
render(<Message avatar="./img" role="user" name="User" content={`[PatternFly](https://www.patternfly.org/)`} />);
756+
// we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
757+
expect(rehypeExternalLinks).toHaveBeenCalledTimes(1);
758+
});
759+
it('should handle external links correctly', () => {
760+
render(
761+
<Message
762+
avatar="./img"
763+
role="user"
764+
name="User"
765+
content={`[PatternFly](https://www.patternfly.org/)`}
766+
openLinkInNewTab={false}
767+
/>
768+
);
769+
// we are mocking rehype libraries, so we can't test target _blank addition on links directly with RTL
770+
expect(rehypeExternalLinks).not.toHaveBeenCalled();
771+
});
750772
});

packages/module/src/Message/Message.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ import ThMessage from './TableMessage/ThMessage';
3838
import { TableProps } from '@patternfly/react-table';
3939
import ImageMessage from './ImageMessage/ImageMessage';
4040
import rehypeUnwrapImages from 'rehype-unwrap-images';
41+
import rehypeExternalLinks from 'rehype-external-links';
42+
import rehypeSanitize from 'rehype-sanitize';
4143
import { PluggableList } from 'react-markdown/lib';
44+
import LinkMessage from './LinkMessage/LinkMessage';
4245

4346
export interface MessageAttachment {
4447
/** Name of file attached to the message */
@@ -136,6 +139,8 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
136139
tableProps?: Required<Pick<TableProps, 'aria-label'>> & TableProps;
137140
/** Additional rehype plugins passed from the consumer */
138141
additionalRehypePlugins?: PluggableList;
142+
/** Whether to open links in message in new tab. */
143+
openLinkInNewTab?: boolean;
139144
}
140145

141146
export const MessageBase: React.FunctionComponent<MessageProps> = ({
@@ -162,10 +167,18 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
162167
isLiveRegion = true,
163168
innerRef,
164169
tableProps,
170+
openLinkInNewTab = true,
165171
additionalRehypePlugins = [],
166172
...props
167173
}: MessageProps) => {
168174
const { beforeMainContent, afterMainContent, endContent } = extraContent || {};
175+
let rehypePlugins: PluggableList = [rehypeUnwrapImages];
176+
if (openLinkInNewTab) {
177+
rehypePlugins = rehypePlugins.concat([[rehypeExternalLinks, { target: '_blank' }, rehypeSanitize]]);
178+
}
179+
if (additionalRehypePlugins) {
180+
rehypePlugins.push(...additionalRehypePlugins);
181+
}
169182
let avatarClassName;
170183
if (avatarProps && 'className' in avatarProps) {
171184
const { className, ...rest } = avatarProps;
@@ -175,9 +188,6 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
175188
// Keep timestamps consistent between Timestamp component and aria-label
176189
const date = new Date();
177190
const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
178-
179-
const rehypePlugins = [rehypeUnwrapImages, ...(additionalRehypePlugins ?? [])];
180-
181191
return (
182192
<section
183193
aria-label={`Message from ${role} - ${dateString}`}
@@ -244,7 +254,12 @@ export const MessageBase: React.FunctionComponent<MessageProps> = ({
244254
return <TdMessage {...rest} />;
245255
},
246256
th: (props) => <ThMessage {...props} />,
247-
img: (props) => <ImageMessage {...props} />
257+
img: (props) => <ImageMessage {...props} />,
258+
a: (props) => (
259+
<LinkMessage href={props.href} rel={props.rel} target={props.target}>
260+
{props.children}
261+
</LinkMessage>
262+
)
248263
}}
249264
remarkPlugins={[remarkGfm]}
250265
rehypePlugins={rehypePlugins}

packages/module/src/Message/TextMessage/TextMessage.scss

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@
2121
padding: var(--pf-t--global--spacer--sm) 0 var(--pf-t--global--spacer--sm) 0;
2222
border-radius: var(--pf-t--global--border--radius--small);
2323

24+
.pf-v6-c-button.pf-m-link {
25+
font-size: var(--pf-t--global--font--size--md);
26+
}
27+
2428
.pf-v6-c-content,
2529
.pf-v6-c-content--small,
2630
.pf-v6-c-content--blockquote,
2731
p,
2832
a {
29-
--pf-v6-c-content--FontSize: var(--pf-t--chatbot--font-size);
33+
--pf-v6-c-content--FontSize: var(--pf-t--global--font--size--md);
3034
}
3135

3236
code {
@@ -41,6 +45,9 @@
4145
color: var(--pf-t--global--text--color--on-brand--default);
4246
--pf-v6-c-content--Color: var(--pf-t--global--text--color--on-brand--default);
4347
padding: var(--pf-t--global--spacer--sm);
48+
.pf-v6-c-button__icon {
49+
--pf-v6-c-button__icon--Color: var(--pf-t--global--text--color--on-brand--default);
50+
}
4451

4552
.pf-v6-c-content,
4653
.pf-v6-c-content--small,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const rehypeExternalLinks = jest.fn((children) => children);
2+
3+
export default rehypeExternalLinks;

0 commit comments

Comments
 (0)