11// ============================================================================
22// Markdown Content - Shared component for rendering markdown
33// ============================================================================
4- import { type FunctionComponent , ReactNode } from 'react' ;
5- import Markdown , { Options } from 'react-markdown' ;
6- import remarkGfm from 'remark-gfm' ;
4+ import { lazy , Suspense , type FunctionComponent , type ReactNode } from 'react' ;
5+ import type { Options } from 'react-markdown' ;
76import { ContentVariants } from '@patternfly/react-core' ;
8- import CodeBlockMessage , { CodeBlockMessageProps } from '../Message/CodeBlockMessage/CodeBlockMessage' ;
7+ import type { CodeBlockMessageProps } from '../Message/CodeBlockMessage/CodeBlockMessage' ;
8+ import type { TableProps } from '@patternfly/react-table' ;
9+ import type { PluggableList } from 'unified' ;
10+ import type { ButtonProps } from '@patternfly/react-core' ;
911import TextMessage from '../Message/TextMessage/TextMessage' ;
10- import ListItemMessage from '../Message/ListMessage/ListItemMessage' ;
11- import UnorderedListMessage from '../Message/ListMessage/UnorderedListMessage' ;
12- import OrderedListMessage from '../Message/ListMessage/OrderedListMessage' ;
13- import TableMessage from '../Message/TableMessage/TableMessage' ;
14- import TrMessage from '../Message/TableMessage/TrMessage' ;
15- import TdMessage from '../Message/TableMessage/TdMessage' ;
16- import TbodyMessage from '../Message/TableMessage/TbodyMessage' ;
17- import TheadMessage from '../Message/TableMessage/TheadMessage' ;
18- import ThMessage from '../Message/TableMessage/ThMessage' ;
19- import { TableProps } from '@patternfly/react-table' ;
20- import ImageMessage from '../Message/ImageMessage/ImageMessage' ;
21- import rehypeUnwrapImages from 'rehype-unwrap-images' ;
22- import rehypeExternalLinks from 'rehype-external-links' ;
23- import rehypeSanitize from 'rehype-sanitize' ;
24- import rehypeHighlight from 'rehype-highlight' ;
25- import 'highlight.js/styles/vs2015.css' ;
26- import { PluggableList } from 'unified' ;
27- import LinkMessage from '../Message/LinkMessage/LinkMessage' ;
28- import { rehypeMoveImagesOutOfParagraphs } from '../Message/Plugins/rehypeMoveImagesOutOfParagraphs' ;
29- import SuperscriptMessage from '../Message/SuperscriptMessage/SuperscriptMessage' ;
30- import { ButtonProps } from '@patternfly/react-core' ;
3112import { css } from '@patternfly/react-styles' ;
3213
14+ const MarkdownRenderer = lazy ( ( ) => import ( './MarkdownRenderer' ) ) ;
15+
3316/**
3417 * MarkdownContent renders content either as plain text or with content with markdown support.
3518 *
@@ -69,32 +52,10 @@ export interface MarkdownContentProps {
6952export const MarkdownContent : FunctionComponent < MarkdownContentProps > = ( {
7053 content,
7154 isMarkdownDisabled,
72- codeBlockProps,
73- tableProps,
74- openLinkInNewTab = true ,
75- additionalRehypePlugins = [ ] ,
76- additionalRemarkPlugins = [ ] ,
77- linkProps,
78- reactMarkdownProps,
79- remarkGfmProps,
80- hasNoImages = false ,
8155 isPrimary,
8256 textComponent,
83- shouldRetainStyles
57+ ... markdownProps
8458} : MarkdownContentProps ) => {
85- let rehypePlugins : PluggableList = [ rehypeUnwrapImages , rehypeMoveImagesOutOfParagraphs , rehypeHighlight ] ;
86- if ( openLinkInNewTab ) {
87- rehypePlugins = rehypePlugins . concat ( [ [ rehypeExternalLinks , { target : '_blank' } , rehypeSanitize ] ] ) ;
88- }
89- if ( additionalRehypePlugins ) {
90- rehypePlugins . push ( ...additionalRehypePlugins ) ;
91- }
92-
93- const disallowedElements = hasNoImages ? [ 'img' ] : [ ] ;
94- if ( reactMarkdownProps && reactMarkdownProps . disallowedElements ) {
95- disallowedElements . push ( ...reactMarkdownProps . disallowedElements ) ;
96- }
97-
9859 if ( isMarkdownDisabled ) {
9960 if ( textComponent ) {
10061 return < > { textComponent } </ > ;
@@ -107,176 +68,9 @@ export const MarkdownContent: FunctionComponent<MarkdownContentProps> = ({
10768 }
10869
10970 return (
110- < Markdown
111- components = { {
112- section : ( props ) => {
113- // eslint-disable-next-line @typescript-eslint/no-unused-vars
114- const { node, ...rest } = props ;
115- return (
116- < section
117- { ...rest }
118- className = { css ( 'pf-chatbot__message-text' , shouldRetainStyles && 'pf-m-markdown' , rest ?. className ) }
119- />
120- ) ;
121- } ,
122- p : ( props ) => {
123- // eslint-disable-next-line @typescript-eslint/no-unused-vars
124- const { node, ...rest } = props ;
125- return (
126- < TextMessage
127- shouldRetainStyles = { shouldRetainStyles }
128- component = { ContentVariants . p }
129- { ...rest }
130- isPrimary = { isPrimary }
131- />
132- ) ;
133- } ,
134- code : ( { children, ...props } ) => {
135- // eslint-disable-next-line @typescript-eslint/no-unused-vars
136- const { node, ...codeProps } = props ;
137- return (
138- < CodeBlockMessage
139- { ...codeProps }
140- { ...codeBlockProps }
141- isPrimary = { isPrimary }
142- shouldRetainStyles = { shouldRetainStyles }
143- >
144- { children }
145- </ CodeBlockMessage >
146- ) ;
147- } ,
148- h1 : ( props ) => {
149- // eslint-disable-next-line @typescript-eslint/no-unused-vars
150- const { node, ...rest } = props ;
151- return < TextMessage shouldRetainStyles = { shouldRetainStyles } component = { ContentVariants . h1 } { ...rest } /> ;
152- } ,
153- h2 : ( props ) => {
154- // eslint-disable-next-line @typescript-eslint/no-unused-vars
155- const { node, ...rest } = props ;
156- return < TextMessage shouldRetainStyles = { shouldRetainStyles } component = { ContentVariants . h2 } { ...rest } /> ;
157- } ,
158- h3 : ( props ) => {
159- // eslint-disable-next-line @typescript-eslint/no-unused-vars
160- const { node, ...rest } = props ;
161- return < TextMessage shouldRetainStyles = { shouldRetainStyles } component = { ContentVariants . h3 } { ...rest } /> ;
162- } ,
163- h4 : ( props ) => {
164- // eslint-disable-next-line @typescript-eslint/no-unused-vars
165- const { node, ...rest } = props ;
166- return < TextMessage shouldRetainStyles = { shouldRetainStyles } component = { ContentVariants . h4 } { ...rest } /> ;
167- } ,
168- h5 : ( props ) => {
169- // eslint-disable-next-line @typescript-eslint/no-unused-vars
170- const { node, ...rest } = props ;
171- return < TextMessage shouldRetainStyles = { shouldRetainStyles } component = { ContentVariants . h5 } { ...rest } /> ;
172- } ,
173- h6 : ( props ) => {
174- // eslint-disable-next-line @typescript-eslint/no-unused-vars
175- const { node, ...rest } = props ;
176- return < TextMessage shouldRetainStyles = { shouldRetainStyles } component = { ContentVariants . h6 } { ...rest } /> ;
177- } ,
178- blockquote : ( props ) => {
179- // eslint-disable-next-line @typescript-eslint/no-unused-vars
180- const { node, ...rest } = props ;
181- return (
182- < TextMessage shouldRetainStyles = { shouldRetainStyles } component = { ContentVariants . blockquote } { ...rest } />
183- ) ;
184- } ,
185- ul : ( props ) => {
186- // eslint-disable-next-line @typescript-eslint/no-unused-vars
187- const { node, ...rest } = props ;
188- return < UnorderedListMessage shouldRetainStyles = { shouldRetainStyles } { ...rest } /> ;
189- } ,
190- ol : ( props ) => {
191- // eslint-disable-next-line @typescript-eslint/no-unused-vars
192- const { node, ...rest } = props ;
193- return < OrderedListMessage shouldRetainStyles = { shouldRetainStyles } { ...rest } /> ;
194- } ,
195- li : ( props ) => {
196- // eslint-disable-next-line @typescript-eslint/no-unused-vars
197- const { node, ...rest } = props ;
198- return < ListItemMessage { ...rest } /> ;
199- } ,
200- // table requires node attribute for calculating headers for mobile breakpoint
201- table : ( props ) => (
202- < TableMessage shouldRetainStyles = { shouldRetainStyles } { ...props } { ...tableProps } isPrimary = { isPrimary } />
203- ) ,
204- tbody : ( props ) => {
205- // eslint-disable-next-line @typescript-eslint/no-unused-vars
206- const { node, ...rest } = props ;
207- return < TbodyMessage { ...rest } /> ;
208- } ,
209- thead : ( props ) => {
210- // eslint-disable-next-line @typescript-eslint/no-unused-vars
211- const { node, ...rest } = props ;
212- return < TheadMessage { ...rest } /> ;
213- } ,
214- tr : ( props ) => {
215- // eslint-disable-next-line @typescript-eslint/no-unused-vars
216- const { node, ...rest } = props ;
217- return < TrMessage { ...rest } /> ;
218- } ,
219- td : ( props ) => {
220- // Conflicts with Td type
221- // eslint-disable-next-line @typescript-eslint/no-unused-vars
222- const { node, width, ...rest } = props ;
223- return < TdMessage { ...rest } /> ;
224- } ,
225- th : ( props ) => {
226- // eslint-disable-next-line @typescript-eslint/no-unused-vars
227- const { node, ...rest } = props ;
228- return < ThMessage { ...rest } /> ;
229- } ,
230- img : ( props ) => {
231- // eslint-disable-next-line @typescript-eslint/no-unused-vars
232- const { node, ...rest } = props ;
233- return < ImageMessage { ...rest } /> ;
234- } ,
235- a : ( props ) => {
236- // node is just the details of the document structure - not needed
237- // eslint-disable-next-line @typescript-eslint/no-unused-vars
238- const { node, ...rest } = props ;
239- return (
240- // some a types conflict with ButtonProps, but it's ok because we are using an a tag
241- // there are too many to handle manually
242- < LinkMessage shouldRetainStyles = { shouldRetainStyles } { ...( rest as any ) } { ...linkProps } >
243- { props . children }
244- </ LinkMessage >
245- ) ;
246- } ,
247- // used for footnotes
248- sup : ( props ) => {
249- // eslint-disable-next-line @typescript-eslint/no-unused-vars
250- const { node, ...rest } = props ;
251- return < SuperscriptMessage { ...rest } /> ;
252- }
253- } }
254- remarkPlugins = { [ [ remarkGfm , { ...remarkGfmProps } ] , ...additionalRemarkPlugins ] }
255- rehypePlugins = { rehypePlugins }
256- { ...reactMarkdownProps }
257- remarkRehypeOptions = { {
258- // removes sr-only class from footnote labels applied by default
259- footnoteLabelProperties : { className : [ '' ] } ,
260- // omit default ↩ text; backref icon is rendered in LinkMessage
261- footnoteBackContent : ( _referenceIndex , rereferenceIndex ) => {
262- if ( rereferenceIndex > 1 ) {
263- return [
264- {
265- type : 'element' ,
266- tagName : 'sup' ,
267- properties : { } ,
268- children : [ { type : 'text' , value : String ( rereferenceIndex ) } ]
269- }
270- ] ;
271- }
272- return [ ] ;
273- } ,
274- ...reactMarkdownProps ?. remarkRehypeOptions
275- } }
276- disallowedElements = { disallowedElements }
277- >
278- { content }
279- </ Markdown >
71+ < Suspense fallback = { < span className = { css ( 'pf-chatbot__message-text' ) } > { content } </ span > } >
72+ < MarkdownRenderer { ...markdownProps } content = { content } isPrimary = { isPrimary } />
73+ </ Suspense >
28074 ) ;
28175} ;
28276
0 commit comments