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' ;
20-
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' ;
4+ import { useState } from 'react' ;
5+ import { ExpandableSectionProps , ExpandableSection } from '@patternfly/react-core' ;
6+
7+ import { CodeEditor , CodeEditorProps , Language } from '@patternfly/react-code-editor' ;
248
259export 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,63 @@ const DEFAULT_COLLAPSED_TEXT = 'Show more';
4729const 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 ) ;
41+ const languageString = / l a n g u a g e - ( \w + ) / . exec ( className || '' ) ?. [ 1 ] ;
42+
43+ const getLanguage = ( lang : string | undefined ) : Language | undefined => {
44+ if ( ! lang ) {
45+ return undefined ;
46+ }
47+
48+ const languageMap : Record < string , Language > = {
49+ javascript : Language . javascript ,
50+ js : Language . javascript ,
51+ typescript : Language . typescript ,
52+ ts : Language . typescript ,
53+ jsx : Language . javascript ,
54+ tsx : Language . typescript ,
55+ html : Language . html ,
56+ css : Language . css ,
57+ scss : Language . scss ,
58+ json : Language . json ,
59+ xml : Language . xml ,
60+ yaml : Language . yaml ,
61+ yml : Language . yaml ,
62+ python : Language . python ,
63+ py : Language . python ,
64+ java : Language . java ,
65+ c : Language . c ,
66+ cpp : Language . cpp ,
67+ csharp : Language . csharp ,
68+ cs : Language . csharp ,
69+ go : Language . go ,
70+ rust : Language . rust ,
71+ php : Language . php ,
72+ ruby : Language . ruby ,
73+ rb : Language . ruby ,
74+ bash : Language . shell ,
75+ sh : Language . shell ,
76+ shell : Language . shell ,
77+ dockerfile : Language . dockerfile ,
78+ sql : Language . sql ,
79+ markdown : Language . markdown ,
80+ md : Language . markdown ,
81+ plaintext : Language . plaintext ,
82+ text : Language . plaintext
83+ } ;
84+
85+ return languageMap [ lang . toLowerCase ( ) ] ;
86+ } ;
6687
67- const language = / l a n g u a g e - ( \w + ) / . exec ( className || '' ) ?. [ 1 ] ;
88+ const language = getLanguage ( languageString ) ;
6889
6990 // Get custom toggle text from data attributes if available - for use with rehype plugins
7091 const customExpandedText = props [ 'data-expanded-text' ] ;
@@ -84,27 +105,10 @@ const CodeBlockMessage = ({
84105 ) ;
85106 }
86107
87- const onToggle = ( isExpanded ) => {
108+ const onToggle = ( _event : React . MouseEvent , isExpanded : boolean ) => {
88109 setIsExpanded ( isExpanded ) ;
89110 } ;
90111
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-
108112 if ( ! String ( children ) . includes ( '\n' ) ) {
109113 return (
110114 < code { ...props } className = "pf-chatbot__message-inline-code" >
@@ -113,82 +117,33 @@ const CodeBlockMessage = ({
113117 ) ;
114118 }
115119
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- </ >
120+ const codeEditor = (
121+ < CodeEditor
122+ key = { `code-editor-${ isExpanded } ` } // Force remount on state change
123+ code = { children ?. toString ( ) }
124+ isReadOnly
125+ isCopyEnabled
126+ isLanguageLabelVisible
127+ language = { language }
128+ height = "sizeToFit"
129+ { ...codeEditorProps }
130+ > </ CodeEditor >
133131 ) ;
134132
135133 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 >
134+ < div >
135+ { isExpandable ? (
136+ < ExpandableSection
137+ toggleText = { isExpanded ? finalExpandedText : finalCollapsedText }
138+ onToggle = { onToggle }
139+ isExpanded = { isExpanded }
140+ { ...expandableSectionProps }
141+ >
142+ { codeEditor }
143+ </ ExpandableSection >
144+ ) : (
145+ codeEditor
146+ ) }
192147 </ div >
193148 ) ;
194149} ;
0 commit comments