9bb9c3cc by Adam Heath

Add react rendering.

1 parent e01f913d
1 import React from 'react'
2
3 import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
4 import { parseHtml, createMatcher, findNode } from 'astro-wt/html'
5 import { decode } from 'html-entities'
6
7 export const ReparseStaticChildren = (replacements, Component) => (props) => {
8 const { children = {}, ...rest } = props
9 const { props: { value: staticHtml } = {} } = children
10 if (!staticHtml) return <Component {...props}/>
11 const bodyToParse = String(staticHtml)
12 const ultra = Replace({
13 debug: 2,
14 html: bodyToParse,
15 replacements,
16 })
17
18 return <Component {...rest}>{ultra}</Component>
19 }
20
21 export const fixAttributes = (attributes) => {
22 const { class: className, srcset: srcSet, maxlength: maxLength, for: htmlFor, ...rest } = attributes
23 const result = rest
24 if (className !== undefined) result.className = className
25 if (srcSet !== undefined) result.srcSet = srcSet
26 if (maxLength !== undefined) result.maxLength = maxLength
27 if (htmlFor !== undefined) result.htmlFor = htmlFor
28 return result
29 return Object.fromEntries(Object.entries(result).map(([ key, value ]) => {
30 return [ key, decode(value) ]
31 }))
32 }
33
34 export const CreateReplacement = (Element, propsOrCreator) => (matchOptions) => {
35 const { debug, parent, node, index, replacers } = matchOptions
36 const props = propsOrCreator instanceof Function ? propsOrCreator(matchOptions) : propsOrCreator
37 return (
38 <Element key={index} {...fixAttributes({...node.attributes, ...props})}>
39 {Children({ debug, parent, children: node.children, replacers})}
40 </Element>
41 )
42 }
43
44 export const DeleteHandler = () => null
45
46 export const Replace = (props) => {
47 const { debug = 0, html, xpath, replacements } = props
48 const doc = props.node ? props.node : parseHtml(html, { react: false })
49 const node = xpath ? findNode(doc, xpath) : doc
50 const replacers = Object.entries(replacements).map(([ selector, Handler ]) => [ createMatcher(selector), Handler ])
51 const nextDebug = debug ? debug - 1 : 0
52 if (debug) console.log('Replace', { html, replacers, node })
53 const result = Match({ debug: nextDebug, node, replacers })
54 if (debug) console.log('Replace result', result)
55 return result
56 }
57
58 export const Match = (props) => {
59 const { debug = 0, parent = null, node, index = 0, replacers } = props
60 const nextDebug = debug ? debug - 1 : 0
61 for (const [ matcher, Handler ] of replacers) {
62 if (matcher(node, parent, index, false)) {
63 return Handler({ debug: nextDebug, parent, node, index, replacers })
64 }
65 }
66 if (debug) console.log('Match:nothing', {node})
67 return Node({ debug: nextDebug, parent, node, index, replacers })
68 }
69
70 export const RAW_TAGS = new Set(['script', 'style'])
71
72 export const Node = (props) => {
73 const { debug = 0, parent, node, index, replacers } = props
74 const { name: Name, attributes } = node
75 const nextDebug = debug ? debug - 1 : 0
76 if (node.type === TEXT_NODE) {
77 if (debug) console.log('Node:text', { value: node.value })
78 return node.value ? decode(node.value) : null
79 //return <React.Fragment dangerouslySetInnerHTML={{ __html: node.value }}/>
80 } else if (node.type === ELEMENT_NODE) {
81 const elementProps = fixAttributes(attributes)
82 if (node.isSelfClosingTag) {
83 if (debug) console.log('Node:selfClosing', { node })
84 return <Name key={index} {...elementProps}/>
85 } else {
86 if (debug) console.log('Node:element', { node, children: node.children })
87 let children = node.children
88 if (children.length === 1) {
89 const [ firstChild ] = children
90 if (firstChild.type === TEXT_NODE && !firstChild.value) children = null
91 }
92 if (Name === 'astro-island') console.log('astro-island', { attributes, elementProps })
93 if (Name === 'script') console.log('script', { node, children })
94 if (RAW_TAGS.has(Name) && children) {
95 if (Array.isArray(children) && children.length === 1) {
96 return <Name key={index} {...elementProps} dangerouslySetInnerHTML={{ __html: children[ 0 ].value } }/>
97 }
98 }
99 return (
100 <Name key={index} {...elementProps} children={children && Children({ debug: nextDebug, parent: node, children: node.children, replacers})}/>
101 )
102 }
103 } else {
104 if (debug) console.log('Node:children', { children: node.children })
105 return Children({ debug: nextDebug, parent: node, children: node.children, replacers })
106 }
107 }
108
109 export const Children = (props) => {
110 const { debug = 0, parent, children, replacers } = props
111 const nextDebug = debug ? debug - 1 : 0
112 if (Array.isArray(children)) {
113 const result = children.map((child, index) => {
114 return Match({ debug: nextDebug, parent, node: child, index, replacers })
115 })
116 if (result.length === 1) return result[0]
117 if (result.length === 0) return null
118 return result
119 } else if (!children) {
120 return ''
121 } else {
122 return Node({ debug: nextDebug, parent, node: children, index: 0, replacers })
123 }
124 }
125
126
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
4 "exports": { 4 "exports": {
5 "./astro": "./lib/astro.ts", 5 "./astro": "./lib/astro.ts",
6 "./html": "./lib/html.ts", 6 "./html": "./lib/html.ts",
7 "./react": "./lib/react.jsx",
7 "./remote-content": "./lib/remote-content.ts" 8 "./remote-content": "./lib/remote-content.ts"
8 }, 9 },
9 "scripts": { 10 "scripts": {
......