2b0bc0ee by Adam Heath

Many updates, working on adding typescript, and direct node rendering.

1 parent 2faeafaa
......@@ -2,7 +2,12 @@ import { configureStore } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { parseHtml } from './html.js'
import { parseHtml } from './html.ts'
interface SiteConfig {
name: string,
baseUrl: string,
}
export const configSlice = createSlice({
name: 'config',
......@@ -30,7 +35,7 @@ const siteBaseQuery = async (args, api, options) => {
}
export const sitePageSlice = createApi({
reducerPath: 'moqui',
reducerPath: 'pages',
tagTypes: ['Page'],
keepUnusedDataFor: 60,
refetchOnReconnect: true,
......@@ -65,17 +70,16 @@ export const store = configureStore({
]),
})
export const getSitePage = async (site, page) => {
export const getSitePage = async (site: string, page: string) => {
const result = await store.dispatch(sitePageSlice.endpoints.getPage.initiate({ site, page }))
if (result.data) result.doc = parseHtml(result.data)
return result
}
export const setSiteConfig = (siteDef) => {
return store.dispatch(configSlice.actions.setSiteConfig(siteDef))
export const setSiteConfig = (siteConfig: SiteConfig) => {
return store.dispatch(configSlice.actions.setSiteConfig(siteConfig))
}
export const getSiteBaseUrl = (name) => {
export const getSiteBaseUrl = (name: string): string => {
return configSlice.selectors.getSiteBaseUrl(store.getState(), name)
}
......
import Children from './children.astro'
import Custom from './custom.astro'
import Render from './render.astro'
import Match from './match.astro'
import Node from './node.astro'
import Replace from './replace.astro'
export { Children, Custom, Render, Replace }
export { Children, Custom, Match, Node, Replace }
......
---
import Render, { slotPassThrough } from './render.astro'
import Match from './match.astro'
const { props: { debug, parent, children, replacers, slotHandler } } = Astro
const slotCallback = slotPassThrough(Astro)
const { props: { parent, children, debug = false, replacers, slotHandler } } = Astro
//console.log('Children:render', { parent, children, replacers })
---
{
Array.isArray(children) ?
children.map((child, index) => <Render debug={debug} parent={parent} node={child} index={index} replacers={replacers} slotHandler={slotHandler}/>)
children.map((child, index) => <Match parent={parent} node={child} index={index} debug={debug} replacers={replacers} slotHandler={slotHandler}/>)
: !children ? ''
: <Render debug={debug} parent={parent} node={children} index={0} replacers={replacers} slotHandler={slotHandler}/>
: <Match parent={parent} node={children} index={0} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
}
......
---
import { slotPassThrough } from './render.astro'
import Children from './children.astro'
const { props: { wrap = false, wrapAttributes = {}, node, replacers, slotHandler } } = Astro
const { name: Name, attributes, children } = node
const CustomName = `custom-${Name}`
const slotCallback = slotPassThrough(Astro)
//console.log('Got custom match', node)
---
......
import nodeUtil from 'util'
import NodeCache from 'node-cache'
import type { NodeType } from 'ultrahtml'
import {
parse as umParse,
render as umRender,
transform as umTransform,
__unsafeHTML,
parse as ultraParse,
ELEMENT_NODE,
TEXT_NODE,
walkSync,
} from 'ultrahtml'
import type { AST } from 'parsel-js'
import {
parse as elParse,
specificity as getSpecificity,
......@@ -19,21 +18,37 @@ import {
import { parsedHtmlCache, selectorCache, findNodeCache } from './cache.js'
export const fixAttributes = (attributes, options = { mapClassname: true }) => {
const { mapClassname } = options
if (!mapClassname) return attributes
const { class: className, ...rest } = attributes
return { ...rest, className }
}
export const appendClasses = (node, extraClasses) => {
if (!node.attributes.class && !extraClasses) return node
const { attributes: { class: className, ...attributes } = {} } = node
return { ...node, attributes: { ...attributes, class: `${className ? className + ' ' : ''}${extraClasses}` } }
}
// TODO: implement a parent/child/element cache
const filterChildElements = (parent) => parent?.children?.filter(n => n.type === ELEMENT_NODE) || []
const nthChildPos = (node, parent) => filterChildElements(parent).findIndex((child) => child === node);
const makeNthChildPosMatcher = (ast) => {
type Matcher = (context, node, parent, i, debug) => boolean
type AttrValueMatch = (string) => string
const makeNthChildPosMatcher = (ast: AST): Matcher => {
const { argument } = ast
const n = Number(argument)
if (!Number.isNaN(n)) {
return (context, node, parent, i) => {
return (context, node, parent, i, debug) => {
return i === n
}
}
switch (argument) {
case 'odd':
return (context, node, parent, i) => Math.abs(i % 2) === 1
return (context, node, parent, i, debug) => Math.abs(i % 2) === 1
case 'even':
return (context, node, parent, i) => i % 2 === 0
default: {
......@@ -56,7 +71,7 @@ const makeNthChildPosMatcher = (ast) => {
}
}
const getAttrValueMatch = (value, operator = '=', caseSenstive) => {
const getAttrValueMatch = (value: string, operator: string = '=', caseSenstive: boolean): AttrValueMatch => {
if (value === undefined) return (attrValue) => attrValue !== undefined
const isCaseInsenstive = caseSensitive === 'i'
if (isCaseInsensitive) value = value.toLowerCase()
......@@ -78,11 +93,11 @@ const getAttrValueMatch = (value, operator = '=', caseSenstive) => {
return (attrValue) => false
}
const compileMatcher = (ast, selector) => {
const compileMatcher = (ast: AST, selector: string): Matcher => {
let counter = 0
const neededContext = []
const makeMatcher = (ast) => {
const makeMatcher = (ast: AST) => {
//console.log('makeMatcher', ast)
switch (ast.type) {
case 'list': {
......@@ -206,7 +221,7 @@ const compileMatcher = (ast, selector) => {
const nthChildMatcher = makeNthChildPosMatcher(ast)
return (context, node, parent, i, debug) => {
const pos = nthChildPos(node, parent) + 1
return nthChildMatcher(context, node, parent, pos)
return nthChildMatcher(context, node, parent, pos, debug)
}
}
default:
......@@ -239,25 +254,25 @@ const compileMatcher = (ast, selector) => {
}
}
export const createMatcher = (selector) => {
export const createMatcher = (selector: string) => {
const matcherCreater = selectorCache.get(selector)
if (false && matcher) return matcherCreater()
if (false && matcherCreater) return matcherCreater()
const ast = elParse(selector)
//console.log('createMatcher', nodeUtil.inspect({ selector, ast }, { depth: null, colors: true }))
console.log('createMatcher', nodeUtil.inspect({ selector, ast }, { depth: null, colors: true }))
const newMatcherCreater = compileMatcher(ast, selector)
selectorCache.set(selector, newMatcherCreater)
return newMatcherCreater()
}
export const parseHtml = (html) => {
export const parseHtml = (html: string) => {
const cached = parsedHtmlCache.get(html)
if (cached) return cached
const doc = umParse(html)
const doc = ultraParse(html)
parsedHtmlCache.set(html, doc)
return doc
}
export const findNode = (doc, selector) => {
export const findNode = (doc: NodeType, selector: string) => {
if (!selector) return doc
let docCache = findNodeCache.get(doc)
if (!docCache) {
......
---
import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
import Children from './children.astro'
import Node from './node.astro'
export const slotPassThrough = (Astro) => (slotName, node) => {
console.log('calling slot', { slotName, node, Astro })
return Astro.slots.render('default', [ slotName, node ])
}
const { props: { debug = false, parent = null, node, index = 0, replacers, slotHandler } } = Astro
const { props: { parent = null, node, index = 0, debug = false, replacers, slotHandler } } = Astro
const { name: Name, attributes } = node
if (debug) {
......@@ -22,17 +17,8 @@ for (const [ matcher, handler ] of replacers) {
}
}
//const slotCallback = slotPassThrough(Astro)
---
{
slotName ? slotHandler(slotName, node)
: node.type === TEXT_NODE ? <Fragment set:html={node.value}/>
: node.type === ELEMENT_NODE ? (
node.isSelfClosingTag ? <Name {...attributes}/>
: <Name {...attributes}>
<Children parent={node} children={node.children} replacers={replacers} slotHandler={slotHandler}/>
</Name>
) : (
<Children parent={node} children={node.children} replacers={replacers} slotHandler={slotHandler}/>
)
: <Node parent={parent} node={node} index={index} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
}
......
---
import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
import Children from './children.astro'
const { props: { parent = null, node, index = 0, debug = false, replacers, slotHandler } } = Astro
const { name: Name, attributes } = node
if (debug) {
console.log('Node.astro:debug', {node})
}
---
{
node.type === TEXT_NODE ? <Fragment set:html={node.value}/>
: node.type === ELEMENT_NODE ? (
node.isSelfClosingTag ? <Name {...attributes}/>
: <Name {...attributes}>
<Children parent={node} children={node.children} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
</Name>
) : (
<Children parent={node} children={node.children} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
)
}
---
import html from '@resources/provider-portal.html?raw'
import { walkSync } from 'ultrahtml'
import { parseHtml, createMatcher, findNode } from './html.js'
import Render from './render.astro'
import { parseHtml, createMatcher, findNode } from './html.ts'
import Match from './match.astro'
const { props } = Astro
const { html, mapClassname = true, xpath, replacements = {} } = props
const { html, debug = false, xpath, replacements = {} } = props
const doc = props.node ? props.node : parseHtml(props.html)
const node = xpath ? findNode(doc, xpath) : doc
const replacers = Object.entries(replacements).map(([ selector, handler ]) => [ createMatcher(selector), handler ])
const fixAttributes = ({ attributes }) => {
if (!mapClassname) return attributes
const { class: className, ...rest } = attributes
return { ...rest, className }
}
const slotHandler = (slotName, node) => {
const attributes = fixAttributes(node)
return Astro.slots.render(slotName, [ { ...node, attributes }, { slotHandler, replacers } ] )
return Astro.slots.render(slotName, [ node, { slotHandler, replacers } ] )
}
---
<Render node={node} replacers={replacers} slotHandler={slotHandler}/>
<Match node={node} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
......
---
import { getSitePage } from '@lib/api.ts'
import { Replace } from '@lib/astro.ts'
import { appendClasses } from '@lib/html.ts'
import { Node, Replace } from '@lib/astro.ts'
import '@config.ts'
const { data: layoutHtml } = await getSitePage('msd', '/')
const layoutReplacements = {
['body']: 'body',
['.hero-section']: 'content',
['.content-section']: 'DELETE',
['.content-section-4']: 'DELETE',
......@@ -14,7 +16,17 @@ const layoutReplacements = {
//['.navbar.w-nav:nth-of-type(n + 1)']: 'DELETE',
}
---
<Replace debug={true} html={layoutHtml} replacements={layoutReplacements}>
<Replace html={layoutHtml} replacements={layoutReplacements}>
<define slot="DELETE">{(node) => ''}</define>
<define slot='content'>{(node) => <slot/>}</define>
<define slot='body'>{(node, context) => <Node {...context} node={appendClasses(node, 'astro')} parent={node.parent}/>}</define>
<define slot='content'>{(node) => <div><slot/></div>}</define>
</Replace>
<style>
.astro {}
a.w-webflow-badge {
display:none;
}
:global(a.w-webflow-badge) {
display:none;
}
</style>
......