11import * as React from 'react' ;
22import { css } from '@patternfly/react-styles' ;
3- import { Converter } from 'showdown ' ;
3+ import { marked } from 'marked ' ;
44import { useForceRender } from '@console/shared' ;
55import { QuickStartContext , QuickStartContextValues } from '../../utils/quick-start-context' ;
66import './_markdown-view.scss' ;
@@ -16,18 +16,7 @@ type ShowdownExtension = {
1616 replace ?: ( ...args : any [ ] ) => string ;
1717} ;
1818
19- export const markdownConvert = ( markdown , extensions ?: ShowdownExtension [ ] ) => {
20- const converter = new Converter ( {
21- tables : true ,
22- openLinksInNewWindow : true ,
23- strikethrough : true ,
24- emoji : false ,
25- } ) ;
26-
27- if ( extensions ) {
28- converter . addExtension ( extensions ) ;
29- }
30-
19+ export const markdownConvert = async ( markdown : string , extensions ?: ShowdownExtension [ ] ) => {
3120 DOMPurify . addHook ( 'beforeSanitizeElements' , function ( node ) {
3221 // nodeType 1 = element type
3322
@@ -62,40 +51,26 @@ export const markdownConvert = (markdown, extensions?: ShowdownExtension[]) => {
6251 }
6352 } ) ;
6453
65- return DOMPurify . sanitize ( converter . makeHtml ( markdown ) , {
66- USE_PROFILES : {
67- html : true ,
68- svg : true ,
69- } ,
70- // ALLOWED_TAGS: [
71- // 'b',
72- // 'i',
73- // 'strike',
74- // 's',
75- // 'del',
76- // 'em',
77- // 'strong',
78- // 'a',
79- // 'p',
80- // 'h1',
81- // 'h2',
82- // 'h3',
83- // 'h4',
84- // 'ul',
85- // 'ol',
86- // 'li',
87- // 'code',
88- // 'pre',
89- // 'button',
90- // ...tableTags,
91- // 'div',
92- // 'img',
93- // 'span',
94- // 'svg',
95- // ],
96- // ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'src', 'alt', 'id'],
97- // ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|didact):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i,
98- } ) ;
54+ // Replace code fences with non markdown formatting relates tokens so that marked doesn't try to parse them as code spans
55+ const markdownWithSubstitutedCodeFences = markdown . replace ( / ` ` ` / g, '@@@' ) ;
56+ const parsedMarkdown = await marked . parse ( markdownWithSubstitutedCodeFences ) ;
57+ // Swap the temporary tokens back to code fences before we run the extensions
58+ let md = parsedMarkdown . replace ( / @ @ @ / g, '```' ) ;
59+
60+ if ( extensions ) {
61+ // Convert code spans back to md format before we run the custom extension regexes
62+ md = md . replace ( / < c o d e > ( .* ) < \/ c o d e > / g, '`$1`' ) ;
63+
64+ extensions . forEach ( ( { regex, replace } ) => {
65+ if ( regex ) {
66+ md = md . replace ( regex , replace ) ;
67+ }
68+ } ) ;
69+
70+ // Convert any remaining backticks back into code spans
71+ md = md . replace ( / ` ( .* ) ` / g, '<code>$1</code>' ) ;
72+ }
73+ return DOMPurify . sanitize ( md ) ;
9974} ;
10075
10176type SyncMarkdownProps = {
@@ -126,9 +101,18 @@ export const SyncMarkdownView: React.FC<SyncMarkdownProps> = ({
126101 className,
127102} ) => {
128103 const { getResource } = React . useContext < QuickStartContextValues > ( QuickStartContext ) ;
129- const markup = React . useMemo ( ( ) => {
130- return markdownConvert ( content || emptyMsg || getResource ( 'Not available' ) , extensions ) ;
131- } , [ content , emptyMsg , extensions , getResource ] ) ;
104+ const [ markup , setMarkup ] = React . useState < string > ( '' ) ;
105+
106+ React . useEffect ( ( ) => {
107+ async function getMd ( ) {
108+ const md = await markdownConvert (
109+ content || emptyMsg || getResource ( 'Not available' ) ,
110+ extensions ,
111+ ) ;
112+ setMarkup ( md ) ;
113+ }
114+ getMd ( ) ;
115+ } , [ content , emptyMsg , getResource , extensions ] ) ;
132116 const innerProps : InnerSyncMarkdownProps = {
133117 renderExtension : extensions ?. length > 0 ? renderExtension : undefined ,
134118 exactHeight,
0 commit comments