Many updates, working on adding typescript, and direct node rendering.
Showing
9 changed files
with
95 additions
and
65 deletions
... | @@ -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 | } | ... | ... |
lib/node.astro
0 → 100644
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> | ... | ... |
-
Please register or sign in to post a comment