@@ -25,7 +25,7 @@ export const markdownConvert = async (markdown: string, extensions?: ShowdownExt
2525 return node ;
2626 }
2727
28- // add PF content classes
28+ // add PF content classes to standard elements (details blocks get handled separately)
2929 if ( node . nodeType === 1 ) {
3030 const contentElements = [
3131 'ul' ,
@@ -85,23 +85,100 @@ export const markdownConvert = async (markdown: string, extensions?: ShowdownExt
8585 ) ;
8686 const markdownWithSubstitutedCodeFences = reverseString ( reverseMarkdownWithSubstitutedCodeFences ) ;
8787
88- const parsedMarkdown = await marked . parse ( markdownWithSubstitutedCodeFences ) ;
88+ // Fix malformed HTML entities early in the process
89+ let preprocessedMarkdown = markdownWithSubstitutedCodeFences ;
90+ preprocessedMarkdown = preprocessedMarkdown . replace ( / & n b s p ( [ ^ ; ] ) / g, ' $1' ) . replace ( / & a m p ; n b s p ; / g, ' ' ) ;
91+ preprocessedMarkdown = preprocessedMarkdown . replace ( / & n b s p (? ! [ ; ] ) / g, ' ' ) ;
92+
93+ // Process content in segments to ensure markdown parsing continues after HTML blocks
94+ const htmlBlockRegex = / ( < (?: d e t a i l s | d i v | s e c t i o n | a r t i c l e ) [ ^ > ] * > [ \s \S ] * ?< \/ (?: d e t a i l s | d i v | s e c t i o n | a r t i c l e ) > ) / g;
95+
96+ let parsedMarkdown = '' ;
97+
98+ // Check if there are any HTML blocks
99+ if ( htmlBlockRegex . test ( preprocessedMarkdown ) ) {
100+ // Reset regex for actual processing
101+ htmlBlockRegex . lastIndex = 0 ;
102+
103+ let lastIndex = 0 ;
104+ let match ;
105+
106+ while ( ( match = htmlBlockRegex . exec ( preprocessedMarkdown ) ) !== null ) {
107+ // Process markdown before the HTML block
108+ const markdownBefore = preprocessedMarkdown . slice ( lastIndex , match . index ) . trim ( ) ;
109+ if ( markdownBefore ) {
110+ const parsed = await marked . parse ( markdownBefore ) ;
111+ parsedMarkdown += parsed ;
112+ }
113+
114+ // Process the HTML block: parse markdown content inside while preserving HTML structure
115+ let htmlBlock = match [ 1 ] ;
116+
117+ // Find and process markdown content inside HTML tags
118+ const contentRegex = / > ( \s * [ \s \S ] * ?) \s * < / g;
119+ const contentMatches = [ ] ;
120+ let contentMatch ;
121+
122+ while ( ( contentMatch = contentRegex . exec ( htmlBlock ) ) !== null ) {
123+ const content = contentMatch [ 1 ] ;
124+ // Only process content that has markdown formatting but no extension syntax
125+ if ( content . trim ( ) && ! content . includes ( '{{' ) && ( content . includes ( '**' ) || content . includes ( '- ' ) || content . includes ( '\n' ) ) ) {
126+ // This looks like markdown content without extensions - parse it as block content
127+ const parsedContent = await marked . parse ( content . trim ( ) ) ;
128+ // Remove wrapping <p> tags if they exist since we're inside HTML already
129+ const cleanedContent = parsedContent . replace ( / ^ < p [ ^ > ] * > ( [ \s \S ] * ) < \/ p > [ \s ] * $ / g, '$1' ) ;
130+ contentMatches . push ( {
131+ original : contentMatch [ 0 ] ,
132+ replacement : `>${ cleanedContent } <`
133+ } ) ;
134+ }
135+ }
136+
137+ // Apply the content replacements
138+ contentMatches . forEach ( ( { original, replacement } ) => {
139+ htmlBlock = htmlBlock . replace ( original , replacement ) ;
140+ } ) ;
141+
142+ // Apply extensions (like admonitions) to the processed HTML block
143+ if ( extensions ) {
144+ extensions . forEach ( ( { regex, replace } ) => {
145+ if ( regex ) {
146+ htmlBlock = htmlBlock . replace ( regex , replace ) ;
147+ }
148+ } ) ;
149+ }
150+
151+ parsedMarkdown += htmlBlock ;
152+ lastIndex = htmlBlockRegex . lastIndex ;
153+ }
154+
155+ // Process any remaining markdown after the last HTML block
156+ const markdownAfter = preprocessedMarkdown . slice ( lastIndex ) . trim ( ) ;
157+ if ( markdownAfter ) {
158+ const parsed = await marked . parse ( markdownAfter ) ;
159+ parsedMarkdown += parsed ;
160+ }
161+ } else {
162+ // No HTML blocks found, process normally
163+ parsedMarkdown = await marked . parse ( preprocessedMarkdown ) ;
164+ }
89165 // Swap the temporary tokens back to code fences before we run the extensions
90166 let md = parsedMarkdown . replace ( / @ @ @ / g, '```' ) ;
91167
92168 if ( extensions ) {
93169 // Convert code spans back to md format before we run the custom extension regexes
94170 md = md . replace ( / < c o d e > ( .* ) < \/ c o d e > / g, '`$1`' ) ;
95171
96- extensions . forEach ( ( { regex, replace } ) => {
172+ extensions . forEach ( ( { regex, replace } , index ) => {
97173 if ( regex ) {
98174 md = md . replace ( regex , replace ) ;
99175 }
100176 } ) ;
101177
102178 // Convert any remaining backticks back into code spans
103179 md = md . replace ( / ` ( .* ) ` / g, '<code>$1</code>' ) ;
104- }
180+ }
181+
105182 return DOMPurify . sanitize ( md ) ;
106183} ;
107184
@@ -210,7 +287,7 @@ const InlineMarkdownView: FC<InnerSyncMarkdownProps> = ({
210287 const id = useMemo ( ( ) => uniqueId ( 'markdown' ) , [ ] ) ;
211288 return (
212289 < div className = { css ( { 'is-empty' : isEmpty } as any , className ) } id = { id } >
213- < div dangerouslySetInnerHTML = { { __html : markup } } />
290+ < div style = { { marginBlockEnd : 'var(--pf-t-global--spacer--md)' } } dangerouslySetInnerHTML = { { __html : markup } } />
214291 { renderExtension && (
215292 < RenderExtension renderExtension = { renderExtension } selector = { `#${ id } ` } markup = { markup } />
216293 ) }
0 commit comments