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' ...@@ -2,7 +2,12 @@ import { configureStore } from '@reduxjs/toolkit'
2 import { createSlice } from '@reduxjs/toolkit' 2 import { createSlice } from '@reduxjs/toolkit'
3 import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' 3 import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
4 4
5 import { parseHtml } from './html.js' 5 import { parseHtml } from './html.ts'
6
7 interface SiteConfig {
8 name: string,
9 baseUrl: string,
10 }
6 11
7 export const configSlice = createSlice({ 12 export const configSlice = createSlice({
8 name: 'config', 13 name: 'config',
...@@ -30,7 +35,7 @@ const siteBaseQuery = async (args, api, options) => { ...@@ -30,7 +35,7 @@ const siteBaseQuery = async (args, api, options) => {
30 } 35 }
31 36
32 export const sitePageSlice = createApi({ 37 export const sitePageSlice = createApi({
33 reducerPath: 'moqui', 38 reducerPath: 'pages',
34 tagTypes: ['Page'], 39 tagTypes: ['Page'],
35 keepUnusedDataFor: 60, 40 keepUnusedDataFor: 60,
36 refetchOnReconnect: true, 41 refetchOnReconnect: true,
...@@ -65,17 +70,16 @@ export const store = configureStore({ ...@@ -65,17 +70,16 @@ export const store = configureStore({
65 ]), 70 ]),
66 }) 71 })
67 72
68 export const getSitePage = async (site, page) => { 73 export const getSitePage = async (site: string, page: string) => {
69 const result = await store.dispatch(sitePageSlice.endpoints.getPage.initiate({ site, page })) 74 const result = await store.dispatch(sitePageSlice.endpoints.getPage.initiate({ site, page }))
70 if (result.data) result.doc = parseHtml(result.data)
71 return result 75 return result
72 } 76 }
73 77
74 export const setSiteConfig = (siteDef) => { 78 export const setSiteConfig = (siteConfig: SiteConfig) => {
75 return store.dispatch(configSlice.actions.setSiteConfig(siteDef)) 79 return store.dispatch(configSlice.actions.setSiteConfig(siteConfig))
76 } 80 }
77 81
78 export const getSiteBaseUrl = (name) => { 82 export const getSiteBaseUrl = (name: string): string => {
79 return configSlice.selectors.getSiteBaseUrl(store.getState(), name) 83 return configSlice.selectors.getSiteBaseUrl(store.getState(), name)
80 } 84 }
81 85
......
1 import Children from './children.astro' 1 import Children from './children.astro'
2 import Custom from './custom.astro' 2 import Custom from './custom.astro'
3 import Render from './render.astro' 3 import Match from './match.astro'
4 import Node from './node.astro'
4 import Replace from './replace.astro' 5 import Replace from './replace.astro'
5 6
6 export { Children, Custom, Render, Replace } 7 export { Children, Custom, Match, Node, Replace }
......
1 --- 1 ---
2 import Render, { slotPassThrough } from './render.astro' 2 import Match from './match.astro'
3 3
4 const { props: { debug, parent, children, replacers, slotHandler } } = Astro 4 const { props: { parent, children, debug = false, replacers, slotHandler } } = Astro
5
6 const slotCallback = slotPassThrough(Astro)
7 5
8 //console.log('Children:render', { parent, children, replacers }) 6 //console.log('Children:render', { parent, children, replacers })
9 --- 7 ---
10 { 8 {
11 Array.isArray(children) ? 9 Array.isArray(children) ?
12 children.map((child, index) => <Render debug={debug} parent={parent} node={child} index={index} replacers={replacers} slotHandler={slotHandler}/>) 10 children.map((child, index) => <Match parent={parent} node={child} index={index} debug={debug} replacers={replacers} slotHandler={slotHandler}/>)
13 : !children ? '' 11 : !children ? ''
14 : <Render debug={debug} parent={parent} node={children} index={0} replacers={replacers} slotHandler={slotHandler}/> 12 : <Match parent={parent} node={children} index={0} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
15 } 13 }
......
1 --- 1 ---
2 2
3 import { slotPassThrough } from './render.astro'
4 import Children from './children.astro' 3 import Children from './children.astro'
5 4
6 const { props: { wrap = false, wrapAttributes = {}, node, replacers, slotHandler } } = Astro 5 const { props: { wrap = false, wrapAttributes = {}, node, replacers, slotHandler } } = Astro
7 const { name: Name, attributes, children } = node 6 const { name: Name, attributes, children } = node
8 7
9 const CustomName = `custom-${Name}` 8 const CustomName = `custom-${Name}`
10 const slotCallback = slotPassThrough(Astro)
11 9
12 //console.log('Got custom match', node) 10 //console.log('Got custom match', node)
13 --- 11 ---
......
1 import nodeUtil from 'util' 1 import nodeUtil from 'util'
2 import NodeCache from 'node-cache' 2 import NodeCache from 'node-cache'
3 3
4 import type { NodeType } from 'ultrahtml'
4 import { 5 import {
5 parse as umParse, 6 parse as ultraParse,
6 render as umRender,
7 transform as umTransform,
8 __unsafeHTML,
9 ELEMENT_NODE, 7 ELEMENT_NODE,
10 TEXT_NODE, 8 TEXT_NODE,
11 walkSync, 9 walkSync,
12 } from 'ultrahtml' 10 } from 'ultrahtml'
13 11
12 import type { AST } from 'parsel-js'
14 import { 13 import {
15 parse as elParse, 14 parse as elParse,
16 specificity as getSpecificity, 15 specificity as getSpecificity,
...@@ -19,21 +18,37 @@ import { ...@@ -19,21 +18,37 @@ import {
19 18
20 import { parsedHtmlCache, selectorCache, findNodeCache } from './cache.js' 19 import { parsedHtmlCache, selectorCache, findNodeCache } from './cache.js'
21 20
21 export const fixAttributes = (attributes, options = { mapClassname: true }) => {
22 const { mapClassname } = options
23 if (!mapClassname) return attributes
24 const { class: className, ...rest } = attributes
25 return { ...rest, className }
26 }
27
28 export const appendClasses = (node, extraClasses) => {
29 if (!node.attributes.class && !extraClasses) return node
30 const { attributes: { class: className, ...attributes } = {} } = node
31 return { ...node, attributes: { ...attributes, class: `${className ? className + ' ' : ''}${extraClasses}` } }
32 }
33
22 // TODO: implement a parent/child/element cache 34 // TODO: implement a parent/child/element cache
23 const filterChildElements = (parent) => parent?.children?.filter(n => n.type === ELEMENT_NODE) || [] 35 const filterChildElements = (parent) => parent?.children?.filter(n => n.type === ELEMENT_NODE) || []
24 const nthChildPos = (node, parent) => filterChildElements(parent).findIndex((child) => child === node); 36 const nthChildPos = (node, parent) => filterChildElements(parent).findIndex((child) => child === node);
25 37
26 const makeNthChildPosMatcher = (ast) => { 38 type Matcher = (context, node, parent, i, debug) => boolean
39 type AttrValueMatch = (string) => string
40
41 const makeNthChildPosMatcher = (ast: AST): Matcher => {
27 const { argument } = ast 42 const { argument } = ast
28 const n = Number(argument) 43 const n = Number(argument)
29 if (!Number.isNaN(n)) { 44 if (!Number.isNaN(n)) {
30 return (context, node, parent, i) => { 45 return (context, node, parent, i, debug) => {
31 return i === n 46 return i === n
32 } 47 }
33 } 48 }
34 switch (argument) { 49 switch (argument) {
35 case 'odd': 50 case 'odd':
36 return (context, node, parent, i) => Math.abs(i % 2) === 1 51 return (context, node, parent, i, debug) => Math.abs(i % 2) === 1
37 case 'even': 52 case 'even':
38 return (context, node, parent, i) => i % 2 === 0 53 return (context, node, parent, i) => i % 2 === 0
39 default: { 54 default: {
...@@ -56,7 +71,7 @@ const makeNthChildPosMatcher = (ast) => { ...@@ -56,7 +71,7 @@ const makeNthChildPosMatcher = (ast) => {
56 } 71 }
57 } 72 }
58 73
59 const getAttrValueMatch = (value, operator = '=', caseSenstive) => { 74 const getAttrValueMatch = (value: string, operator: string = '=', caseSenstive: boolean): AttrValueMatch => {
60 if (value === undefined) return (attrValue) => attrValue !== undefined 75 if (value === undefined) return (attrValue) => attrValue !== undefined
61 const isCaseInsenstive = caseSensitive === 'i' 76 const isCaseInsenstive = caseSensitive === 'i'
62 if (isCaseInsensitive) value = value.toLowerCase() 77 if (isCaseInsensitive) value = value.toLowerCase()
...@@ -78,11 +93,11 @@ const getAttrValueMatch = (value, operator = '=', caseSenstive) => { ...@@ -78,11 +93,11 @@ const getAttrValueMatch = (value, operator = '=', caseSenstive) => {
78 return (attrValue) => false 93 return (attrValue) => false
79 } 94 }
80 95
81 const compileMatcher = (ast, selector) => { 96 const compileMatcher = (ast: AST, selector: string): Matcher => {
82 let counter = 0 97 let counter = 0
83 98
84 const neededContext = [] 99 const neededContext = []
85 const makeMatcher = (ast) => { 100 const makeMatcher = (ast: AST) => {
86 //console.log('makeMatcher', ast) 101 //console.log('makeMatcher', ast)
87 switch (ast.type) { 102 switch (ast.type) {
88 case 'list': { 103 case 'list': {
...@@ -206,7 +221,7 @@ const compileMatcher = (ast, selector) => { ...@@ -206,7 +221,7 @@ const compileMatcher = (ast, selector) => {
206 const nthChildMatcher = makeNthChildPosMatcher(ast) 221 const nthChildMatcher = makeNthChildPosMatcher(ast)
207 return (context, node, parent, i, debug) => { 222 return (context, node, parent, i, debug) => {
208 const pos = nthChildPos(node, parent) + 1 223 const pos = nthChildPos(node, parent) + 1
209 return nthChildMatcher(context, node, parent, pos) 224 return nthChildMatcher(context, node, parent, pos, debug)
210 } 225 }
211 } 226 }
212 default: 227 default:
...@@ -239,25 +254,25 @@ const compileMatcher = (ast, selector) => { ...@@ -239,25 +254,25 @@ const compileMatcher = (ast, selector) => {
239 } 254 }
240 } 255 }
241 256
242 export const createMatcher = (selector) => { 257 export const createMatcher = (selector: string) => {
243 const matcherCreater = selectorCache.get(selector) 258 const matcherCreater = selectorCache.get(selector)
244 if (false && matcher) return matcherCreater() 259 if (false && matcherCreater) return matcherCreater()
245 const ast = elParse(selector) 260 const ast = elParse(selector)
246 //console.log('createMatcher', nodeUtil.inspect({ selector, ast }, { depth: null, colors: true })) 261 console.log('createMatcher', nodeUtil.inspect({ selector, ast }, { depth: null, colors: true }))
247 const newMatcherCreater = compileMatcher(ast, selector) 262 const newMatcherCreater = compileMatcher(ast, selector)
248 selectorCache.set(selector, newMatcherCreater) 263 selectorCache.set(selector, newMatcherCreater)
249 return newMatcherCreater() 264 return newMatcherCreater()
250 } 265 }
251 266
252 export const parseHtml = (html) => { 267 export const parseHtml = (html: string) => {
253 const cached = parsedHtmlCache.get(html) 268 const cached = parsedHtmlCache.get(html)
254 if (cached) return cached 269 if (cached) return cached
255 const doc = umParse(html) 270 const doc = ultraParse(html)
256 parsedHtmlCache.set(html, doc) 271 parsedHtmlCache.set(html, doc)
257 return doc 272 return doc
258 } 273 }
259 274
260 export const findNode = (doc, selector) => { 275 export const findNode = (doc: NodeType, selector: string) => {
261 if (!selector) return doc 276 if (!selector) return doc
262 let docCache = findNodeCache.get(doc) 277 let docCache = findNodeCache.get(doc)
263 if (!docCache) { 278 if (!docCache) {
......
1 --- 1 ---
2 import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml' 2 import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
3 import Children from './children.astro' 3 import Node from './node.astro'
4 4
5 export const slotPassThrough = (Astro) => (slotName, node) => { 5 const { props: { parent = null, node, index = 0, debug = false, replacers, slotHandler } } = Astro
6 console.log('calling slot', { slotName, node, Astro })
7 return Astro.slots.render('default', [ slotName, node ])
8 }
9
10 const { props: { debug = false, parent = null, node, index = 0, replacers, slotHandler } } = Astro
11 const { name: Name, attributes } = node 6 const { name: Name, attributes } = node
12 7
13 if (debug) { 8 if (debug) {
...@@ -22,17 +17,8 @@ for (const [ matcher, handler ] of replacers) { ...@@ -22,17 +17,8 @@ for (const [ matcher, handler ] of replacers) {
22 } 17 }
23 } 18 }
24 19
25 //const slotCallback = slotPassThrough(Astro)
26 --- 20 ---
27 { 21 {
28 slotName ? slotHandler(slotName, node) 22 slotName ? slotHandler(slotName, node)
29 : node.type === TEXT_NODE ? <Fragment set:html={node.value}/> 23 : <Node parent={parent} node={node} index={index} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
30 : node.type === ELEMENT_NODE ? (
31 node.isSelfClosingTag ? <Name {...attributes}/>
32 : <Name {...attributes}>
33 <Children parent={node} children={node.children} replacers={replacers} slotHandler={slotHandler}/>
34 </Name>
35 ) : (
36 <Children parent={node} children={node.children} replacers={replacers} slotHandler={slotHandler}/>
37 )
38 } 24 }
......
1 ---
2 import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
3 import Children from './children.astro'
4
5 const { props: { parent = null, node, index = 0, debug = false, replacers, slotHandler } } = Astro
6 const { name: Name, attributes } = node
7
8 if (debug) {
9 console.log('Node.astro:debug', {node})
10 }
11 ---
12 {
13 node.type === TEXT_NODE ? <Fragment set:html={node.value}/>
14 : node.type === ELEMENT_NODE ? (
15 node.isSelfClosingTag ? <Name {...attributes}/>
16 : <Name {...attributes}>
17 <Children parent={node} children={node.children} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
18 </Name>
19 ) : (
20 <Children parent={node} children={node.children} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
21 )
22 }
1 --- 1 ---
2 import html from '@resources/provider-portal.html?raw' 2 import html from '@resources/provider-portal.html?raw'
3 import { walkSync } from 'ultrahtml' 3 import { walkSync } from 'ultrahtml'
4 import { parseHtml, createMatcher, findNode } from './html.js' 4 import { parseHtml, createMatcher, findNode } from './html.ts'
5 import Render from './render.astro' 5 import Match from './match.astro'
6 6
7 const { props } = Astro 7 const { props } = Astro
8 const { html, mapClassname = true, xpath, replacements = {} } = props 8 const { html, debug = false, xpath, replacements = {} } = props
9 const doc = props.node ? props.node : parseHtml(props.html) 9 const doc = props.node ? props.node : parseHtml(props.html)
10 10
11 const node = xpath ? findNode(doc, xpath) : doc 11 const node = xpath ? findNode(doc, xpath) : doc
12 const replacers = Object.entries(replacements).map(([ selector, handler ]) => [ createMatcher(selector), handler ]) 12 const replacers = Object.entries(replacements).map(([ selector, handler ]) => [ createMatcher(selector), handler ])
13 const fixAttributes = ({ attributes }) => {
14 if (!mapClassname) return attributes
15 const { class: className, ...rest } = attributes
16 return { ...rest, className }
17 }
18 13
19 const slotHandler = (slotName, node) => { 14 const slotHandler = (slotName, node) => {
20 const attributes = fixAttributes(node) 15 return Astro.slots.render(slotName, [ node, { slotHandler, replacers } ] )
21 return Astro.slots.render(slotName, [ { ...node, attributes }, { slotHandler, replacers } ] )
22 } 16 }
23 --- 17 ---
24 <Render node={node} replacers={replacers} slotHandler={slotHandler}/> 18 <Match node={node} debug={debug} replacers={replacers} slotHandler={slotHandler}/>
......
1 --- 1 ---
2 import { getSitePage } from '@lib/api.ts' 2 import { getSitePage } from '@lib/api.ts'
3 import { Replace } from '@lib/astro.ts' 3 import { appendClasses } from '@lib/html.ts'
4 import { Node, Replace } from '@lib/astro.ts'
4 import '@config.ts' 5 import '@config.ts'
5 6
6 const { data: layoutHtml } = await getSitePage('msd', '/') 7 const { data: layoutHtml } = await getSitePage('msd', '/')
7 const layoutReplacements = { 8 const layoutReplacements = {
9 ['body']: 'body',
8 ['.hero-section']: 'content', 10 ['.hero-section']: 'content',
9 ['.content-section']: 'DELETE', 11 ['.content-section']: 'DELETE',
10 ['.content-section-4']: 'DELETE', 12 ['.content-section-4']: 'DELETE',
...@@ -14,7 +16,17 @@ const layoutReplacements = { ...@@ -14,7 +16,17 @@ const layoutReplacements = {
14 //['.navbar.w-nav:nth-of-type(n + 1)']: 'DELETE', 16 //['.navbar.w-nav:nth-of-type(n + 1)']: 'DELETE',
15 } 17 }
16 --- 18 ---
17 <Replace debug={true} html={layoutHtml} replacements={layoutReplacements}> 19 <Replace html={layoutHtml} replacements={layoutReplacements}>
18 <define slot="DELETE">{(node) => ''}</define> 20 <define slot="DELETE">{(node) => ''}</define>
19 <define slot='content'>{(node) => <slot/>}</define> 21 <define slot='body'>{(node, context) => <Node {...context} node={appendClasses(node, 'astro')} parent={node.parent}/>}</define>
22 <define slot='content'>{(node) => <div><slot/></div>}</define>
20 </Replace> 23 </Replace>
24 <style>
25 .astro {}
26 a.w-webflow-badge {
27 display:none;
28 }
29 :global(a.w-webflow-badge) {
30 display:none;
31 }
32 </style>
......