Add some simple instructions.
Showing
21 changed files
with
642 additions
and
0 deletions
README
0 → 100644
astro.config.mjs
0 → 100644
bin/run
0 → 100755
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | set -e | ||
| 4 | |||
| 5 | TOP_DIR="$(cd "$(dirname "$0")/.."; pwd -P)" | ||
| 6 | |||
| 7 | ti_arg="" | ||
| 8 | if [ -t 0 ]; then | ||
| 9 | ti_arg="-ti" | ||
| 10 | fi | ||
| 11 | docker run --rm $ti_arg \ | ||
| 12 | -e UID_REMAP=$(id -u) -e GID_REMAP=$(id -g) \ | ||
| 13 | -v "$TOP_DIR/container-entrypoint.sh:/container-entrypoint.sh" --entrypoint /container-entrypoint.sh \ | ||
| 14 | -w /srv/app \ | ||
| 15 | -v "$TOP_DIR:/srv/app" \ | ||
| 16 | $DOCKER_ARGS \ | ||
| 17 | node "$@" | ||
| 18 | 
container-entrypoint.sh
0 → 100755
| 1 | #!/bin/bash | ||
| 2 | |||
| 3 | set -e | ||
| 4 | if [[ 0 -eq $(id -u) ]]; then | ||
| 5 | [[ $UID_REMAP ]] && usermod -u $UID_REMAP node | ||
| 6 | [[ $GID_REMAP ]] && groupmod -g $GID_REMAP node | ||
| 7 | fi | ||
| 8 | if [[ $# -eq 0 ]]; then | ||
| 9 | set -- bash | ||
| 10 | fi | ||
| 11 | |||
| 12 | cmd="$1" | ||
| 13 | shift | ||
| 14 | start-stop-daemon -c node -d $PWD -u node --start --exec "$(which "$cmd")" -- "$@" | 
lib/api.ts
0 → 100644
| 1 | import { configureStore } from '@reduxjs/toolkit' | ||
| 2 | import { createSlice } from '@reduxjs/toolkit' | ||
| 3 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' | ||
| 4 | |||
| 5 | import { parseHtml } from './html.js' | ||
| 6 | |||
| 7 | export const configSlice = createSlice({ | ||
| 8 | name: 'config', | ||
| 9 | initialState: { | ||
| 10 | sites: {}, | ||
| 11 | }, | ||
| 12 | reducers: { | ||
| 13 | setSiteConfig(state, { payload: { name, baseUrl } }) { | ||
| 14 | if (!state.sites[ name ]) state.sites[ name ] = {} | ||
| 15 | Object.assign(state.sites[ name ], { baseUrl }) | ||
| 16 | } | ||
| 17 | }, | ||
| 18 | selectors: { | ||
| 19 | getSiteBaseUrl: (state, name) => state.sites?.[ name ]?.baseUrl, | ||
| 20 | }, | ||
| 21 | }) | ||
| 22 | |||
| 23 | |||
| 24 | const baseQuery = fetchBaseQuery() | ||
| 25 | |||
| 26 | const siteBaseQuery = async (args, api, options) => { | ||
| 27 | const { site, url } = args | ||
| 28 | const baseUrl = getSiteBaseUrl(site) | ||
| 29 | return baseQuery({ ...args, url: `${baseUrl}/${url}` }, api, options) | ||
| 30 | } | ||
| 31 | |||
| 32 | export const sitePageSlice = createApi({ | ||
| 33 | reducerPath: 'moqui', | ||
| 34 | tagTypes: ['Page'], | ||
| 35 | keepUnusedDataFor: 60, | ||
| 36 | refetchOnReconnect: true, | ||
| 37 | refetchOnMountOrArgChange: true, | ||
| 38 | baseQuery: siteBaseQuery, | ||
| 39 | endpoints: builder => ({ | ||
| 40 | getPage: builder.query({ | ||
| 41 | query: (args) => { | ||
| 42 | const { site, page } = args | ||
| 43 | return { | ||
| 44 | site, | ||
| 45 | url: page, | ||
| 46 | method: 'GET', | ||
| 47 | responseHandler: 'text', | ||
| 48 | } | ||
| 49 | }, | ||
| 50 | providesTags: (result, err, args) => { | ||
| 51 | const { site, page } = args | ||
| 52 | return [ { type: 'Page', id: { site, page } } ] | ||
| 53 | }, | ||
| 54 | }), | ||
| 55 | }), | ||
| 56 | }) | ||
| 57 | |||
| 58 | export const store = configureStore({ | ||
| 59 | reducer: { | ||
| 60 | [ configSlice.reducerPath ]: configSlice.reducer, | ||
| 61 | [ sitePageSlice.reducerPath ]: sitePageSlice.reducer, | ||
| 62 | }, | ||
| 63 | middleware: getDefaultMiddleware => getDefaultMiddleware().concat([ | ||
| 64 | sitePageSlice.middleware, | ||
| 65 | ]), | ||
| 66 | }) | ||
| 67 | |||
| 68 | export const getSitePage = async (site, page) => { | ||
| 69 | const result = await store.dispatch(sitePageSlice.endpoints.getPage.initiate({ site, page })) | ||
| 70 | if (result.data) result.doc = parseHtml(result.data) | ||
| 71 | return result | ||
| 72 | } | ||
| 73 | |||
| 74 | export const setSiteConfig = (siteDef) => { | ||
| 75 | return store.dispatch(configSlice.actions.setSiteConfig(siteDef)) | ||
| 76 | } | ||
| 77 | |||
| 78 | export const getSiteBaseUrl = (name) => { | ||
| 79 | return configSlice.selectors.getSiteBaseUrl(store.getState(), name) | ||
| 80 | } | ||
| 81 | |||
| 82 | setSiteConfig({ name: 'msd', baseUrl: 'https://myspecialtydoc-d1d234.webflow.io' }) | ||
| 83 | 
lib/astro.ts
0 → 100644
lib/cache.ts
0 → 100644
lib/children.astro
0 → 100644
| 1 | --- | ||
| 2 | import Render, { slotPassThrough } from './render.astro' | ||
| 3 | |||
| 4 | const { props: { debug, parent, children, replacers, slotHandler } } = Astro | ||
| 5 | |||
| 6 | const slotCallback = slotPassThrough(Astro) | ||
| 7 | |||
| 8 | //console.log('Children:render', { parent, children, replacers }) | ||
| 9 | --- | ||
| 10 | { | ||
| 11 | Array.isArray(children) ? | ||
| 12 | children.map((child, index) => <Render debug={debug} parent={parent} node={child} index={index} replacers={replacers} slotHandler={slotHandler}/>) | ||
| 13 | : !children ? '' | ||
| 14 | : <Render debug={debug} parent={parent} node={children} index={0} replacers={replacers} slotHandler={slotHandler}/> | ||
| 15 | } | 
lib/custom.astro
0 → 100644
| 1 | --- | ||
| 2 | |||
| 3 | import { slotPassThrough } from './render.astro' | ||
| 4 | import Children from './children.astro' | ||
| 5 | |||
| 6 | const { props: { wrap = false, wrapAttributes = {}, node, replacers, slotHandler } } = Astro | ||
| 7 | const { name: Name, attributes, children } = node | ||
| 8 | |||
| 9 | const CustomName = `custom-${Name}` | ||
| 10 | const slotCallback = slotPassThrough(Astro) | ||
| 11 | |||
| 12 | //console.log('Got custom match', node) | ||
| 13 | --- | ||
| 14 | { | ||
| 15 | wrap ? ( | ||
| 16 | <CustomName {...wrapAttributes}> | ||
| 17 | { | ||
| 18 | node.isSelfClosingTag ? <Name {...attributes}/> | ||
| 19 | : <Name {...attributes}> | ||
| 20 | <Children parent={node} children={children} replacers={replacers} slotHandler={slotHandler}/> | ||
| 21 | </Name> | ||
| 22 | } | ||
| 23 | </CustomName> | ||
| 24 | ) : ( | ||
| 25 | node.isSelfClosingTag ? <CustomName {...attributes}/> | ||
| 26 | : <CustomName {...attributes}> | ||
| 27 | <Children parent={node} children={children} replacers={replacers} slotHandler={slotHandler}/> | ||
| 28 | </CustomName> | ||
| 29 | ) | ||
| 30 | } | ||
| 31 | 
lib/html.ts
0 → 100644
| 1 | import nodeUtil from 'util' | ||
| 2 | import NodeCache from 'node-cache' | ||
| 3 | |||
| 4 | import { | ||
| 5 | parse as umParse, | ||
| 6 | render as umRender, | ||
| 7 | transform as umTransform, | ||
| 8 | __unsafeHTML, | ||
| 9 | ELEMENT_NODE, | ||
| 10 | TEXT_NODE, | ||
| 11 | walkSync, | ||
| 12 | } from 'ultrahtml' | ||
| 13 | |||
| 14 | import { | ||
| 15 | parse as elParse, | ||
| 16 | specificity as getSpecificity, | ||
| 17 | specificityToNumber, | ||
| 18 | } from 'parsel-js' | ||
| 19 | |||
| 20 | import { parsedHtmlCache, selectorCache, findNodeCache } from './cache.js' | ||
| 21 | |||
| 22 | // TODO: implement a parent/child/element cache | ||
| 23 | const filterChildElements = (parent) => parent?.children?.filter(n => n.type === ELEMENT_NODE) || [] | ||
| 24 | const nthChildPos = (node, parent) => filterChildElements(parent).findIndex((child) => child === node); | ||
| 25 | |||
| 26 | const makeNthChildPosMatcher = (ast) => { | ||
| 27 | const { argument } = ast | ||
| 28 | const n = Number(argument) | ||
| 29 | if (!Number.isNaN(n)) { | ||
| 30 | return (context, node, parent, i) => { | ||
| 31 | return i === n | ||
| 32 | } | ||
| 33 | } | ||
| 34 | switch (argument) { | ||
| 35 | case 'odd': | ||
| 36 | return (context, node, parent, i) => Math.abs(i % 2) === 1 | ||
| 37 | case 'even': | ||
| 38 | return (context, node, parent, i) => i % 2 === 0 | ||
| 39 | default: { | ||
| 40 | if (!argument) throw new Error(`Unsupported empty nth-child selector!`) | ||
| 41 | let [_, A = '1', B = '0'] = /^\s*(?:(-?(?:\d+)?)n)?\s*\+?\s*(\d+)?\s*$/gm.exec(argument) ?? [] | ||
| 42 | if (A.length === 0) A = '1' | ||
| 43 | const a = Number.parseInt(A === '-' ? '-1' : A) | ||
| 44 | const b = Number.parseInt(B) | ||
| 45 | const nth = (index) => (a * n) + b | ||
| 46 | return (context, node, parent, i) => { | ||
| 47 | const elements = filterChildElements(parent) | ||
| 48 | for (let index = 0; index < elements.length; index++) { | ||
| 49 | const n = nth(index) | ||
| 50 | if (n > elements.length) return false | ||
| 51 | if (n === i) return true | ||
| 52 | } | ||
| 53 | return false | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | const getAttrValueMatch = (value, operator = '=', caseSenstive) => { | ||
| 60 | if (value === undefined) return (attrValue) => attrValue !== undefined | ||
| 61 | const isCaseInsenstive = caseSensitive === 'i' | ||
| 62 | if (isCaseInsensitive) value = value.toLowerCase() | ||
| 63 | const adjustMatcher = (matcher) => isCaseInsensitive ? (attrValue) => matcher(attrValue.toLowerCase()) : matcher | ||
| 64 | switch (operator) { | ||
| 65 | case '=': return (attrValue) => value === attrValue | ||
| 66 | case '~=': { | ||
| 67 | const keys = value.split(/\s+/g).reduce((keys, item) => { | ||
| 68 | keys[ item ] = true | ||
| 69 | return keys | ||
| 70 | }, {}) | ||
| 71 | return adjustMatcher((attrValue) => keys[ attrValue ]) | ||
| 72 | } | ||
| 73 | case '|=': return adjustMatcher((attrValue) => value.startsWith(attrValue + '-')) | ||
| 74 | case '*=': return adjustMatcher((attrValue) => value.indexOf(attrValue) > -1) | ||
| 75 | case '$=': return adjustMatcher((attrValue) => value.endsWith(attrValue)) | ||
| 76 | case '^=': return adjustMatcher((attrValue) => value.startsWith(attrValue)) | ||
| 77 | } | ||
| 78 | return (attrValue) => false | ||
| 79 | } | ||
| 80 | |||
| 81 | const compileMatcher = (ast, selector) => { | ||
| 82 | let counter = 0 | ||
| 83 | |||
| 84 | const neededContext = [] | ||
| 85 | const makeMatcher = (ast) => { | ||
| 86 | //console.log('makeMatcher', ast) | ||
| 87 | switch (ast.type) { | ||
| 88 | case 'list': { | ||
| 89 | const matchers = ast.list.map(s => makeMatcher(s)) | ||
| 90 | return (context, node, parent, i, debug) => { | ||
| 91 | for (const matcher of matchers) { | ||
| 92 | if (!matcher(context, node, parent, i)) return false | ||
| 93 | } | ||
| 94 | return true | ||
| 95 | } | ||
| 96 | } | ||
| 97 | case 'compound': { | ||
| 98 | const matchers = ast.list.map(s => makeMatcher(s)) | ||
| 99 | return (context, node, parent, i, debug) => { | ||
| 100 | for (const matcher of matchers) { | ||
| 101 | if (!matcher(context, node, parent, i)) return false | ||
| 102 | } | ||
| 103 | return true | ||
| 104 | } | ||
| 105 | } | ||
| 106 | case 'complex': { | ||
| 107 | const { left, right, combinator, pos } = ast | ||
| 108 | const leftMatcher = makeMatcher(left) | ||
| 109 | const rightMatcher = makeMatcher(right) | ||
| 110 | const setCounter = counter++ | ||
| 111 | neededContext[ setCounter ] = () => new WeakSet() | ||
| 112 | return (context, node, parent, i, debug) => { | ||
| 113 | const seen = context[ setCounter ] | ||
| 114 | if (leftMatcher(context, node, parent, i, debug)) { | ||
| 115 | if (debug) console.log('matched on left', { left, right, combinator, pos, parent }) | ||
| 116 | // TODO: Check seen.has(), and maybe skip calling leftMatcher? | ||
| 117 | seen.add(node) | ||
| 118 | } else if (parent && seen.has(parent) && combinator === ' ') { | ||
| 119 | seen.add(node) | ||
| 120 | } | ||
| 121 | if (!rightMatcher(context, node, parent, i, debug)) return false | ||
| 122 | seen.add(node) | ||
| 123 | if (debug) console.log('matched on right', { left, right, combinator, pos, node, parent }) | ||
| 124 | switch (combinator) { | ||
| 125 | case ' ': | ||
| 126 | let parentPtr = parent | ||
| 127 | while (parentPtr) { | ||
| 128 | if (seen.has(parentPtr)) return true | ||
| 129 | parentPtr = parentPtr.parent | ||
| 130 | } | ||
| 131 | return false | ||
| 132 | case '>': | ||
| 133 | if (debug) console.log('seen parent', seen.has(parent)) | ||
| 134 | return parent ? seen.has(parent) : false | ||
| 135 | case '+': { | ||
| 136 | if (!parent) return false | ||
| 137 | let prevSiblings = parent.children.slice(0, i).filter((el) => el.type === ELEMENT_NODE) | ||
| 138 | if (prevSiblings.length === 0) return false | ||
| 139 | const prev = prevSiblings[prevSiblings.length - 1] | ||
| 140 | if (!prev) return false | ||
| 141 | if (seen.has(prev)) return true | ||
| 142 | return false | ||
| 143 | } | ||
| 144 | case '~': { | ||
| 145 | if (!parent) return false | ||
| 146 | let prevSiblings = parent.children.slice(0, i).filter((el) => el.type === ELEMENT_NODE) | ||
| 147 | if (prevSiblings.length === 0) return false | ||
| 148 | for (const prev of prevSiblings) { | ||
| 149 | if (seen.has(prev)) return true | ||
| 150 | } | ||
| 151 | return false | ||
| 152 | } | ||
| 153 | default: | ||
| 154 | return false | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | case 'type': { | ||
| 159 | const { name, content } = ast | ||
| 160 | if (content === '*') return (context, node, parent, i) => true | ||
| 161 | return (context, node, parent, i, debug) => node.name === name | ||
| 162 | } | ||
| 163 | case 'class': { | ||
| 164 | const { name } = ast | ||
| 165 | return (context, node, parent, i, debug) => node.attributes?.['class']?.split(/\s+/g).includes(name) | ||
| 166 | } | ||
| 167 | case 'id': { | ||
| 168 | const { name } = ast | ||
| 169 | return (context, node, parent, i, debug) => node.attributes?.id === name | ||
| 170 | } | ||
| 171 | case 'pseudo-class': | ||
| 172 | switch (ast.name) { | ||
| 173 | case 'global': | ||
| 174 | return makeMatcher(elParse(ast.argument)) | ||
| 175 | case 'not': { | ||
| 176 | const matcher = makeMatcher(ast.subtree) | ||
| 177 | return (...args) => !matcher(...args) | ||
| 178 | } | ||
| 179 | case 'is': | ||
| 180 | return makeMatcher(ast.subtree) | ||
| 181 | case 'where': | ||
| 182 | return makeMatcher(ast.subtree) | ||
| 183 | case 'root': | ||
| 184 | return (context, node, parent, i) => !node.parent | ||
| 185 | case 'empty': | ||
| 186 | return (context, node, parent, i, debug) => { | ||
| 187 | if (node.type !== ELEMENT_NODE) return false | ||
| 188 | const { children } = node | ||
| 189 | if (children.length === 0) return false | ||
| 190 | return children.every(child => child.type === TEXT_NODE && child.value.trim() === '') | ||
| 191 | } | ||
| 192 | case 'first-child': | ||
| 193 | return (context, node, parent, i, debug) => { | ||
| 194 | return parent?.children.findFirst(child => child.type === ELEMENT_NODE) === node | ||
| 195 | } | ||
| 196 | case 'last-child': | ||
| 197 | return (context, node, parent, i, debug) => { | ||
| 198 | return parent?.children.findLast(child => child.type === ELEMENT_NODE) === node | ||
| 199 | } | ||
| 200 | case 'only-child': | ||
| 201 | return (context, node, parent, i, debug) => { | ||
| 202 | // TODO: This can break-early after it finds the second element | ||
| 203 | return filterChildElements(parent).length === 1 | ||
| 204 | } | ||
| 205 | case 'nth-child': { | ||
| 206 | const nthChildMatcher = makeNthChildPosMatcher(ast) | ||
| 207 | return (context, node, parent, i, debug) => { | ||
| 208 | const pos = nthChildPos(node, parent) + 1 | ||
| 209 | return nthChildMatcher(context, node, parent, pos) | ||
| 210 | } | ||
| 211 | } | ||
| 212 | default: | ||
| 213 | throw new Error(`Unknown pseudo-class: ${ast.name}`) | ||
| 214 | } | ||
| 215 | case 'attribute': | ||
| 216 | const { caseSensitive, name, value, operator } = ast | ||
| 217 | const attrValueMatch = getAttrValueMatch(value, operator, caseSenstive) | ||
| 218 | return (context, node, parent, i, debug) => { | ||
| 219 | const { attributes: { [ name ]: attrValue } = {} } = node | ||
| 220 | return attrValueMatch(attrValue) | ||
| 221 | } | ||
| 222 | case 'universal': | ||
| 223 | return (context, node, parent, i, debug) => true | ||
| 224 | default: | ||
| 225 | throw new Error(`Unhandled ast: ${ast.type}`) | ||
| 226 | } | ||
| 227 | } | ||
| 228 | const matcher = makeMatcher(ast) | ||
| 229 | return () => { | ||
| 230 | const context = neededContext.map(item => item()) | ||
| 231 | const nodeMatcher = (node, parent, i, debug) => { | ||
| 232 | //if (debug) console.log('starting to match', {node, context}) | ||
| 233 | return matcher(context, node, parent, i, debug) | ||
| 234 | } | ||
| 235 | nodeMatcher.toString = () => { | ||
| 236 | return '[matcher:' + selector + ']' | ||
| 237 | } | ||
| 238 | return nodeMatcher | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | export const createMatcher = (selector) => { | ||
| 243 | const matcherCreater = selectorCache.get(selector) | ||
| 244 | if (false && matcher) return matcherCreater() | ||
| 245 | const ast = elParse(selector) | ||
| 246 | //console.log('createMatcher', nodeUtil.inspect({ selector, ast }, { depth: null, colors: true })) | ||
| 247 | const newMatcherCreater = compileMatcher(ast, selector) | ||
| 248 | selectorCache.set(selector, newMatcherCreater) | ||
| 249 | return newMatcherCreater() | ||
| 250 | } | ||
| 251 | |||
| 252 | export const parseHtml = (html) => { | ||
| 253 | const cached = parsedHtmlCache.get(html) | ||
| 254 | if (cached) return cached | ||
| 255 | const doc = umParse(html) | ||
| 256 | parsedHtmlCache.set(html, doc) | ||
| 257 | return doc | ||
| 258 | } | ||
| 259 | |||
| 260 | export const findNode = (doc, selector) => { | ||
| 261 | if (!selector) return doc | ||
| 262 | let docCache = findNodeCache.get(doc) | ||
| 263 | if (!docCache) { | ||
| 264 | docCache = new NodeCache({ stdTTL: 10*60, useClones: false }) | ||
| 265 | findNodeCache.set(doc, docCache) | ||
| 266 | } | ||
| 267 | const found = docCache.get(selector) | ||
| 268 | if (found !== undefined) return found[0] | ||
| 269 | //console.log('cache miss', {selector}) | ||
| 270 | const matcher = createMatcher(selector) | ||
| 271 | try { | ||
| 272 | walkSync(doc, (node, parent, index) => { | ||
| 273 | if (matcher(node, parent, index)) throw node | ||
| 274 | }) | ||
| 275 | } catch (e) { | ||
| 276 | if (e instanceof Error) throw e | ||
| 277 | docCache.set(selector, [ e ]) | ||
| 278 | return e | ||
| 279 | } | ||
| 280 | } | 
lib/render.astro
0 → 100644
| 1 | --- | ||
| 2 | import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml' | ||
| 3 | import Children from './children.astro' | ||
| 4 | |||
| 5 | export const slotPassThrough = (Astro) => (slotName, node) => { | ||
| 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 | ||
| 12 | |||
| 13 | if (debug) { | ||
| 14 | console.log('trying to match against', {node}) | ||
| 15 | } | ||
| 16 | let slotName | ||
| 17 | for (const [ matcher, handler ] of replacers) { | ||
| 18 | if (debug) console.log('attempting matcher', matcher.toString()) | ||
| 19 | if (matcher(node, parent, index, false)) { | ||
| 20 | if (debug) console.log('matched') | ||
| 21 | slotName = handler | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | //const slotCallback = slotPassThrough(Astro) | ||
| 26 | --- | ||
| 27 | { | ||
| 28 | slotName ? slotHandler(slotName, node) | ||
| 29 | : node.type === TEXT_NODE ? <Fragment set:html={node.value}/> | ||
| 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 | } | 
lib/replace.astro
0 → 100644
| 1 | --- | ||
| 2 | import html from '@resources/provider-portal.html?raw' | ||
| 3 | import { walkSync } from 'ultrahtml' | ||
| 4 | import { parseHtml, createMatcher, findNode } from './html.js' | ||
| 5 | import Render from './render.astro' | ||
| 6 | |||
| 7 | const { props } = Astro | ||
| 8 | const { html, mapClassname = true, xpath, replacements = {} } = props | ||
| 9 | const doc = props.node ? props.node : parseHtml(props.html) | ||
| 10 | |||
| 11 | const node = xpath ? findNode(doc, xpath) : doc | ||
| 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 | |||
| 19 | const slotHandler = (slotName, node) => { | ||
| 20 | const attributes = fixAttributes(node) | ||
| 21 | return Astro.slots.render(slotName, [ { ...node, attributes }, { slotHandler, replacers } ] ) | ||
| 22 | } | ||
| 23 | --- | ||
| 24 | <Render node={node} replacers={replacers} slotHandler={slotHandler}/> | 
package-lock.json
0 → 100644
This diff could not be displayed because it is too large.
package.json
0 → 100644
| 1 | { | ||
| 2 | "name": "astro-wt", | ||
| 3 | "type": "module", | ||
| 4 | "exports": { | ||
| 5 | "./astro": "./src/astro.mjs", | ||
| 6 | "./client": "./src/client.mjs", | ||
| 7 | "./slices": "./src/slices.mjs", | ||
| 8 | "./react": "./src/react.jsx", | ||
| 9 | "./session": "./src/session.mjs", | ||
| 10 | "./ReduxIsland": "./src/ReduxIsland.astro" | ||
| 11 | }, | ||
| 12 | "scripts": { | ||
| 13 | "dev": "astro dev" | ||
| 14 | }, | ||
| 15 | "dependencies": { | ||
| 16 | "@reduxjs/toolkit": "^2.2.5", | ||
| 17 | "node-cache": "^5.1.2", | ||
| 18 | "parsel-js": "^1.1.2", | ||
| 19 | "ultrahtml": "^1.5.3" | ||
| 20 | }, | ||
| 21 | "devDependencies": { | ||
| 22 | "@astrojs/node": "^8.2.5", | ||
| 23 | "@astrojs/react": "^3.3.4", | ||
| 24 | "astro": "^4.8.7", | ||
| 25 | "react": "^18.3.1" | ||
| 26 | } | ||
| 27 | } | 
src/components/EventDetails.jsx
0 → 100644
src/config.ts
0 → 100644
src/env.d.ts
0 → 100644
| 1 | /// <reference types="astro/client" /> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | 
src/layouts/base.astro
0 → 100644
| 1 | --- | ||
| 2 | import { getSitePage } from '@lib/api.ts' | ||
| 3 | import { Replace } from '@lib/astro.ts' | ||
| 4 | import '@config.ts' | ||
| 5 | |||
| 6 | const { data: layoutHtml } = await getSitePage('msd', '/') | ||
| 7 | const layoutReplacements = { | ||
| 8 | ['.hero-section']: 'content', | ||
| 9 | ['.content-section']: 'DELETE', | ||
| 10 | ['.content-section-4']: 'DELETE', | ||
| 11 | //['.navbar.w-nav:nth-child(n + 1)']: 'DELETE', | ||
| 12 | // The following 2 are not implemented yet | ||
| 13 | //[':nth-child(n + 1 of .navbar.w-nav)']: 'DELETE', | ||
| 14 | //['.navbar.w-nav:nth-of-type(n + 1)']: 'DELETE', | ||
| 15 | } | ||
| 16 | --- | ||
| 17 | <Replace debug={true} html={layoutHtml} replacements={layoutReplacements}> | ||
| 18 | <define slot="DELETE">{(node) => ''}</define> | ||
| 19 | <define slot='content'>{(node) => <slot/>}</define> | ||
| 20 | </Replace> | 
src/pages/index.astro
0 → 100644
| 1 | --- | ||
| 2 | import { getSitePage } from '@lib/api.ts' | ||
| 3 | import { Replace } from '@lib/astro.ts' | ||
| 4 | import BaseLayout from '@layouts/base.astro' | ||
| 5 | import { Demographics } from '@components/EventDetails.jsx' | ||
| 6 | |||
| 7 | const { data: layoutHtml } = await getSitePage('msd', '/') | ||
| 8 | const { data: pageHtml } = await getSitePage('msd', '/provider-portal') | ||
| 9 | const pageReplacements = { | ||
| 10 | ['#w-node-_3615b991-cc00-f776-58c2-a728a0fba1a9-70cc1f5d']: 'Demographics', | ||
| 11 | ['#w-node-eaef93e0-275c-ff2c-39b3-0cc9b81495f3-70cc1f5d .div-block-11']: 'DELETE', | ||
| 12 | ['#w-node-eaef93e0-275c-ff2c-39b3-0cc9b81495f3-70cc1f5d > .div-block-8 img']: 'custom', | ||
| 13 | ['#w-node-eaef93e0-275c-ff2c-39b3-0cc9b81495f3-70cc1f5d > .div-block-8']: 'Comms', | ||
| 14 | ['#w-node-a9641a71-b768-107f-2209-4491bf21ed0a-70cc1f5d > .textblock']: 'ChiefComplaint', | ||
| 15 | ['#w-node-bbdf8d21-dc38-5a20-e2b8-38fbe2e0f88a-70cc1f5d > .textblock']: 'HealthHistory', | ||
| 16 | ['#w-node-_4b7383d7-9725-37cd-1d15-d6292436eb85-70cc1f5d .textblock']: 'Subjective', | ||
| 17 | // ['#w-node-a97d0be9-a1a9-af1d-a30b-f54664c79b09-70cc1f5d > .textblock']: 'ObjectiveLevels', | ||
| 18 | ['#w-node-_8c10d845-8d0d-25e6-d1da-42fc4c0faa17-70cc1f5d > .textblock']: 'Objective', | ||
| 19 | ['#w-node-_77cf40d0-cb39-d173-d0ed-0a30bcbbda03-70cc1f5d ~ .div-block-15 > .textblock']: 'Assessment', | ||
| 20 | ['#w-node-f55c4197-553d-142d-be0f-1364e3d5ce27-70cc1f5d .textblock']: 'Assessment', | ||
| 21 | ['#w-node-_993bee43-3117-30d9-0b03-cb9d32541bb9-70cc1f5d > .textblock']: 'Formulary', | ||
| 22 | ['#w-node-_1817664f-14e2-687e-1460-429ef0a62ab3-70cc1f5d .textblock']: 'Plan', | ||
| 23 | } | ||
| 24 | --- | ||
| 25 | <BaseLayout> | ||
| 26 | <Replace html={pageHtml} xpath='.container-2.w-container' replacements={pageReplacements}> | ||
| 27 | <define slot='DELETE'>{(node, context) => ''}</define> | ||
| 28 | <define slot='Demographics'>{(node, context) => <Demographics/>}</define> | ||
| 29 | </Replace> | ||
| 30 | </BaseLayout> | 
- 
Please register or sign in to post a comment