Add react rendering.
Showing
2 changed files
with
127 additions
and
0 deletions
lib/react.jsx
0 → 100644
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": { | ... | ... |
-
Please register or sign in to post a comment