From e6d26c4d115dc3c3f488086a05bce6f82996b90b Mon Sep 17 00:00:00 2001 From: TudorCe <101194278+TudorCe@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:39:42 +0200 Subject: [PATCH] Add support for array mapper in HTML (#952) --- .../src/node-handlers.ts | 185 ++++++++++++++++-- 1 file changed, 174 insertions(+), 11 deletions(-) diff --git a/packages/teleport-plugin-html-base-component/src/node-handlers.ts b/packages/teleport-plugin-html-base-component/src/node-handlers.ts index 41355643d..977ac78a6 100644 --- a/packages/teleport-plugin-html-base-component/src/node-handlers.ts +++ b/packages/teleport-plugin-html-base-component/src/node-handlers.ts @@ -24,6 +24,7 @@ import { ElementsLookup, UIDLConditionalNode, PropDefaultValueTypes, + UIDLCMSListRepeaterNode, } from '@teleporthq/teleport-types' import { join, relative } from 'path' import { HASTBuilders, HASTUtils, ASTUtils } from '@teleporthq/teleport-plugin-common' @@ -82,6 +83,13 @@ type NodeToHTML = ( dependencies: Record options: GeneratorOptions outputOptions: UIDLComponentOutputOptions + }, + /** + * This param is just to be able to handle CMS array mappers/Repeater nodes. A bit hacky, better support should be implemented + */ + resolvedExpressions?: { + expressions: Record + currentIndex: number } ) => ReturnType @@ -92,7 +100,8 @@ export const generateHtmlSyntax: NodeToHTML { switch (node.type) { case 'inject': @@ -113,7 +122,8 @@ export const generateHtmlSyntax: NodeToHTML { } } -const generateElementNode: NodeToHTML> = async ( +const generateRepeaterNode: NodeToHTML< + UIDLCMSListRepeaterNode, + Promise +> = async ( node, compName, nodesLookup, @@ -258,6 +303,94 @@ const generateElementNode: NodeToHTML { + const { nodes } = node.content + + const contextId = node.content.renderPropIdentifier + const sourceProp = node.content.source + const propDef = + propDefinitions[ + Object.keys(propDefinitions).find((propKey) => sourceProp.includes(propKey)) || '' + ] + + propDefinitions[contextId] = propDef + + if (!propDef || !Array.isArray(propDef.defaultValue)) { + return HASTBuilders.createComment( + 'CMS Array Mapper/Repeater not supported in HTML without a prop source' + ) + } + + const elementNode = HASTBuilders.createHTMLNode('div') + node.content.nodes.list.content.style = { display: { type: 'static', content: 'contents' } } + // Empty case + if (propDef.defaultValue.length === 0) { + const emptyChildren = nodes.empty.content.children + if (emptyChildren) { + for (const child of emptyChildren) { + const childTag = await generateHtmlSyntax( + child, + compName, + nodesLookup, + propDefinitions, + stateDefinitions, + subComponentOptions, + structure + ) + + if (typeof childTag === 'string') { + HASTUtils.addTextNode(elementNode, childTag) + } else { + HASTUtils.addChildNode(elementNode, childTag as HastNode) + } + } + } + return elementNode + } + + const listChildren = nodes.list.content.children + if (listChildren) { + for (let index = 0; index < propDef.defaultValue.length; index++) { + for (const child of listChildren) { + const childTag = await generateHtmlSyntax( + child, + compName, + nodesLookup, + propDefinitions, + stateDefinitions, + subComponentOptions, + structure, + { currentIndex: index, expressions: { [contextId]: propDef } } + ) + + if (typeof childTag === 'string') { + HASTUtils.addTextNode(elementNode, childTag) + } else { + HASTUtils.addChildNode(elementNode, childTag as HastNode) + } + } + } + } + + addNodeToLookup( + `${node.content.nodes.list.content.key}`, + node.content.nodes.list, + elementNode, + nodesLookup, + [compName] + ) + return elementNode +} + +const generateElementNode: NodeToHTML> = async ( + node, + compName, + nodesLookup, + propDefinitions, + stateDefinitions, + subComponentOptions, + structure, + resolvedExpressions ) => { const { elementType, @@ -301,7 +434,8 @@ const generateElementNode: NodeToHTML, stateDefinitions: Record, routeDefinitions: UIDLRouteDefinitions, - outputOptions: UIDLComponentOutputOptions + outputOptions: UIDLComponentOutputOptions, + currentIndex?: number ) => { for (const attrKey of Object.keys(attrs)) { const attrValue = attrs[attrKey] @@ -820,9 +964,28 @@ const handleAttributes = ( break } + case 'expr': { + const fullPath = content.split('?.') + const prop = propDefinitions[fullPath?.[0] || ''] + + if (!prop) { + break + } + + const path = + typeof currentIndex === 'number' + ? [currentIndex.toString(), ...fullPath.slice(1)] + : fullPath.slice(1) + const value = extractDefaultValueFromRefPath(prop.defaultValue, path) + if (!value) { + break + } + HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value)) + break + } + case 'element': case 'import': - case 'expr': case 'object': break