1- import { convertHtmlToReact , type ParserOptions } from '@hedgedoc/html-to-react' ;
2- import { createElement , Fragment , type ReactElement } from 'react' ;
1+ import { convertHtmlToReact , type ParserOptions as HTMLToReactParserOptions } from '@hedgedoc/html-to-react' ;
2+ import { createElement , Fragment , isValidElement , type ReactElement , type ReactNode } from 'react' ;
33
4- type ReactChild = ReactElement | string | null ;
4+ export interface ParserOptions extends HTMLToReactParserOptions {
5+ tailwind ?: boolean | 'data' | 'class' ;
6+ }
57
6- function props ( input : { children ?: ReactChild | ReactChild [ ] } ) {
7- const props = { ... input } ;
8+ class Transformer {
9+ private options : ParserOptions & { tailwind : boolean | 'data' | 'class' } ;
810
9- if ( 'children' in props ) {
10- const { children } = props ;
11- if ( typeof children === 'undefined' || children === null ) {
12- delete props . children ;
13- } else if ( typeof children === 'string' ) {
14- props . children = children ;
15- } else if ( Array . isArray ( children ) ) {
16- const filtered = children
17- . filter ( ( child ) => typeof child === 'string' || Boolean ( child ) )
18- . map ( ( child ) => ( typeof child === 'object' && child ? element ( child ) : child ) ) ;
11+ constructor ( options : ParserOptions = { } ) {
12+ this . options = { tailwind : false , ...options } ;
13+ }
1914
20- if ( filtered . length === 0 ) {
15+ transform ( html : string ) : ReactElement {
16+ return this . wrapper ( convertHtmlToReact ( html ) ) ;
17+ }
18+
19+ wrapper ( children : ( ReactElement | string | null ) [ ] ) : ReactElement {
20+ if ( children . length === 1 && isValidElement ( children [ 0 ] ) ) {
21+ return this . element ( children [ 0 ] ) ;
22+ }
23+
24+ return this . fragment ( children ) ;
25+ }
26+
27+ element ( element : ReactElement ) {
28+ return createElement ( element . type , typeof element . props === 'object' && element . props ? this . props ( element . props ) : { } ) ;
29+ }
30+
31+ fragment ( children : ReactNode ) : ReactElement {
32+ const transformed = this . children ( children ) ;
33+ return createElement ( Fragment , typeof transformed !== 'undefined' ? { children : transformed } : { } ) ;
34+ }
35+
36+ props ( input : { children ?: ReactNode ; className ?: unknown ; tw ?: unknown ; 'data-tw' ?: unknown } ) {
37+ const props = { ...input } ;
38+
39+ if ( 'children' in props ) {
40+ const transformed = this . children ( props . children ) ;
41+ if ( typeof transformed === 'undefined' ) {
2142 delete props . children ;
22- } else if ( filtered . length === 1 && typeof filtered [ 0 ] === 'string' ) {
23- props . children = filtered [ 0 ] ;
2443 } else {
25- props . children = filtered ;
44+ props . children = transformed ;
2645 }
2746 }
28- }
2947
30- return props ;
31- }
48+ if ( this . options . tailwind === true || this . options . tailwind === 'class' ) {
49+ if ( 'className' in props && typeof props . className === 'string' ) {
50+ props . tw = props . className ;
51+ }
52+ } else if ( this . options . tailwind === 'data' ) {
53+ if ( 'data-tw' in props && typeof props [ 'data-tw' ] === 'string' ) {
54+ props . tw = props [ 'data-tw' ] ;
55+ }
56+ }
3257
33- function element ( element : ReactElement ) {
34- return createElement ( element . type , typeof element . props === 'object' && element . props ? props ( element . props ) : { } ) ;
35- }
58+ return props ;
59+ }
3660
37- function fragment ( children : ReactChild [ ] ) : ReactElement {
38- return createElement ( Fragment , { children } ) ;
39- }
61+ children ( children : ReactNode ) : ReactNode {
62+ if ( children === null || typeof children === 'undefined' || typeof children === 'boolean' ) {
63+ return undefined ;
64+ }
65+ if ( isValidElement ( children ) ) {
66+ return this . element ( children ) ;
67+ }
68+ if ( Array . isArray ( children ) ) {
69+ const filtered : ReactNode [ ] = [ ] ;
4070
41- function wrapper ( children : ReactChild [ ] ) : ReactElement {
42- if ( children . length === 1 && typeof children [ 0 ] === 'object' && children [ 0 ] ) {
43- return element ( { ...children [ 0 ] , key : null } ) ;
44- }
71+ for ( const child of children ) {
72+ if ( child === null || typeof child === 'undefined' || typeof child === 'boolean' ) {
73+ continue ;
74+ }
75+ if ( isValidElement ( child ) ) {
76+ filtered . push ( this . element ( child ) ) ;
77+ } else {
78+ filtered . push ( child ) ;
79+ }
80+ }
4581
46- return element ( fragment ( children ) ) ;
82+ if ( filtered . length === 0 ) {
83+ return undefined ;
84+ }
85+ if ( filtered . length === 1 ) {
86+ return filtered [ 0 ] ;
87+ }
88+ return filtered ;
89+ }
90+ return children ;
91+ }
4792}
4893
4994/**
@@ -54,15 +99,15 @@ function wrapper(children: ReactChild[]): ReactElement {
5499 *
55100 * @returns The {@link ReactElement}
56101 */
57- export const htmlToReact = ( html : string , options ? : ParserOptions ) : ReactElement => {
102+ export const htmlToReact = ( html : string , options : ParserOptions = { } ) : ReactElement => {
58103 if ( typeof html !== 'string' ) {
59104 throw new TypeError ( 'Argument 1 must be of type string' ) ;
60105 }
61106 if ( html . trim ( ) . length === 0 ) {
62107 throw new TypeError ( 'Blank html string cannot be parsed' ) ;
63108 }
64109
65- return wrapper ( convertHtmlToReact ( html , options ) ) ;
110+ return new Transformer ( options ) . transform ( html ) ;
66111} ;
67112
68- export { htmlToReact as t , type ParserOptions } ;
113+ export { htmlToReact as t } ;
0 commit comments