Skip to content

Commit c2ddda4

Browse files
committed
Investigate code editor
1 parent 912cd12 commit c2ddda4

8 files changed

Lines changed: 48 additions & 179 deletions

File tree

packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ spec:
6565
6666
Here is some JavaScript code:
6767
68-
~~~js
68+
~~~javascript
6969
const MessageLoading = () => (
7070
<div className="pf-chatbot__message-loading">
7171
<span className="pf-chatbot__message-loading-dots">

packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ spec:
6565
6666
Here is some JavaScript code:
6767
68-
~~~js
68+
~~~javascript
6969
const MessageLoading = () => (
7070
<div className="pf-chatbot__message-loading">
7171
<span className="pf-chatbot__message-loading-dots">

packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ spec:
7878
7979
Here is some JavaScript code:
8080
81-
~~~js
81+
~~~javascript
8282
const MessageLoading = () => (
8383
<div className="pf-chatbot__message-loading">
8484
<span className="pf-chatbot__message-loading-dots">

packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotInDrawer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ spec:
8787
8888
Here is some JavaScript code:
8989
90-
~~~js
90+
~~~javascript
9191
const MessageLoading = () => (
9292
<div className="pf-chatbot__message-loading">
9393
<span className="pf-chatbot__message-loading-dots">

packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedChatbot.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ spec:
8585
8686
Here is some JavaScript code:
8787
88-
~~~js
88+
~~~javascript
8989
const MessageLoading = () => (
9090
<div className="pf-chatbot__message-loading">
9191
<span className="pf-chatbot__message-loading-dots">

packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx

Lines changed: 35 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,22 @@
11
// ============================================================================
22
// Chatbot Main - Message - Content - Code Block
33
// ============================================================================
4-
import { useState, useRef, useId, useCallback, useEffect } from 'react';
5-
import SyntaxHighlighter from 'react-syntax-highlighter';
6-
import { obsidian } from 'react-syntax-highlighter/dist/esm/styles/hljs';
7-
// Import PatternFly components
8-
import {
9-
CodeBlock,
10-
CodeBlockAction,
11-
CodeBlockCode,
12-
Button,
13-
Tooltip,
14-
ExpandableSection,
15-
ExpandableSectionToggle,
16-
ExpandableSectionProps,
17-
ExpandableSectionToggleProps,
18-
ExpandableSectionVariant
19-
} from '@patternfly/react-core';
4+
import { useState } from 'react';
5+
import { ExpandableSectionProps, ExpandableSection } from '@patternfly/react-core';
206

21-
import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon';
22-
import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon';
23-
import { ExpandableSectionForSyntaxHighlighter } from './ExpandableSectionForSyntaxHighlighter';
7+
import { CodeEditor, CodeEditorProps, Language } from '@patternfly/react-code-editor';
248

259
export interface CodeBlockMessageProps {
2610
/** Content rendered in code block */
2711
children?: React.ReactNode;
28-
/** Aria label applied to code block */
29-
'aria-label'?: string;
3012
/** Class name applied to code block */
3113
className?: string;
3214
/** Whether code block is expandable */
3315
isExpandable?: boolean;
3416
/** Additional props passed to expandable section if isExpandable is applied */
3517
expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
36-
/** Additional props passed to expandable toggle if isExpandable is applied */
37-
expandableSectionToggleProps?: ExpandableSectionToggleProps;
18+
/** Additional props passed to code editor */
19+
codeEditorProps?: Omit<CodeEditorProps, 'ref'>;
3820
/** Link text applied to expandable toggle when expanded */
3921
expandedText?: string;
4022
/** Link text applied to expandable toggle when collapsed */
@@ -47,24 +29,17 @@ const DEFAULT_COLLAPSED_TEXT = 'Show more';
4729
const CodeBlockMessage = ({
4830
children,
4931
className,
50-
'aria-label': ariaLabel,
5132
isExpandable = false,
5233
expandableSectionProps,
53-
expandableSectionToggleProps,
5434
expandedText = DEFAULT_EXPANDED_TEXT,
5535
collapsedText = DEFAULT_COLLAPSED_TEXT,
36+
codeEditorProps,
5637
...props
5738
}: CodeBlockMessageProps) => {
58-
const [copied, setCopied] = useState(false);
5939
const [isExpanded, setIsExpanded] = useState(false);
6040

61-
const buttonRef = useRef();
62-
const tooltipID = useId();
63-
const toggleId = useId();
64-
const contentId = useId();
65-
const codeBlockRef = useRef<HTMLDivElement>(null);
66-
67-
const language = /language-(\w+)/.exec(className || '')?.[1];
41+
const languageString = /language-(\w+)/.exec(className || '')?.[1];
42+
const language = languageString ? Language[languageString] : undefined;
6843

6944
// Get custom toggle text from data attributes if available - for use with rehype plugins
7045
const customExpandedText = props['data-expanded-text'];
@@ -84,27 +59,10 @@ const CodeBlockMessage = ({
8459
);
8560
}
8661

87-
const onToggle = (isExpanded) => {
62+
const onToggle = (_event: React.MouseEvent, isExpanded: boolean) => {
8863
setIsExpanded(isExpanded);
8964
};
9065

91-
// Handle clicking copy button
92-
const handleCopy = useCallback((event, text) => {
93-
navigator.clipboard.writeText(text.toString());
94-
setCopied(true);
95-
}, []);
96-
97-
// Reset copied state
98-
useEffect(() => {
99-
if (copied) {
100-
const timer = setTimeout(() => {
101-
setCopied(false);
102-
}, 3000);
103-
104-
return () => clearTimeout(timer);
105-
}
106-
});
107-
10866
if (!String(children).includes('\n')) {
10967
return (
11068
<code {...props} className="pf-chatbot__message-inline-code">
@@ -113,82 +71,35 @@ const CodeBlockMessage = ({
11371
);
11472
}
11573

116-
// Setup code block header
117-
const actions = (
118-
<>
119-
<CodeBlockAction>
120-
{language && <div className="pf-chatbot__message-code-block-language">{language}</div>}
121-
<Button
122-
ref={buttonRef}
123-
aria-label={ariaLabel ?? 'Copy code'}
124-
variant="plain"
125-
className="pf-chatbot__button--copy"
126-
onClick={(event) => handleCopy(event, children)}
127-
>
128-
{copied ? <CheckIcon /> : <CopyIcon />}
129-
</Button>
130-
<Tooltip id={tooltipID} content="Copy" position="top" triggerRef={buttonRef} />
131-
</CodeBlockAction>
132-
</>
74+
const codeEditor = (
75+
<CodeEditor
76+
// Force remount on state change
77+
// Prevents bug where code editor is 1px high when expandable is opened, closed, and reopened
78+
key={`code-editor-${isExpanded}`}
79+
code={children?.toString()}
80+
isReadOnly
81+
isCopyEnabled
82+
isLanguageLabelVisible
83+
language={language}
84+
height="sizeToFit"
85+
{...codeEditorProps}
86+
></CodeEditor>
13387
);
13488

13589
return (
136-
<div className="pf-chatbot__message-code-block" ref={codeBlockRef}>
137-
<CodeBlock actions={actions}>
138-
<CodeBlockCode>
139-
<>
140-
{language ? (
141-
// SyntaxHighlighter doesn't work with ExpandableSection because it targets the direct child
142-
// Forked for now and adjusted to match what we need
143-
<ExpandableSectionForSyntaxHighlighter
144-
variant={ExpandableSectionVariant.truncate}
145-
isExpanded={isExpanded}
146-
isDetached
147-
toggleId={toggleId}
148-
contentId={contentId}
149-
language={language}
150-
{...expandableSectionProps}
151-
>
152-
<SyntaxHighlighter
153-
{...props}
154-
language={language}
155-
style={obsidian}
156-
PreTag="div"
157-
CodeTag="div"
158-
wrapLongLines
159-
>
160-
{String(children).replace(/\n$/, '')}
161-
</SyntaxHighlighter>
162-
</ExpandableSectionForSyntaxHighlighter>
163-
) : (
164-
<ExpandableSection
165-
variant={ExpandableSectionVariant.truncate}
166-
isExpanded={isExpanded}
167-
isDetached
168-
toggleId={toggleId}
169-
contentId={contentId}
170-
{...expandableSectionProps}
171-
>
172-
{children}
173-
</ExpandableSection>
174-
)}
175-
</>
176-
</CodeBlockCode>
177-
{isExpandable && (
178-
<ExpandableSectionToggle
179-
isExpanded={isExpanded}
180-
onToggle={onToggle}
181-
direction="up"
182-
toggleId={toggleId}
183-
contentId={contentId}
184-
hasTruncatedContent
185-
className="pf-chatbot__message-code-toggle"
186-
{...expandableSectionToggleProps}
187-
>
188-
{isExpanded ? finalExpandedText : finalCollapsedText}
189-
</ExpandableSectionToggle>
190-
)}
191-
</CodeBlock>
90+
<div>
91+
{isExpandable ? (
92+
<ExpandableSection
93+
toggleText={isExpanded ? finalExpandedText : finalCollapsedText}
94+
onToggle={onToggle}
95+
isExpanded={isExpanded}
96+
{...expandableSectionProps}
97+
>
98+
{codeEditor}
99+
</ExpandableSection>
100+
) : (
101+
codeEditor
102+
)}
192103
</div>
193104
);
194105
};

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

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -491,28 +491,13 @@ describe('Message', () => {
491491
it('should render code correctly', () => {
492492
render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} />);
493493
expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
494-
expect(screen.getByRole('button', { name: 'Copy code' })).toBeTruthy();
495-
expect(screen.getByText(/yaml/)).toBeTruthy();
496-
expect(screen.getByText(/apiVersion:/i)).toBeTruthy();
497-
expect(screen.getByText(/helm.openshift.io\/v1beta1/i)).toBeTruthy();
498-
expect(screen.getByText(/metadata:/i)).toBeTruthy();
499-
expect(screen.getByText(/name:/i)).toBeTruthy();
500-
expect(screen.getByText(/azure-sample-repo0oooo00ooo/i)).toBeTruthy();
501-
expect(screen.getByText(/spec/i)).toBeTruthy();
502-
expect(screen.getByText(/connectionConfig:/i)).toBeTruthy();
503-
expect(screen.getByText(/url:/i)).toBeTruthy();
504-
expect(
505-
screen.getByText(/https:\/\/raw.githubusercontent.com\/Azure-Samples\/helm-charts\/master\/docs/i)
506-
).toBeTruthy();
494+
expect(screen.getByRole('button', { name: 'Copy code to clipboard' })).toBeTruthy();
507495
});
508496
it('should render expandable code correctly', () => {
509497
render(
510498
<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} codeBlockProps={{ isExpandable: true }} />
511499
);
512500
expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
513-
expect(screen.getByRole('button', { name: 'Copy code' })).toBeTruthy();
514-
expect(screen.getByText(/yaml/)).toBeTruthy();
515-
expect(screen.getByText(/apiVersion/i)).toBeTruthy();
516501
expect(screen.getByRole('button', { name: /Show more/i })).toBeTruthy();
517502
});
518503
it('should handle click on expandable code correctly', async () => {
@@ -522,40 +507,16 @@ describe('Message', () => {
522507
const button = screen.getByRole('button', { name: /Show more/i });
523508
await userEvent.click(button);
524509
expect(screen.getByRole('button', { name: /Show less/i })).toBeTruthy();
525-
expect(screen.getByText(/yaml/)).toBeTruthy();
526-
expect(screen.getByText(/apiVersion:/i)).toBeTruthy();
527-
expect(screen.getByText(/helm.openshift.io\/v1beta1/i)).toBeTruthy();
528-
expect(screen.getByText(/metadata:/i)).toBeTruthy();
529-
expect(screen.getByText(/name:/i)).toBeTruthy();
530-
expect(screen.getByText(/azure-sample-repo0oooo00ooo/i)).toBeTruthy();
531-
expect(screen.getByText(/spec/i)).toBeTruthy();
532-
expect(screen.getByText(/connectionConfig:/i)).toBeTruthy();
533-
expect(screen.getByText(/url:/i)).toBeTruthy();
534-
expect(
535-
screen.getByText(/https:\/\/raw.githubusercontent.com\/Azure-Samples\/helm-charts\/master\/docs/i)
536-
).toBeTruthy();
537510
});
538511
it('can click copy code button', async () => {
539512
// need explicit setup since RTL stubs clipboard if you do this
540513
const user = userEvent.setup();
541514
render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} />);
542-
expect(screen.getByRole('button', { name: 'Copy code' })).toBeTruthy();
543-
await user.click(screen.getByRole('button', { name: 'Copy code' }));
515+
expect(screen.getByRole('button', { name: 'Copy code to clipboard' })).toBeTruthy();
516+
await user.click(screen.getByRole('button', { name: 'Copy code to clipboard' }));
544517
const clipboardText = await navigator.clipboard.readText();
545518
expect(clipboardText.trim()).toEqual(CODE.trim());
546519
});
547-
it('should handle codeBlockProps correctly by spreading it onto the CodeMessage', () => {
548-
render(
549-
<Message
550-
avatar="./img"
551-
role="user"
552-
name="User"
553-
content={CODE_MESSAGE}
554-
codeBlockProps={{ 'aria-label': 'test' }}
555-
/>
556-
);
557-
expect(screen.getByRole('button', { name: 'test' })).toBeTruthy();
558-
});
559520
it('should handle hasRoundAvatar correctly when it is true', () => {
560521
render(<Message avatar="./img" role="user" name="User" content="Hi" hasRoundAvatar />);
561522
expect(screen.getByRole('img')).toBeTruthy();

packages/module/src/Message/Message.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import LinkMessage from './LinkMessage/LinkMessage';
4949
import ErrorMessage from './ErrorMessage/ErrorMessage';
5050
import MessageInput from './MessageInput';
5151
import { rehypeMoveImagesOutOfParagraphs } from './Plugins/rehypeMoveImagesOutOfParagraphs';
52+
import { CodeEditorProps } from '@patternfly/react-code-editor';
5253

5354
export interface MessageAttachment {
5455
/** Name of file attached to the message */
@@ -111,18 +112,14 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
111112
loadingWord?: string;
112113
/** Props for code blocks */
113114
codeBlockProps?: {
114-
/** Aria label applied to code blocks */
115-
'aria-label'?: string;
116-
/** Class name applied to code blocks */
115+
/** Class name applied to code block */
117116
className?: string;
118-
/** Whether code blocks are expandable */
117+
/** Whether code block is expandable */
119118
isExpandable?: boolean;
120-
/** Length of text initially shown in expandable code blocks; defaults to 10 characters */
121-
maxLength?: number;
122119
/** Additional props passed to expandable section if isExpandable is applied */
123120
expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
124-
/** Additional props passed to expandable toggle if isExpandable is applied */
125-
expandableSectionToggleProps?: ExpandableSectionToggleProps;
121+
/** Additional props passed to code editor */
122+
codeEditorProps?: Omit<CodeEditorProps, 'ref'>;
126123
/** Link text applied to expandable toggle when expanded */
127124
expandedText?: string;
128125
/** Link text applied to expandable toggle when collapsed */

0 commit comments

Comments
 (0)