react.jsx 4.82 KB
import React from 'react'

import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
import { parseHtml, createMatcher, findNode } from 'astro-wt/html'
import { decode } from 'html-entities'

export const ReparseStaticChildren = (replacements, Component) => (props) => {
  const { children = {}, ...rest } = props
  const { props: { value: staticHtml } = {} } = children
  if (!staticHtml) return <Component {...props}/>
  const bodyToParse = String(staticHtml)
  const ultra = Replace({
    debug: 2,
    html: bodyToParse,
    replacements,
  })

  return <Component {...rest}>{ultra}</Component>  
}

export const fixAttributes = (attributes) => {
  const { class: className, srcset: srcSet, maxlength: maxLength, for: htmlFor, ...rest } = attributes
  const result = rest
  if (className !== undefined) result.className = className
  if (srcSet !== undefined) result.srcSet = srcSet
  if (maxLength !== undefined) result.maxLength = maxLength
  if (htmlFor !== undefined) result.htmlFor = htmlFor
  return result
  return Object.fromEntries(Object.entries(result).map(([ key, value ]) => {
    return [ key, decode(value) ]
  }))
}

export const CreateReplacement = (Element, propsOrCreator) => (matchOptions) => {
  const { debug, parent, node, index, replacers } = matchOptions
  const props = propsOrCreator instanceof Function ? propsOrCreator(matchOptions) : propsOrCreator
  return (
    <Element key={index} {...fixAttributes({...node.attributes, ...props})}>
      {Children({ debug, parent, children: node.children, replacers})}
    </Element>
  )
}

export const DeleteHandler = () => null

export const Replace = (props) => {
  const { debug = 0, html, xpath, replacements } = props
  const doc = props.node ? props.node : parseHtml(html, { react: false })
  const node = xpath ? findNode(doc, xpath) : doc
  const replacers = Object.entries(replacements).map(([ selector, Handler ]) => [ createMatcher(selector), Handler ])
  const nextDebug = debug ? debug - 1 : 0
  if (debug) console.log('Replace', { html, replacers, node })
  const result = Match({ debug: nextDebug, node, replacers })
  if (debug) console.log('Replace result', result)
  return result
}

export const Match = (props) => {
  const { debug = 0, parent = null, node, index = 0, replacers } = props
  const nextDebug = debug ? debug - 1 : 0
  for (const [ matcher, Handler ] of replacers) {
    if (matcher(node, parent, index, false)) {
      return Handler({ debug: nextDebug, parent, node, index, replacers })
    }
  }
  if (debug) console.log('Match:nothing', {node}) 
  return Node({ debug: nextDebug, parent, node, index, replacers })
}

export const RAW_TAGS = new Set(['script', 'style'])

export const Node = (props) => {
  const { debug = 0, parent, node, index, replacers } = props
  const { name: Name, attributes } = node
  const nextDebug = debug ? debug - 1 : 0
  if (node.type === TEXT_NODE) {
    if (debug) console.log('Node:text', { value: node.value })
    return node.value ? decode(node.value) : null
    //return <React.Fragment dangerouslySetInnerHTML={{ __html: node.value }}/>
  } else if (node.type === ELEMENT_NODE) {
    const elementProps = fixAttributes(attributes)
    if (node.isSelfClosingTag) {
      if (debug) console.log('Node:selfClosing', { node })
      return <Name key={index} {...elementProps}/>
    } else {
      if (debug) console.log('Node:element', { node, children: node.children })
      let children = node.children
      if (children.length === 1) {
        const [ firstChild ] = children
        if (firstChild.type === TEXT_NODE && !firstChild.value) children = null
      }
      if (Name === 'astro-island') console.log('astro-island', { attributes, elementProps })
      if (Name === 'script') console.log('script', { node, children })
      if (RAW_TAGS.has(Name) && children) {
        if (Array.isArray(children) && children.length === 1) {
          return <Name key={index} {...elementProps} dangerouslySetInnerHTML={{ __html: children[ 0 ].value } }/>
        }
      }
      return (
        <Name key={index} {...elementProps} children={children && Children({ debug: nextDebug, parent: node, children: node.children, replacers})}/>
      )
    }
  } else {
    if (debug) console.log('Node:children', { children: node.children })
    return Children({ debug: nextDebug, parent: node, children: node.children, replacers })
  }
}

export const Children = (props) => {
  const { debug = 0, parent, children, replacers } = props
  const nextDebug = debug ? debug - 1 : 0
  if (Array.isArray(children)) {
    const result = children.map((child, index) => {
      return Match({ debug: nextDebug, parent, node: child, index, replacers })
    })
    if (result.length === 1) return result[0]
    if (result.length === 0) return null
    return result
  } else if (!children) {
    return ''
  } else {
    return Node({ debug: nextDebug, parent, node: children, index: 0, replacers })
  }
}