import h2m from 'h2m' ; import { decode } from 'he' ; import { htmlDecode } from 'js-htmlencode' ; import { HTMLElement , parse , TextNode } from 'node-html-parser' ; const noChildren = [ 'strong' , 'b' , 'em' , 'i' , 'u' , 's' ] ; const trimNodes = [ ... noChildren , 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' , 'li' , 'a' ] ; const cleanText = ( node , parentType ) : TextNode => { const text = parentType && trimNodes . includes ( parentType . toLowerCase ( ) ) ? node . rawText . trim ( ) : node . rawText ; return new TextNode ( text ) ; } ; const deepClean = ( node ) : HTMLElement => { const attributes = Object . entries ( node . attributes ) . map ( ( [ key , val ] ) => { if ( key === 'href' && val ) { val = val . replace ( / /g , '%20' ) ; } return `${ key }="${ val }"` ; } ) . join ( ' ' ) ; const cleaned = new HTMLElement ( node . tagName , { } , attributes , node . parentNode ) ; node . childNodes . forEach ( ( childNode ) => { if ( childNode instanceof HTMLElement ) { if ( node . tagName && noChildren . includes ( node . tagName . toLowerCase ( ) ) ) { cleaned . childNodes . push ( cleanText ( new TextNode ( childNode . innerText ) , node . tagName ) ) ; } else { cleaned . childNodes . push ( deepClean ( childNode ) ) ; } } else if ( childNode instanceof TextNode ) { cleaned . childNodes . push ( cleanText ( childNode , node . tagName ) ) ; } } ) ; return cleaned ; } ; const rearrangeWhitespace = ( html = '' ) : string => { let content = html . replace ( /<span[^>]*>/gm , '' ) . split ( '</span>' ) . join ( '' ) . replace ( /<div[^>]*>/gm , '' ) . split ( '</div>' ) . join ( '' ) . split ( '&#160;' ) . join ( '&amp;nbsp;' ) ; trimNodes . forEach ( ( trimNode ) => { content = content . split ( `<${ trimNode }> ` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( ` </${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; content = content . split ( `<${ trimNode }>&amp;nbsp;` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( `&amp;nbsp;</${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; } ) ; trimNodes . forEach ( ( trimNode ) => { content = content . split ( `<${ trimNode }> ` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( ` </${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; content = content . split ( `<${ trimNode }>&amp;nbsp;` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( `&amp;nbsp;</${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; } ) ; trimNodes . forEach ( ( trimNode ) => { content = content . split ( `<${ trimNode }> ` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( ` </${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; content = content . split ( `<${ trimNode }>&amp;nbsp;` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( `&amp;nbsp;</${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; } ) ; trimNodes . forEach ( ( trimNode ) => { content = content . split ( `<${ trimNode }> ` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( ` </${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; content = content . split ( `<${ trimNode }>&amp;nbsp;` ) . join ( ` <${ trimNode }>` ) ; content = content . split ( `&amp;nbsp;</${ trimNode }>` ) . join ( `</${ trimNode }> ` ) ; } ) ; return content ; } ; export const clean = ( html = '' ) : string => deepClean ( parse ( decode ( html ) ) ) . outerHTML ; interface Node { name : string ; attrs : { [ key ] : string ; } ; isInPreNode : boolean ; md : string ; } const converter = 'MarkdownExtra' ; const overides = { a : ( node ) => `[${ node . md }](${ node . attrs . href })` , img : ( node ) => `![${ node . attrs . title || '' }](${ node . attrs . src })` , i : ( node ) => `*${ node . md }*` , b : ( node ) => `**${ node . md }**` , h1 : ( node ) => `# ${ node . md }\n` , h2 : ( node ) => `## ${ node . md }\n` , h3 : ( node ) => `### ${ node . md }\n` , h4 : ( node ) => `#### ${ node . md }\n` , h5 : ( node ) => `##### ${ node . md }\n` , h6 : ( node ) => `###### ${ node . md }\n` , } ; export const toMarkdown = ( html ? ) : string => { if ( html ?. length == 0 ) return '' ; const rearranged = rearrangeWhitespace ( html ) ; const trimmed = clean ( rearranged ) ; const markdown = h2m ( trimmed , { overides , converter } ) ; const decoded = htmlDecode ( markdown ) ; return decoded ; } ;