diff --git a/apps/node/content/canvas-data.ts b/apps/node/content/canvas-data.ts index 13c6c19..d3bf899 100644 --- a/apps/node/content/canvas-data.ts +++ b/apps/node/content/canvas-data.ts @@ -713,4 +713,178 @@ export const data: Block[] = [ }, "value": "I feel the need, the need for speed" } +]; + +export const emailData: Block[] = [ + { + "id": "1f7b06d4", + "type": "_divider" + }, + { + "id": "db6fbe71", + "type": "_heading", + "properties": { + "level": 1 + }, + "value": "Heading one" + }, + { + "id": "3d4ca613", + "type": "_heading", + "properties": { + "level": 2 + }, + "value": "Heading two" + }, + { + "id": "fe7a61db", + "type": "_paragraph", + "value": "Fringilla vivamus facilisi est sed mi cubilia fusce penatibus. Praesent enim id molestie suspendisse arcu luctus. Quis pede praesent etiam cras nunc molestie massa consequat mi fringilla. Consectetur dictumst pellentesque maecenas quis adipiscing efficitur ad dis gravida. Porta venenatis pretium magna dictumst nullam placerat proin elit posuere ligula amet. Penatibus blandit ad egestas duis nascetur suscipit in vivamus." + }, + { + "id": "17184e46", + "type": "_list", + "properties": { + "listType": "ordered" + }, + "value": [ + { + "id": "f85f8d21", + "type": "_listItem", + "value": "Consectetur dictumst pellentesque maecenas quis adipiscing efficitur ad dis gravida." + }, + { + "id": "a29cc5c8", + "type": "_listItem", + "value": "Porta venenatis pretium magna dictumst nullam placerat proin elit posuere ligula amet." + }, + { + "id": "f5cec1e6", + "type": "_listItem", + "value": "Penatibus blandit ad egestas duis nascetur suscipit in vivamus." + } + ] + }, + { + "id": "f58fad3c", + "type": "_paragraph", + "value": "Himenaeos et sagittis sociosqu proin ex ligula interdum condimentum porttitor. Potenti turpis vestibulum consectetuer amet laoreet dictum justo dolor. Enim euismod elementum commodo lacinia viverra. Leo montes augue facilisi massa bibendum rhoncus lobortis interdum. Ornare risus taciti adipiscing ultrices metus imperdiet laoreet condimentum. Placerat lacinia semper condimentum dictum dolor. In accumsan curae at massa mollis potenti hendrerit habitasse hac. Facilisi risus urna vitae ante auctor ultricies sagittis turpis semper." + }, + { + "id": "96363391", + "type": "_list", + "properties": { + "listType": "unordered" + }, + "value": [ + { + "id": "ea27614d", + "type": "_listItem", + "value": "Consectetur dictumst pellentesque maecenas quis adipiscing efficitur ad dis gravida." + }, + { + "id": "744a890f", + "type": "_listItem", + "value": "Porta venenatis pretium magna dictumst nullam placerat proin elit posuere ligula amet." + }, + { + "id": "8a454e21", + "type": "_listItem", + "value": "Penatibus blandit ad egestas duis nascetur suscipit in vivamus." + } + ] + }, + { + "id": "22116229", + "type": "_heading", + "properties": { + "level": 3 + }, + "value": "Heading three" + }, + { + "id": "61a42305", + "type": "_table", + "value": [ + { + "id": "92a840c2", + "type": "_tableCaption", + "value": [] + }, + { + "id": "35eb22dc", + "type": "_tableBody", + "value": [ + { + "id": "2b74719", + "type": "_tableRow", + "value": [ + { + "id": "44ee4eb5", + "type": "_tableHeaderCell", + "value": "Name:" + }, + { + "id": "4d9bba8b", + "type": "_tableCell", + "value": "{{name}}" + } + ] + }, + { + "id": "1a301c54", + "type": "_tableRow", + "value": [ + { + "id": "d25b6aa5", + "type": "_tableCell", + "value": "Email:" + }, + { + "id": "bb68de3e", + "type": "_tableCell", + "value": "{{email}}" + } + ] + }, + { + "id": "fe1a1578", + "type": "_tableRow", + "value": [ + { + "id": "1be2e16a", + "type": "_tableCell", + "value": "Phone:" + }, + { + "id": "cabab943", + "type": "_tableCell", + "value": "{{phoneNumber}}" + } + ] + }, + { + "id": "2978b392", + "type": "_tableRow", + "value": [ + { + "id": "c077dfed", + "type": "_tableCell", + "value": "Message:" + }, + { + "id": "7c56ceec", + "type": "_tableCell", + "value": "{{message}}" + } + ] + } + ] + } + ] + }, + { + "id": "3f36dbd9", + "type": "_divider" + } ]; \ No newline at end of file diff --git a/apps/node/content/contents.ts b/apps/node/content/contents.ts index d02157c..46b0693 100644 --- a/apps/node/content/contents.ts +++ b/apps/node/content/contents.ts @@ -1,6 +1,7 @@ import { createRenderer } from '@contensis/canvas-html'; import { myHtmlHeading, myHtmlParagraph, myHtmlFragment, myHtmlTable, myHtmlPanel, myHtmlImage, myHtmlCode, myHtmlList, myHtmlListItem, myHtmlAuthorComponent, myHtmlBookComponent } from './elements'; import { Block } from '@contensis/canvas-types'; +import { divider, heading, table, tableCell, tableHeaderCell } from '@contensis/canvas-html'; const renderer = createRenderer({ blocks: { @@ -23,3 +24,42 @@ const renderer = createRenderer({ export function getHtml(data: Block[]) { return renderer({ data }); } + + +const emailRenderer = createRenderer({ + blocks: { + _divider: function (props: any) { + return divider({ ...props, style: 'background-color: #dbdbdb; border: 0; height: 1px; margin: 32px 0' }); + }, + _heading: function (props: any) { + const level = props.block?.properties?.level || 1; + switch (level) { + case 1: { + return heading({ ...props, style: 'font-size: 18px; line-height: 24px; font-weight: 800; margin-bottom: 16px;' }) + } + case 2: { + return heading({ ...props, style: 'font-size: 16px; line-height: 24px; margin-bottom: 16px' }); + } + case 3: { + return heading({ ...props, style: 'font-size: 14px; line-height: 20px; margin-bottom: 16px' }); + } + default: { + return heading(props); + } + } + }, + _table: function (props: any) { + return table({ ...props, style: 'margin-top: 8px; margin-bottom: 32px' }); + }, + _tableCell: function (props: any) { + return tableCell({ ...props, style: 'padding: 8px 0' }); + }, + _tableHeaderCell: function (props: any) { + return tableHeaderCell({ ...props, style: 'padding: 8px 0' }); + } + } +}); + +export function toEmail(data: Block[]) { + return emailRenderer({ data }); +} \ No newline at end of file diff --git a/apps/node/index.ts b/apps/node/index.ts index 92c43ed..deb3d36 100644 --- a/apps/node/index.ts +++ b/apps/node/index.ts @@ -1,6 +1,6 @@ import express, { Request, Response } from 'express'; import * as CanvasData from './content/canvas-data'; -import { getHtml } from './content/contents'; +import { toEmail, getHtml } from './content/contents'; const app = express(); @@ -10,4 +10,8 @@ app.get('/', (req: Request, res: Response) => { res.render('pages/index', { content: getHtml(CanvasData.data) }); }); +app.get('/email', (req: Request, res: Response) => { + res.render('pages/plain', { content: toEmail(CanvasData.emailData) }); +}); + export const viteNodeApp = app; \ No newline at end of file diff --git a/apps/node/views/pages/plain.ejs b/apps/node/views/pages/plain.ejs new file mode 100644 index 0000000..7b225a5 --- /dev/null +++ b/apps/node/views/pages/plain.ejs @@ -0,0 +1,19 @@ + + + + + + + Node - EJS + + + + +
<%- content %>
+ + diff --git a/packages/dom/src/renderer.ts b/packages/dom/src/renderer.ts index e60b51f..889ab85 100644 --- a/packages/dom/src/renderer.ts +++ b/packages/dom/src/renderer.ts @@ -11,6 +11,7 @@ import { ImageBlock, InlineEntryBlock, LinkBlock, + LiquidBlock, ListBlock, ListItemBlock, PanelBlock, @@ -49,14 +50,8 @@ type WithRenderers = { type RendererProps = { data: Block[]; context?: RenderContext }; type RenderBlocksProps = { blocks: Block[] } & WithContext & WithH & WithRenderers; -type RenderBlockProps = { block: T } & WithContext & - Attributes & - WithH & - WithRenderers; -type RenderDecoratorProps = { block: FragmentBlock; decorator: DecoratorType; otherDecorators: DecoratorType[] } & WithH< - TNode, - TFragment -> & +type RenderBlockProps = { block: T } & WithContext & Attributes & WithH & WithRenderers; +type RenderDecoratorProps = { block: FragmentBlock; decorator: DecoratorType; otherDecorators: DecoratorType[] } & WithH & WithRenderers & WithContext & Attributes; @@ -68,7 +63,7 @@ type TypedBlock = Extract; type BlockRenderer = (props: RenderBlockProps, ...children: TNode[]) => TNode; type BlockRenderers = { - [TType in Block['type']]: BlockRenderer, TNode, TFragment> + [TType in Block['type']]: BlockRenderer, TNode, TFragment>; }; type DecoratorRenderer = (props: RenderDecoratorProps, ...children: TNode[]) => TNode; @@ -300,7 +295,6 @@ divider.children = function (_props: RenderBlockProps(props: RenderBlockProps, ...children: TNode[]) { const { block, context, renderers, h, hFragment, hText } = props; const attributes = getAttributes(props, { @@ -314,7 +308,6 @@ formContentType.children = function (props: RenderBlockProps(props: RenderBlockProps, ...children: TNode[]) { const { block, context, renderers, h, hFragment, hText } = props; const hasDecorators = !!block?.properties?.decorators?.length; @@ -394,6 +387,17 @@ function link(props: RenderBlockProps(); +function liquid(props: RenderBlockProps, ...children: TNode[]) { + const { block, context, decorator, otherDecorators, renderers, h, hFragment, hText } = props; + children = getChildren(children, () => liquid.children({ block, context, decorator, otherDecorators, renderers, h, hFragment, hText })); + return toFragment(props, children); +} + +liquid.children = function (props: RenderBlockProps) { + const { hText } = props; + return toFragment(props, [hText(props.block?.value)]); +}; + const list = createBlockRenderer( (block) => (block?.properties?.listType === 'ordered' ? 'ol' : 'ul'), (block) => ({ @@ -484,6 +488,7 @@ function createRendererFactory(h: H, hFragme _image: imageWithCaption, _inlineEntry: inlineEntry, _link: link, + _liquid: liquid, _list: list, _listItem: listItem, _quote: quote, @@ -561,6 +566,7 @@ function createElements() { image: createBlockElement(image), inlineEntry: createBlockElement(inlineEntry), link: createBlockElement(link), + liquid: createBlockElement(liquid), list: createBlockElement(list), listItem: createBlockElement(listItem), panel: createBlockElement(panel), @@ -593,19 +599,16 @@ function createElements() { export type { BlockRenderer, - BlockRendererWithChildren, BlockRenderers, + BlockRendererWithChildren, ComponentRenderer, ComponentRenderers, DecoratorRenderer, - DecoratorRendererWithChildren, DecoratorRenderers, + DecoratorRendererWithChildren, RenderBlockProps, RenderDecoratorProps }; -export { - createElements, - createRendererFactory -}; +export { createElements, createRendererFactory }; diff --git a/packages/html-canvas/src/index.ts b/packages/html-canvas/src/index.ts index b69838c..9c0405b 100644 --- a/packages/html-canvas/src/index.ts +++ b/packages/html-canvas/src/index.ts @@ -34,11 +34,14 @@ export const createHtmlParser = async (opts: ParseConfiguration = {}): Promise t.type === '_component')?.components?.allowed || []; + const formContentTypes = field.validations?.allowedTypes?.types.find((t) => t.type === '_formContentType')?.formContentTypes?.allowed || []; + // Handle the '*' allowed type to allow any component to be parsed (which may not exist in the target project) if (field.validations?.allowedTypes?.types.find((t) => t.type === '*')) components.push('*'); const parserSettings: ParserSettings = { components, + formContentTypes, project, projectUuid: (project as any).uuid, rootUrl: rootUri @@ -72,12 +75,15 @@ export const createHtmlParser = async (opts: ParseConfiguration = {}): Promise t.type === '_component')?.components?.allowed || []; + const formContentTypes = field.validations?.allowedTypes?.types.find((t) => t.type === '_formContentType')?.formContentTypes?.allowed || []; + // Handle the '*' allowed type to allow any component to be parsed (which may not exist in the target project) if (field.validations?.allowedTypes?.types.find((t) => t.type === '*')) components.push('*'); // No client available, add ParserSettings filler const parserSettings: ParserSettings = { components, + formContentTypes, project: { id: 'any', primaryLanguage: 'en-GB', diff --git a/packages/html-parser/src/models/models.ts b/packages/html-parser/src/models/models.ts index b853684..6996d71 100644 --- a/packages/html-parser/src/models/models.ts +++ b/packages/html-parser/src/models/models.ts @@ -115,11 +115,13 @@ type Block = | CodeBlock | ComponentBlock | DividerBlock + | FormContentTypeBlock | FragmentBlock | HeadingBlock | ImageBlock | InlineEntryBlock | LinkBlock + | LiquidBlock | ListBlock | ListItemBlock | PanelBlock @@ -157,7 +159,6 @@ type CodeBlock = { }; properties?: { id?: string; - isNew?: boolean; // UI ONLY }; }; @@ -168,7 +169,6 @@ type ComponentBlock = any> = { properties?: { id?: string; component: string; - isNew?: boolean; // UI ONLY }; }; @@ -181,6 +181,21 @@ type DividerBlock = { }; }; +type FormContentType = { + id: string; +}; + +type FormContentTypeBlock = { + type: '_formContentType'; + id: string; + properties?: { + id?: string; + }; + value?: { + contentType?: FormContentType; + }; +}; + type FragmentBlock = { type: '_fragment'; id: string; @@ -207,7 +222,6 @@ type ImageBlock = { value?: Image; properties?: { id?: string; - isNew?: boolean; // UI ONLY }; }; @@ -231,6 +245,18 @@ type LinkBlock = { }; }; +type LiquidType = 'tag' | 'variable'; + +type LiquidBlock = { + type: '_liquid'; + id: string; + value?: string; + properties?: { + id?: string; + type?: LiquidType; + }; +}; + type ListType = 'ordered' | 'unordered'; type ListBlock = { @@ -373,11 +399,13 @@ export { CodeBlock, ComponentBlock, DividerBlock, + FormContentTypeBlock, FragmentBlock, HeadingBlock, ImageBlock, InlineEntryBlock, LinkBlock, + LiquidBlock, ListBlock, ListItemBlock, PanelBlock, diff --git a/packages/html-parser/src/models/settings.ts b/packages/html-parser/src/models/settings.ts index 9965bef..6c854fb 100644 --- a/packages/html-parser/src/models/settings.ts +++ b/packages/html-parser/src/models/settings.ts @@ -11,7 +11,7 @@ type _Values = { }; type _Array = { - [P in keyof T]: T[P] extends boolean ? T[P] : Array; + [P in keyof T]: T[P] extends boolean ? T[P] : null | Array; }; type _ValueOf = T[keyof T]; @@ -51,6 +51,8 @@ type SettingNames = { 'type.component.component': string; 'type.component': boolean; 'type.divider': boolean; + 'type.formContentType.contentType': string; + 'type.formContentType': boolean; 'type.heading.level': number; 'type.heading': boolean; 'type.image': boolean; @@ -65,12 +67,16 @@ type SettingNames = { 'type.image.minHeight': number; 'type.image.maxHeight': number; 'type.image.filterPaths': string; + 'type.image.uploadPath': string; 'type.inlineEntry.contentType': string; 'type.inlineEntry': boolean; + 'type.link.assetContentType': string; + 'type.link.assetFilterPaths': string; 'type.link.contentType': string; 'type.link.linkType': LinkType; 'type.link': boolean; + 'type.liquid': boolean; 'type.list.listType': ListType; 'type.list': boolean; 'type.quote': boolean; @@ -148,8 +154,8 @@ export function hasSetting(settings: CanvasSettings, setting: CanvasSetting) { if (parentKey && !settings[parentKey as keyof CanvasSettings]) { return false; } - const settingValue: any[] = settings[key]; - return settingValue ? settingValue.includes(value) : true; + const settingValue = settings[key]; + return settingValue ? (settingValue as any).includes(value) : true; } export function fixSetting( settings: CanvasSettings, @@ -161,11 +167,11 @@ export function fixSetting( if (parentKey && !settings[parentKey as keyof CanvasSettings]) { return { enabled: false, value: null }; } - const settingValue: any[] = settings[key]; - if (!settingValue || settingValue.includes(value)) { + const settingValue = settings[key]; + if (!settingValue || settingValue.includes(value as any)) { return { enabled: true, value }; } - if (settingValue.includes(defaultValue)) { + if (settingValue.includes(defaultValue as any)) { return { enabled: true, value: defaultValue }; } const firstValue = settingValue[0]; @@ -177,7 +183,7 @@ export function getSetting(settings: CanvasSet if (parentKey && !settings[parentKey as keyof CanvasSettings]) { return defaultValue; } - const settingValue: any[] = settings[key]; + const settingValue = settings[key]; return settingValue?.[0] || defaultValue; } @@ -216,8 +222,10 @@ type CanvasTypeValidation = { type: CanvasType; languages?: CanvasValidationSetting; components?: CanvasValidationSetting; + formContentTypes?: CanvasValidationSetting; levels?: CanvasValidationSetting; imageTypes?: CanvasValidationSetting; + linkAssetContentTypes?: CanvasValidationSetting; linkContentTypes?: CanvasValidationSetting; linkTypes?: CanvasValidationSetting; listTypes?: CanvasValidationSetting; @@ -246,6 +254,7 @@ type CanvasEditorProperties = { uploadPath?: string; filterPaths?: string[]; actions?: CanvasEditorActions; + types?: CanvasEditorType[]; }; type CanvasEditorActions = { @@ -255,7 +264,17 @@ type CanvasEditorActions = { duplicate?: boolean; editorPanel?: boolean; order?: boolean; - image?: CanvasEditorImageAction[]; +}; + +export type CanvasEditorType = { + type: CanvasType; + + // image + imageActions?: CanvasEditorImageAction[]; + uploadPath?: string; + + // image and asset link + filterPaths?: string[]; }; type CanvasEditorAction = keyof CanvasEditorActions; @@ -273,6 +292,10 @@ function getType(allowedTypes: CanvasAllowedTypesValidation, type: CanvasType) { return allowedTypes?.types?.find((t) => t.type === type); } +function getEditorType(properties: undefined | CanvasEditorProperties, type: CanvasType) { + return properties?.types?.find((t) => t.type === type); +} + function isDecoratorEnabled(allowedTypes: CanvasAllowedTypesValidation, decorator: CanvasDecorator) { const allowedDecorators = getType(allowedTypes, '_fragment')?.decorators?.allowed; const decoratorValidation = allowedDecorators?.find((d) => d.decorator === decorator || d.decorator === '*'); @@ -302,7 +325,7 @@ function isTypeEnabledAndAllowed(allowedTypes: return isEnabled && (!values || (Array.isArray(values) && !!values.length)); } -function getLocalisedValue(dict: string | Record) { +function getLocalisedValue(dict: undefined | string | Record) { if (!dict) { return ''; } @@ -318,9 +341,11 @@ function asArray(item: T) { export function createCanvasSettings(canvasField: CanvasField): CanvasSettings { const allowedTypes = canvasField?.validations?.allowedTypes as CanvasAllowedTypesValidation; - const actions = canvasField?.editor?.properties?.actions; - const imageActions = actions?.image; + const properties = canvasField?.editor?.properties; + const actions = properties?.actions; const image = getType(allowedTypes, '_image'); + const imageEditor = getEditorType(properties, '_image'); + const linkEditor = getEditorType(properties, '_link'); return { 'actions.contentEditable': isActionEnabled(actions, 'contentEditable'), 'actions.deleteItem': isActionEnabled(actions, 'deleteItem'), @@ -340,7 +365,8 @@ export function createCanvasSettings(canvasField: CanvasField): CanvasSettings { 'decorator.superscript': isDecoratorEnabled(allowedTypes, 'superscript'), 'decorator.underline': isDecoratorEnabled(allowedTypes, 'underline'), 'decorator.variable': isDecoratorEnabled(allowedTypes, 'variable'), - 'editor.placeholder': [getLocalisedValue(canvasField?.editor?.properties?.placeholderText || '')], + + 'editor.placeholder': [getLocalisedValue(canvasField?.editor?.properties?.placeholderText)], 'properties.friendlyId': true, 'type.anchor': isTypeEnabled(allowedTypes, '_anchor'), 'type.code': isTypeEnabledAndAllowed(allowedTypes, '_code', 'languages'), @@ -348,10 +374,12 @@ export function createCanvasSettings(canvasField: CanvasField): CanvasSettings { 'type.component': isTypeEnabledAndAllowed(allowedTypes, '_component', 'components'), 'type.component.component': getAllowedValues(allowedTypes, '_component', 'components'), 'type.divider': isTypeEnabled(allowedTypes, '_divider'), + 'type.formContentType': isTypeEnabled(allowedTypes, '_formContentType'), + 'type.formContentType.contentType': getAllowedValues(allowedTypes, '_formContentType', 'formContentTypes'), 'type.heading': isTypeEnabledAndAllowed(allowedTypes, '_heading', 'levels'), 'type.heading.level': getAllowedValues(allowedTypes, '_heading', 'levels'), 'type.image': isTypeEnabled(allowedTypes, '_image'), - 'type.image.actions': imageActions || [], + 'type.image.actions': imageEditor?.imageActions || null, 'type.image.altTextRequired': isTypeEnabled(allowedTypes, '_image') && image?.altTextRequired ? ['required'] : [], 'type.image.altTextRequiredMessage': isTypeEnabled(allowedTypes, '_image') && image?.altTextRequired?.message?.['en-GB'] ? [image.altTextRequired.message['en-GB']] : [], @@ -359,18 +387,26 @@ export function createCanvasSettings(canvasField: CanvasField): CanvasSettings { 'type.image.captionRequiredMessage': isTypeEnabled(allowedTypes, '_image') && image?.captionRequired?.message?.['en-GB'] ? [image.captionRequired.message['en-GB']] : [], - 'type.image.filterPaths': isTypeEnabled(allowedTypes, '_image') ? canvasField?.editor?.properties?.filterPaths || [] : [], + 'type.image.filterPaths': isTypeEnabled(allowedTypes, '_image') ? imageEditor?.filterPaths || canvasField?.editor?.properties?.filterPaths || null : null, 'type.image.minWidth': asArray(isTypeEnabled(allowedTypes, '_image') ? image?.imageDimensions?.minWidth : null), 'type.image.maxWidth': asArray(isTypeEnabled(allowedTypes, '_image') ? image?.imageDimensions?.maxWidth : null), 'type.image.minHeight': asArray(isTypeEnabled(allowedTypes, '_image') ? image?.imageDimensions?.minHeight : null), 'type.image.maxHeight': asArray(isTypeEnabled(allowedTypes, '_image') ? image?.imageDimensions?.maxHeight : null), + 'type.image.uploadPath': + isTypeEnabled(allowedTypes, '_image') && (imageEditor?.uploadPath || canvasField?.editor?.properties?.uploadPath) + ? [(imageEditor?.uploadPath || canvasField?.editor?.properties?.uploadPath) as string] + : null, 'type.image.imageType': getAllowedValues(allowedTypes, '_image', 'imageTypes'), 'type.inlineEntry': isTypeEnabled(allowedTypes, '_inlineEntry'), 'type.inlineEntry.contentType': getAllowedValues(allowedTypes, '_inlineEntry', 'linkContentTypes'), 'type.link': isTypeEnabledAndAllowed(allowedTypes, '_link', 'linkTypes'), 'type.link.linkType': getAllowedValues(allowedTypes, '_link', 'linkTypes'), - 'type.link.contentType': getAllowedValues(allowedTypes, '_inlineEntry', 'linkContentTypes'), + 'type.link.assetContentType': getAllowedValues(allowedTypes, '_link', 'linkAssetContentTypes'), + 'type.link.assetFilterPaths': linkEditor?.filterPaths || null, + 'type.link.contentType': getAllowedValues(allowedTypes, '_link', 'linkContentTypes'), + 'type.liquid': false, + 'type.list': isTypeEnabledAndAllowed(allowedTypes, '_list', 'listTypes'), 'type.list.listType': getAllowedValues(allowedTypes, '_list', 'listTypes'), 'type.panel': isTypeEnabledAndAllowed(allowedTypes, '_panel', 'panelTypes'), diff --git a/packages/html-parser/src/parser/models/decorators/decorator-element.ts b/packages/html-parser/src/parser/models/decorators/decorator-element.ts index 6851cec..f68cfda 100644 --- a/packages/html-parser/src/parser/models/decorators/decorator-element.ts +++ b/packages/html-parser/src/parser/models/decorators/decorator-element.ts @@ -5,7 +5,12 @@ import { Attributes, Element } from '../models'; import { toValue } from '../shared'; export abstract class DecoratorElement extends BaseElement { - constructor(protected type: DecoratorType, name: string, attributes: Attributes, context: Context) { + constructor( + protected type: DecoratorType, + name: string, + attributes: Attributes, + context: Context + ) { super(name, attributes, context); this.popContext = context.setDecorator(type); } diff --git a/packages/html-parser/src/parser/models/elements/a-element.ts b/packages/html-parser/src/parser/models/elements/a-element.ts index 5b70a76..e145247 100644 --- a/packages/html-parser/src/parser/models/elements/a-element.ts +++ b/packages/html-parser/src/parser/models/elements/a-element.ts @@ -5,6 +5,8 @@ import { LinkType, getLinkType } from '../links'; import { Attributes, Element } from '../models'; import { toValue } from '../shared'; +type Link = Exclude['link']; + export class AElement extends BaseElement { private linkType: LinkType; @@ -67,7 +69,7 @@ export class AElement extends BaseElement { private addLink(parent: Element) { const link = this.getLink(); const hasData = link?.sys?.uri || link?.sys?.id || link?.sys?.node?.id; - if (hasData && this.context.hasSetting(['type.link.linkType', link.sys.linkProperties.type])) { + if (hasData && link?.sys && this.context.hasSetting(['type.link.linkType', link.sys.linkProperties.type])) { const children = this.mergeItems(this.children).filter(isInline); const value = this.trimItems(children); @@ -85,8 +87,8 @@ export class AElement extends BaseElement { } } - private getLink(): LinkBlock['properties']['link'] { - const link = tryParse(this.attributes['data-link']); + private getLink(): null | Link { + const link = tryParse(this.attributes['data-link']); if (link) { return link; } diff --git a/packages/html-parser/src/parser/models/elements/aside-element.ts b/packages/html-parser/src/parser/models/elements/aside-element.ts index dd7e7af..997a492 100644 --- a/packages/html-parser/src/parser/models/elements/aside-element.ts +++ b/packages/html-parser/src/parser/models/elements/aside-element.ts @@ -28,12 +28,12 @@ export class AsideElement extends BlockElement { } private getProperties(): PanelBlock['properties'] { - let _panelType: PanelType; + let _panelType: undefined | PanelType; if (this.attributes['class']) { const classList = this.attributes['class'].split(' '); _panelType = PanelTypes.find((panelType) => classList.includes(panelType)); } const { value: panelType } = this.context.fixSetting('type.panel.panelType', _panelType || 'info', 'info'); - return { panelType }; + return !!panelType ? { panelType } : {}; } } diff --git a/packages/html-parser/src/parser/models/elements/blockquote-element.ts b/packages/html-parser/src/parser/models/elements/blockquote-element.ts index 8a1e744..b476495 100644 --- a/packages/html-parser/src/parser/models/elements/blockquote-element.ts +++ b/packages/html-parser/src/parser/models/elements/blockquote-element.ts @@ -6,9 +6,9 @@ import { toValue } from '../shared'; import { FigureElement } from './figure-element'; export class BlockquoteElement extends BlockElement { - private _citation: string; - private _source: string; - private _quoteItem: QuoteBlock; + private _citation: undefined | string; + private _source: undefined | string; + private _quoteItem: undefined | QuoteBlock; constructor(name: string, attributes: Attributes, context: Context) { super(name, attributes, context); @@ -44,6 +44,7 @@ export class BlockquoteElement extends BlockElement { } withCaption(source: string) { + if (this._quoteItem) { let properties = this._quoteItem.properties; if (source) { properties = { @@ -61,6 +62,9 @@ export class BlockquoteElement extends BlockElement { ...this._quoteItem, properties }; + } else { + return undefined; + } } private getProperties(): QuoteBlock['properties'] { diff --git a/packages/html-parser/src/parser/models/elements/div-element.ts b/packages/html-parser/src/parser/models/elements/div-element.ts index 3d03fe6..18fa55b 100644 --- a/packages/html-parser/src/parser/models/elements/div-element.ts +++ b/packages/html-parser/src/parser/models/elements/div-element.ts @@ -1,16 +1,20 @@ -import { ComponentBlock } from '../../../models'; +import { ComponentBlock, FormContentTypeBlock } from '../../../models'; import { Attributes, Element } from '../models'; import { Context, tryParse } from '../context'; import { BlockElement } from '../block-element'; export class DivElement extends BlockElement { private _isComponent: boolean; + private _isForm: boolean; constructor(name: string, attributes: Attributes, context: Context) { super(name, attributes, context); this._isComponent = this.isComponent(); + this._isForm = this.isForm(); if (this._isComponent) { this.popContext = context.setType('_component', this); + } else if (this._isForm) { + this.popContext = context.setType('_formContentType', this); } } @@ -25,6 +29,18 @@ export class DivElement extends BlockElement { value }; parent.append(componentItem); + } else if (this._isForm && this.context.canAddType('_formContentType')) { + const formItem: FormContentTypeBlock = { + type: '_formContentType', + id: this.id(), + properties: this.withFriendlyId({}), + value: { + contentType: { + id: this.getFormId() as string + } + } + }; + parent.append(formItem); } else { super.appendTo(parent); } @@ -39,6 +55,10 @@ export class DivElement extends BlockElement { return !!component && !!value; } + private isForm() { + return !!this.getFormId(); + } + private getComponentProperties(): { component: string; value: any } { const component = this.attributes['data-component']; let isValid = component && this.context.hasSetting(['type.component.component', component]); @@ -49,4 +69,14 @@ export class DivElement extends BlockElement { const value = isValid ? tryParse(this.attributes['data-component-value']) : null; return { component, value }; } + + private getFormId() { + const formId = this.attributes['data-contensis-form-id']; + let isValid = formId && this.context.hasSetting(['type.formContentType.contentType', formId]); + if (isValid) { + const { formContentTypes } = this.context.parserSettings; + isValid = formContentTypes.includes(formId); + } + return isValid ? formId : null; + } } diff --git a/packages/html-parser/src/parser/models/elements/figure-element.ts b/packages/html-parser/src/parser/models/elements/figure-element.ts index 5c5c087..810c2e7 100644 --- a/packages/html-parser/src/parser/models/elements/figure-element.ts +++ b/packages/html-parser/src/parser/models/elements/figure-element.ts @@ -4,13 +4,13 @@ import { Context } from '../context'; import { Attributes, Element } from '../models'; export type WithCaptionElement = { - withCaption: (caption: string) => Block; + withCaption: (caption: string) => undefined | Block; }; export class FigureElement extends BlockElement { - withCaptionElement: WithCaptionElement; + withCaptionElement: undefined | WithCaptionElement; - private _caption: string; + private _caption: undefined | string; constructor(name: string, attributes: Attributes, context: Context) { super(name, attributes, context); @@ -19,10 +19,13 @@ export class FigureElement extends BlockElement { appendTo(parent: Element) { this.popContext(); - if (this.withCaptionElement) { - const item = this.withCaptionElement.withCaption(this._caption); + const item = this.withCaptionElement.withCaption(this._caption || ''); + if (item) { parent.append(item); + } else { + super.appendTo(parent); + } } else { super.appendTo(parent); } diff --git a/packages/html-parser/src/parser/models/elements/heading-element.ts b/packages/html-parser/src/parser/models/elements/heading-element.ts index 54f70f6..ae1a476 100644 --- a/packages/html-parser/src/parser/models/elements/heading-element.ts +++ b/packages/html-parser/src/parser/models/elements/heading-element.ts @@ -28,7 +28,7 @@ export class HeadingElement extends BlockElement { } private getProperties(): HeadingBlock['properties'] { - const { value: level } = this.context.fixSetting('type.heading.level', HEADING_TAGS[this.name], 1); - return { level }; + const { value: level } = this.context.fixSetting('type.heading.level', HEADING_TAGS[this.name as 'h1'], 1); + return !!level ? { level } : {}; } } diff --git a/packages/html-parser/src/parser/models/elements/img-element.ts b/packages/html-parser/src/parser/models/elements/img-element.ts index 347b628..e69f348 100644 --- a/packages/html-parser/src/parser/models/elements/img-element.ts +++ b/packages/html-parser/src/parser/models/elements/img-element.ts @@ -6,7 +6,7 @@ import { FigureElement } from './figure-element'; import { isBase64 } from '../images'; export class ImgElement extends VoidElement { - private _imageItem: ImageBlock; + private _imageItem: undefined | ImageBlock; constructor(name: string, attributes: Attributes, context: Context) { super(name, attributes, context); @@ -22,7 +22,7 @@ export class ImgElement extends VoidElement { const asset = this.context.getImage(path); const imageType = asset ? 'managed' : 'external'; if (this.context.hasSetting(['type.image.imageType', imageType])) { - let transformations: Transformations = undefined; + let transformations: undefined | Transformations = undefined; if (asset) { transformations = toTransformations(queryParams); } @@ -48,12 +48,16 @@ export class ImgElement extends VoidElement { } withCaption(caption: string) { + if (this._imageItem) { return caption ? { ...this._imageItem, value: { ...this._imageItem.value, caption } } : this._imageItem; + } else { + return undefined; + } } } -function toTransformations(search: Record): Transformations { - let t: Transformations = undefined; +function toTransformations(search: Record): undefined | Transformations { + let t: undefined | Transformations = undefined; const width = toNumber(search['w']); const height = toNumber(search['h']); @@ -77,8 +81,8 @@ function toTransformations(search: Record): Transformations { t = t || {}; t.crop = t.crop || {}; - t.crop.width = width; - t.crop.height = height; + t.crop.width = width as number; + t.crop.height = height as number; t.crop.x = x; t.crop.y = y; } diff --git a/packages/html-parser/src/parser/models/elements/list-element.ts b/packages/html-parser/src/parser/models/elements/list-element.ts index 4d16d60..778e13a 100644 --- a/packages/html-parser/src/parser/models/elements/list-element.ts +++ b/packages/html-parser/src/parser/models/elements/list-element.ts @@ -32,9 +32,9 @@ export class ListElement extends BlockElement { } private getProperties(): ListBlock['properties'] { - const { value: listType } = this.context.fixSetting('type.list.listType', LIST_TAGS[this.name], 'unordered'); + const { value: listType } = this.context.fixSetting('type.list.listType', LIST_TAGS[this.name as 'ol'], 'unordered'); const properties: ListBlock['properties'] = { - listType + listType: listType || 'unordered' }; if (listType === 'ordered') { const startAttr = this.attributes.start; diff --git a/packages/html-parser/src/parser/models/elements/p-element.ts b/packages/html-parser/src/parser/models/elements/p-element.ts index 2182f0f..8ebfd38 100644 --- a/packages/html-parser/src/parser/models/elements/p-element.ts +++ b/packages/html-parser/src/parser/models/elements/p-element.ts @@ -8,7 +8,7 @@ export class PElement extends BlockElement { const classList = this.attributes['class']?.split(' '); let paragraphType = classList?.includes('lead') || classList?.includes('lede') ? ('lead' as const) : null; if (paragraphType) { - const { value } = this.context.fixSetting('type.paragraph.paragraphType', paragraphType, null); + const { value } = this.context.fixSetting('type.paragraph.paragraphType', paragraphType, null as any); paragraphType = value; } if (paragraphType) { diff --git a/packages/html-parser/src/parser/models/elements/pre-element.ts b/packages/html-parser/src/parser/models/elements/pre-element.ts index e3a7210..b58784c 100644 --- a/packages/html-parser/src/parser/models/elements/pre-element.ts +++ b/packages/html-parser/src/parser/models/elements/pre-element.ts @@ -4,12 +4,12 @@ import { BlockElement } from '../block-element'; import { FigureElement } from './figure-element'; export class PreElement extends BlockElement { - private _code: string; - private _codeAttributes: Attributes; - private _codeItem: CodeBlock; + private _code: undefined | string; + private _codeAttributes: undefined | Attributes; + private _codeItem: undefined | CodeBlock; appendTo(parent: Element) { - let language = ''; + let language: string | null = null; const isCodeBlock = !!this._codeAttributes && this.context.canAddType('_code'); if (isCodeBlock) { language = this.getLanguage(); @@ -41,11 +41,15 @@ export class PreElement extends BlockElement { } withCaption(caption: string) { + if (this._codeItem) { return caption ? { ...this._codeItem, value: { ...this._codeItem.value, caption } } : this._codeItem; + } else { + return undefined; + } } private getLanguage() { - let language = this.attributes['data-language']; + let language: string | null = this.attributes['data-language']; if (!language && this.attributes['class']) { const classList = this.attributes['class'].split(' '); diff --git a/packages/html-parser/src/parser/models/elements/table-element.ts b/packages/html-parser/src/parser/models/elements/table-element.ts index db8737d..13a1558 100644 --- a/packages/html-parser/src/parser/models/elements/table-element.ts +++ b/packages/html-parser/src/parser/models/elements/table-element.ts @@ -1,14 +1,16 @@ import { Block, - TableBodyBlock, - TableCaptionBlock, - TableBlock, - TableSectionBlock, isTableBody, isTableCaption, isTableFooter, isTableHeader, - isTableRow + isTableRow, + TableBlock, + TableBodyBlock, + TableCaptionBlock, + TableFooterBlock, + TableHeaderBlock, + TableSectionBlock } from '../../../models'; import { BlockElement } from '../block-element'; import { Context } from '../context'; @@ -41,7 +43,7 @@ export class TableElement extends BlockElement { tableBody = ensureCells(tableBody, cellCount, newId); tableFooter = ensureCells(tableFooter, cellCount, newId); - const value = [caption, tableHeader, tableBody, tableFooter].filter((c) => !!c); + const value = [caption, tableHeader, tableBody, tableFooter].filter((c) => !!c) as (TableCaptionBlock | TableHeaderBlock | TableBodyBlock | TableFooterBlock)[]; const table: TableBlock = { type: '_table', id: this.id(), @@ -66,8 +68,12 @@ function createTableBody(children: Block[], newId: () => string): TableBodyBlock if (!tableBody.properties) { tableBody.properties = item.properties; } + if (item.value) { + tableBody.value = tableBody.value || []; tableBody.value.push(...item.value); + } } else if (isTableRow(item)) { + tableBody.value = tableBody.value || []; tableBody.value.push(item); } }); @@ -82,7 +88,7 @@ function createTableCaption(newId: () => string): TableCaptionBlock { }; } -function ensureCells(section: T, count: number, newId: () => string): T { +function ensureCells(section: undefined | T, count: number, newId: () => string): undefined | T { if (section) { const type = isTableHeader(section) ? '_tableHeaderCell' : '_tableCell'; section.value = section.value || []; @@ -107,7 +113,7 @@ function ensureCells(section: T, count: number, new return section; } -function getCellCount(...sections: TableSectionBlock[]): number { +function getCellCount(...sections: (undefined | TableSectionBlock)[]): number { const rows = sections.map((section) => section?.value || []).flat(); const cellCounts = rows.map((block) => (Array.isArray(block.value) ? block.value.length : 0)); return Math.max(...cellCounts); diff --git a/packages/html-parser/src/parser/models/links.ts b/packages/html-parser/src/parser/models/links.ts index e897336..2f8b2d8 100644 --- a/packages/html-parser/src/parser/models/links.ts +++ b/packages/html-parser/src/parser/models/links.ts @@ -4,7 +4,7 @@ import { Attributes } from './models'; export type LinkType = '_anchor' | '_link' | '_inlineEntry'; -export type LinkDataType = CanvasSettings['type.link.linkType'][0]; +export type LinkDataType = NonNullable[0]; export function getLinkType(attributes: Attributes): LinkType { if (attributes['class']) { diff --git a/packages/html-parser/src/parser/models/models.ts b/packages/html-parser/src/parser/models/models.ts index 54a6e21..f2591c9 100644 --- a/packages/html-parser/src/parser/models/models.ts +++ b/packages/html-parser/src/parser/models/models.ts @@ -15,6 +15,7 @@ export type VoidFn = () => void; export type ParserSettings = { components: string[]; + formContentTypes: string[]; project: Project; rootUrl: string; projectUuid: string; diff --git a/packages/html-parser/src/parser/models/schema.ts b/packages/html-parser/src/parser/models/schema.ts index 9bd80b1..b6de336 100644 --- a/packages/html-parser/src/parser/models/schema.ts +++ b/packages/html-parser/src/parser/models/schema.ts @@ -8,8 +8,10 @@ const DEFAULT_ALLOWED_CHILDREN: AllowedTypes = { _code: isInlineType('_code'), _component: isInlineType('_component'), _divider: isInlineType('_divider'), + _formContentType: isInlineType('_formContentType'), _heading: isInlineType('_heading'), _image: isInlineType('_image'), + _liquid: isInlineType('_liquid'), _list: isInlineType('_list'), _listItem: isInlineType('_list'), _panel: isInlineType('_panel'), @@ -33,8 +35,10 @@ const NO_CHILDREN: AllowedTypes = { _code: false, _component: false, _divider: false, + _formContentType: false, _heading: false, _image: false, + _liquid: false, _list: false, _listItem: false, _panel: false, @@ -58,8 +62,10 @@ export const ROOT_CHILDREN: AllowedTypes = { _code: true, _component: true, _divider: true, + _formContentType: true, _heading: true, _image: true, + _liquid: true, _list: true, _listItem: false, _panel: true, @@ -83,8 +89,10 @@ export const DECORATOR_CHILDREN: AllowedTypes = { _code: false, _component: false, _divider: false, + _formContentType: false, _heading: false, _image: false, + _liquid: true, _list: false, _listItem: false, _panel: false, @@ -108,8 +116,10 @@ export const ALLOWED_CHILDREN: AllowedChildren = { _code: DEFAULT_ALLOWED_CHILDREN, _component: NO_CHILDREN, _divider: NO_CHILDREN, + _formContentType: NO_CHILDREN, _heading: DEFAULT_ALLOWED_CHILDREN, _image: NO_CHILDREN, + _liquid: { ...NO_CHILDREN, _fragment: true }, _list: { ...NO_CHILDREN, _listItem: true }, _listItem: { ...DEFAULT_ALLOWED_CHILDREN, _list: true }, _panel: DEFAULT_ALLOWED_CHILDREN, @@ -157,8 +167,10 @@ export function settingsToTypes(settings: CanvasSettings): AllowedTypes { _code: settings['type.code'], _component: settings['type.component'], _divider: settings['type.divider'], + _formContentType: settings['type.formContentType'], _heading: settings['type.heading'], _image: settings['type.image'], + _liquid: settings['type.liquid'], _list: settings['type.list'], _listItem: settings['type.list'], _panel: settings['type.panel'], diff --git a/packages/html-parser/src/parser/models/shared.ts b/packages/html-parser/src/parser/models/shared.ts index 2011b58..e0324c8 100644 --- a/packages/html-parser/src/parser/models/shared.ts +++ b/packages/html-parser/src/parser/models/shared.ts @@ -36,7 +36,7 @@ function isEquivalentFragment(fragment1: FragmentBlock, fragment2: FragmentBlock if (decoratorCount1 === 0) { return true; } - const combinedDecorators = [...new Set([...fragment1.properties.decorators, ...fragment2.properties.decorators])]; + const combinedDecorators = [...new Set([...(fragment1.properties?.decorators || []), ...(fragment2.properties?.decorators || [])])]; return combinedDecorators.length === decoratorCount1; } @@ -48,7 +48,10 @@ function mergeLists(blocks: T[]): T[] { const lastListType = lastItem.properties?.listType || 'unordered'; const listType = item.properties?.listType || 'unordered'; if (lastListType === listType && !lastItem.properties?.id && !item?.properties?.id) { + if (item.value) { + lastItem.value = lastItem.value || []; lastItem.value.push(...item.value); + } pushCurrentItem = false; } } @@ -142,7 +145,7 @@ export function wrapInline(blocks: Block[], createBlock: () => BlockBlock): Bloc } return prev; }, - { items: [], lastBlock: null } as { items: Block[]; lastBlock: BlockBlock } + { items: [], lastBlock: null } as { items: Block[]; lastBlock: null | BlockBlock } ); items = items .filter((item) => isVoid(item) || !!item.value) @@ -222,7 +225,7 @@ function trimBoundary(blocks: T[], trimString: (s: string) => s value = trimString(value); } if (Array.isArray(value) && !value.length) { - value = null; + value = undefined; } if (value) { shouldTrim = false; diff --git a/packages/markdown/src/markdown-renderer.ts b/packages/markdown/src/markdown-renderer.ts index 8951c10..acd7379 100644 --- a/packages/markdown/src/markdown-renderer.ts +++ b/packages/markdown/src/markdown-renderer.ts @@ -9,6 +9,7 @@ import { ImageBlock, InlineEntryBlock, LinkBlock, + LiquidBlock, ListBlock, ListItemBlock, PanelBlock, @@ -92,6 +93,11 @@ const link = createBlockRenderer(({ block, contents }) => { return href ? `[${contents}](${href})` : contents; }); +const liquid = createBlockRenderer( + ({ contents }) => contents, + ({ block }) => block?.value || '' +); + const list = function (props: RenderBlockProps, ...children: string[]) { const { block, context, renderers, encode } = props; const parentList = !!context.list; @@ -194,6 +200,7 @@ const createRenderer = createRendererFactory( _image: image, _inlineEntry: inlineEntry, _link: link, + _liquid: liquid, _list: list, _listItem: listItem, _quote: quote, @@ -231,12 +238,16 @@ const text = htmlEncode; export { anchor, code, - component, createRenderer, divider, + component, + createRenderer, + divider, emphasis, fragment, heading, image, - inlineCode, inlineDelete, inlineEntry, + inlineCode, + inlineDelete, + inlineEntry, insert, keyboard, lineBreak, @@ -258,7 +269,9 @@ export { tableFooter, tableHeader, tableHeaderCell, - tableRow, text, underline, + tableRow, + text, + underline, variable }; diff --git a/packages/react/src/renderer.tsx b/packages/react/src/renderer.tsx index ab7ac26..9aab82d 100644 --- a/packages/react/src/renderer.tsx +++ b/packages/react/src/renderer.tsx @@ -12,6 +12,7 @@ import { ImageBlock, InlineEntryBlock, LinkBlock, + LiquidBlock, ListBlock, ListItemBlock, PanelBlock, @@ -426,6 +427,16 @@ export function Link(props: RenderBlockPropsWithChildren) { Link.Children = RenderBlockChildrenFactory(); +export function Liquid(props: RenderBlockPropsWithChildren) { + return ( + } /> + ); +} + +Liquid.Children = function (props: RenderBlockProps) { + return (<>{props.block?.value}); +}; + export function List(props: RenderBlockPropsWithChildren) { const isOrdered = (props.block?.properties?.listType === 'ordered'); const attributes = getAttributes(props, { @@ -512,17 +523,6 @@ Quote.Children = function (props: RenderBlockProps) { ) : () ); - // return (); - // return ( - // }> - //

- // - //

- // {source()}}> - //
{source()} {citation()}
- //
- //
- // ); }; @@ -779,7 +779,6 @@ export function Variable(props: RenderDecoratorPropsWithChildren) { Variable.Children = DecoratorChildren; - const BLOCK_RENDERERS: BlockRenderers = { '_anchor': Anchor, '_code': CodeWithCaption, @@ -791,6 +790,7 @@ const BLOCK_RENDERERS: BlockRenderers = { '_image': ImageWithCaption, '_inlineEntry': InlineEntry, '_link': Link, + '_liquid': Liquid, '_list': List, '_listItem': ListItem, '_panel': Panel, diff --git a/packages/solidjs/src/renderer.tsx b/packages/solidjs/src/renderer.tsx index 66236a4..93c13f6 100644 --- a/packages/solidjs/src/renderer.tsx +++ b/packages/solidjs/src/renderer.tsx @@ -1,10 +1,19 @@ import { - AnchorBlock, CodeBlock, ComponentBlock, Block, DecoratorType, DividerBlock, + AnchorBlock, + Block, + CodeBlock, ComponentBlock, + DecoratorType, DividerBlock, + FormContentTypeBlock, FragmentBlock, HeadingBlock, ImageBlock, InlineEntryBlock, LinkBlock, - ListBlock, ListItemBlock, PanelBlock, ParagraphBlock, QuoteBlock, TableBodyBlock, - TableCaptionBlock, TableCellBlock, TableBlock, TableFooterBlock, - TableHeaderCellBlock, TableHeaderBlock, TableRowBlock, - FormContentTypeBlock + LiquidBlock, + ListBlock, ListItemBlock, PanelBlock, ParagraphBlock, QuoteBlock, + TableBlock, + TableBodyBlock, + TableCaptionBlock, TableCellBlock, + TableFooterBlock, + TableHeaderBlock, + TableHeaderCellBlock, + TableRowBlock } from '@contensis/canvas-types'; import { For, JSX, Match, Show, Switch, createContext, splitProps, useContext } from 'solid-js'; import { Dynamic } from 'solid-js/web'; @@ -375,6 +384,16 @@ export function Link(props: RenderBlockPropsWithChildren) { Link.Children = RenderBlockChildrenFactory(); +export function Liquid(props: RenderBlockPropsWithChildren) { + return ( + } /> + ); +} + +Liquid.Children = function (props: RenderBlockProps) { + return (<>{props.block?.value}); +}; + export function List(props: RenderBlockPropsWithChildren) { const isOrdered = () => (props.block?.properties?.listType === 'ordered'); const attributes = getAttributes(props, () => ({ @@ -722,7 +741,6 @@ export function Variable(props: RenderDecoratorPropsWithChildren) { Variable.Children = DecoratorChildren; - const BLOCK_RENDERERS: BlockRenderers = { '_anchor': Anchor, '_code': CodeWithCaption, @@ -734,6 +752,7 @@ const BLOCK_RENDERERS: BlockRenderers = { '_image': ImageWithCaption, '_inlineEntry': InlineEntry, '_link': Link, + '_liquid': Liquid, '_list': List, '_listItem': ListItem, '_panel': Panel, diff --git a/packages/text/src/text-renderer.ts b/packages/text/src/text-renderer.ts index 2ea7f00..c77e70a 100644 --- a/packages/text/src/text-renderer.ts +++ b/packages/text/src/text-renderer.ts @@ -3,26 +3,27 @@ import { CodeBlock, ComponentBlock, DividerBlock, + FormContentTypeBlock, HeadingBlock, ImageBlock, InlineEntryBlock, LinkBlock, + LiquidBlock, ListBlock, ListItemBlock, PanelBlock, ParagraphBlock, QuoteBlock, + TableBlock, TableBodyBlock, TableCaptionBlock, TableCellBlock, - TableBlock, TableFooterBlock, - TableHeaderCellBlock, TableHeaderBlock, - TableRowBlock, - FormContentTypeBlock + TableHeaderCellBlock, + TableRowBlock } from '@contensis/canvas-types'; -import { fragment, renderBlocks, createDecoratorRenderer, createBlockRenderer, createRendererFactory, RenderBlockProps, getContents } from './renderer'; +import { createBlockRenderer, createDecoratorRenderer, createRendererFactory, fragment, getContents, RenderBlockProps, renderBlocks } from './renderer'; const anchor = createBlockRenderer(({ contents }) => contents); const code = createBlockRenderer( @@ -67,6 +68,11 @@ const inlineEntry = createBlockRenderer( const link = createBlockRenderer(({ contents }) => contents); +const liquid = createBlockRenderer( + ({ contents }) => contents, + ({ block, encode }) => encode(block?.value) +); + const list = function (props: RenderBlockProps, ...children: string[]) { const { block, context, renderers, encode } = props; const parentList = !!context.list; @@ -152,6 +158,7 @@ const createRenderer = createRendererFactory( _image: image, _inlineEntry: inlineEntry, _link: link, + _liquid: liquid, _list: list, _listItem: listItem, _quote: quote, @@ -188,12 +195,13 @@ export { anchor, code, component, - inlineDelete, + createRenderer, divider, emphasis, heading, image, inlineCode, + inlineDelete, inlineEntry, insert, keyboard, @@ -218,6 +226,5 @@ export { tableHeaderCell, tableRow, underline, - variable, - createRenderer + variable }; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 940e31a..12b7cb2 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -116,6 +116,7 @@ type Block = | ImageBlock | InlineEntryBlock | LinkBlock + | LiquidBlock | ListBlock | ListItemBlock | PanelBlock @@ -183,7 +184,7 @@ type FormContentTypeBlock = { }; value?: { contentType?: FormContentType; - } + }; }; type FragmentBlock = { @@ -235,6 +236,18 @@ type LinkBlock = { }; }; +type LiquidType = 'tag' | 'variable'; + +type LiquidBlock = { + type: '_liquid'; + id: string; + value?: string; + properties?: { + id?: string; + type?: LiquidType; + }; +}; + type ListType = 'ordered' | 'unordered'; type ListBlock = { @@ -378,6 +391,7 @@ export type { ImageBlock, InlineEntryBlock, LinkBlock, + LiquidBlock, ListBlock, ListItemBlock, PanelBlock,