From c52ca6a50efe8a2e20d413e038fe571fef05098f Mon Sep 17 00:00:00 2001 From: Christian Westgaard Date: Tue, 12 Nov 2024 13:54:50 +0100 Subject: [PATCH] wip #1500 RenderableComponent should not have config --- src/jest/server/DataFetcher.test.ts | 641 ++++++++++-------- src/jest/server/data.ts | 53 +- src/jest/server/mockXP.ts | 60 +- .../lib/enonic/react4xp/DataFetcher.ts | 130 ++-- .../resources/lib/enonic/react4xp/React4xp.ts | 3 +- 5 files changed, 532 insertions(+), 355 deletions(-) diff --git a/src/jest/server/DataFetcher.test.ts b/src/jest/server/DataFetcher.test.ts index bcac08ad..6e955cad 100644 --- a/src/jest/server/DataFetcher.test.ts +++ b/src/jest/server/DataFetcher.test.ts @@ -1,27 +1,13 @@ import type { Component, - // Part, - // PartComponent, Request, } from '@enonic-types/core'; -// import type { -// ListDynamicSchemasParams, -// MixinSchema, -// } from '@enonic-types/lib-schema'; -// import type {RegionsProps} from '../../src/Regions'; -// import type {MacroComponent} from '../../src/types'; -// import type {InfoPanelProps} from './InfoPanel'; import type { - // DecoratedLayoutComponent, - DecoratedPageComponent, - // DecoratedPartComponent + // RenderableLayoutComponent, + RenderablePageComponent, + // RenderablePartComponent } from '@enonic/react-components'; -// import { -// ComponentRegistry, -// XpComponent, -// // XpRegion -// } from '@enonic/react-components'; import { // beforeAll, // afterAll, @@ -29,23 +15,12 @@ import { expect, test as it } from '@jest/globals'; -// import {render} from '@testing-library/react' -// import toDiffableHtml from 'diffable-html'; -// import {print, stringify} from 'q-i'; -// import * as React from 'react'; +import {stringify} from 'q-i'; //────────────────────────────────────────────────────────────────────────────── // SRC imports //────────────────────────────────────────────────────────────────────────────── -// import Regions from '../../src/Regions'; -// import Page from '../../src/Page'; -// import {RichText} from '../../src/RichText'; -// import {replaceMacroComments} from '../../src/replaceMacroComments'; -import { - DataFetcher, - // processComponents, -} from '/lib/enonic/react4xp/DataFetcher'; -// import {InfoPanel} from './InfoPanel'; +import { DataFetcher } from '/lib/enonic/react4xp/DataFetcher'; //────────────────────────────────────────────────────────────────────────────── // TEST imports @@ -53,70 +28,21 @@ import { import { DEFAULT_PAGE_DESCRIPTOR, EXAMPLE_PART_DESCRIPTOR, - LAYOUT_COMPONENT, - LAYOUT_FRAGMENT_CONTENT_ID, - LAYOUT_FRAGMENT_CONTENT, + // LAYOUT_COMPONENT, + // LAYOUT_FRAGMENT_CONTENT_ID, + // LAYOUT_FRAGMENT_CONTENT, PAGE_COMPONENT, PAGE_CONTENT, - PART_COMPONENT, - PART_FRAGMENT_CONTENT_ID, - PART_FRAGMENT_CONTENT, - PROCESSED_HTML, - TEXT_FRAGMENT_CONTENT_ID, - TEXT_FRAGMENT_CONTENT, + // PART_COMPONENT, + // PART_FRAGMENT_CONTENT_ID, + // PART_FRAGMENT_CONTENT, + // PROCESSED_HTML, + // TEXT_FRAGMENT_CONTENT_ID, + // TEXT_FRAGMENT_CONTENT, TWO_COLUMNS_LAYOUT_DESCRIPTOR, } from './data'; -import { - LAYOUT_SCHEMA, - MIXIN_SCHEMAS, - PART_SCHEMA, - PAGE_SCHEMA, -} from './schema'; -// import {DefaultPage} from './DefaultPage'; -// import {ExamplePart} from './ExamplePart'; -// import {TwoColumnLayout} from './TwoColumnLayout'; -const dataFetcher = new DataFetcher( - // { - // getComponentSchema: ({ - // // key, - // type, - // }) => { - // if (type === 'PART') return PART_SCHEMA; - // if (type === 'LAYOUT') return LAYOUT_SCHEMA; - // return PAGE_SCHEMA; - // }, - // // @ts-expect-error - // getContentByKey: ({key}) => { - // if (key === LAYOUT_FRAGMENT_CONTENT_ID) { - // return LAYOUT_FRAGMENT_CONTENT; - // } - // if (key === PART_FRAGMENT_CONTENT_ID) { - // return PART_FRAGMENT_CONTENT; - // } - // if (key === TEXT_FRAGMENT_CONTENT_ID) { - // return TEXT_FRAGMENT_CONTENT; - // } - // console.error("getContentByKey:", key); - // return undefined; - // }, - // listSchemas: ({ - // application: _application, - // type, - // }: ListDynamicSchemasParams) => { - // if (type === 'MIXIN') { - // return MIXIN_SCHEMAS as MixinSchema[]; - // } - // // ContentSchemaType[] - // // XDataSchema[] - // throw new Error(`listSchemas: type: ${type} not mocked.`); - // }, - // processHtml: ({ value }) => { - // // console.info("processHtml:", value); - // return PROCESSED_HTML; - // }, -// } -); +const dataFetcher = new DataFetcher(); dataFetcher.addLayout(TWO_COLUMNS_LAYOUT_DESCRIPTOR, { toProps: ({ component, @@ -174,192 +100,363 @@ dataFetcher.addPart(EXAMPLE_PART_DESCRIPTOR, { }, }); -// const componentRegistry = new ComponentRegistry; -// componentRegistry.addMacro('info', { -// View: InfoPanel -// }); -// componentRegistry.addPart(EXAMPLE_PART_DESCRIPTOR, { -// View: ExamplePart -// }); -// componentRegistry.addLayout(TWO_COLUMNS_LAYOUT_DESCRIPTOR, { -// View: TwoColumnLayout -// }); -// componentRegistry.addPage(DEFAULT_PAGE_DESCRIPTOR, { -// View: DefaultPage -// }); +const renderablePageComponent = dataFetcher.process({ + component: PAGE_COMPONENT as Component, + content: PAGE_CONTENT, + request: {} as Request, +}) as RenderablePageComponent; + +const {components} = renderablePageComponent.props.regions['main']; +// console.info('components:%s', stringify(components, { maxItems: Infinity })); +const textComponent = components[0]; +const textComponentFragment = components[1]; +const partComponent = components[2]; +const partComponentFragment = components[3]; +const layoutComponent = components[4]; +const layoutComponentFragment = components[5]; -describe('processComponents', () => { +describe('DataFetcher', () => { - // it('is able to process a part component', () => { -// const processedComponent = dataFetcher.process({ -// component: PART_COMPONENT as Component, -// content: PAGE_CONTENT, -// request: {} as Request, -// }); -// // print(processedComponent, { maxItems: Infinity }); -// expect(processedComponent.props).toEqual({ -// data: { -// processedHtml: '', -// macros: [ -// { -// config: { -// info: { -// header: 'Header', -// body: 'Text' -// } -// }, -// ref: '1', -// name: 'info', -// descriptor: 'whatever:info' -// } -// ] -// } -// }); -// // const element = render().container; -// // expect(toDiffableHtml(element.outerHTML)).toEqual(toDiffableHtml(` -// //
-// //
-// //
-// //
-// //
-// // -// // -// // -// // Header -// // -// // Text -// //
-// //
-// //
-// //
-// //
-// // `)); -// }); + it('the page component should not have a config property', () => { + // @ts-expect-error config is not defined + expect(renderablePageComponent.config).toBeUndefined(); + }); - // it('is able to process a layout component', () => { - // const processedComponent = dataFetcher.process({ - // component: LAYOUT_COMPONENT as Component, - // content: PAGE_CONTENT, - // request: {} as Request, - // }) as DecoratedLayoutComponent; - // // print(processedComponent, { maxItems: Infinity }); - // // expect(processedComponent.props).toEqual({ - // // data: { - // // processedHtml: '', - // // macros: [ - // // { - // // config: { - // // info: { - // // header: 'Header', - // // body: 'Text' - // // } - // // }, - // // ref: '1', - // // name: 'info', - // // descriptor: 'whatever:info' - // // } - // // ] - // // } - // // }); - // const element = render().container; - // // console.debug(toDiffableHtml(element.outerHTML)); - // expect(toDiffableHtml(element.outerHTML)).toEqual(toDiffableHtml(` - //
- //
- //
- //
- //
- //
- // - // - // - // Header - // - // Text - //
- //
- //
- //
- //
- //
- //
- //
- // `)); - // }); + it('is able to process a text component', () => { + // console.info('textComponent:%s', stringify(textComponent, { maxItems: Infinity })); + expect(textComponent.props).toEqual({ + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + }, + // mode: 'edit' // TODO + }); + }); - it('is able to process a page component', () => { - const decoratedPageComponent = dataFetcher.process({ - component: PAGE_COMPONENT as Component, - content: PAGE_CONTENT, - request: {} as Request, - }) as DecoratedPageComponent; - // print(decoratedPageComponent, { maxItems: Infinity }); - // expect(decoratedPageComponent.props).toEqual({ - // data: { - // processedHtml: '', - // macros: [ - // { - // config: { - // info: { - // header: 'Header', - // body: 'Text' - // } - // }, - // ref: '1', - // name: 'info', - // descriptor: 'whatever:info' - // } - // ] - // } - // }); - // const element = render().container; - // // console.debug(toDiffableHtml(element.outerHTML)); - // expect(toDiffableHtml(element.outerHTML)).toEqual(toDiffableHtml(` - //
- //
- //
- //
- //
- //
- // - // - // - // Header - // - // Text - //
- //
- //
- //
- //
- //
- // `)); + it('is able to process a text fragment component', () => { + // console.info('textComponentFragment:%s', stringify(textComponentFragment, { maxItems: Infinity })); + expect(textComponentFragment.props).toEqual({ + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + }, + // mode: 'edit' // TODO + }); + }); + + it('is able to process a part component', () => { + // console.info('partComponent:%s', stringify(partComponent, { maxItems: Infinity })); + expect(partComponent.props).toEqual({ + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + }, + // mode: 'edit' // TODO + }); + }); + + it('the part component should not have a config property', () => { + expect(partComponent.config).toBeUndefined(); + }); + + it('is able to process a part fragment component', () => { + // console.info('partComponentFragment:%s', stringify(partComponentFragment, { maxItems: Infinity })); + expect(partComponentFragment.props).toEqual({ + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + }, + // mode: 'edit' // TODO + }); + }); + + it('the part fragment component should not have a config property', () => { + expect(partComponentFragment.config).toBeUndefined(); + }); + + it('is able to process a layout component', () => { + // console.info('layoutComponent:%s', stringify(layoutComponent, { maxItems: Infinity })); + expect(layoutComponent.props).toEqual({ + regions: { + left: { + components: [ + { + path: '/main/4/left/0', + type: 'text', + text: '

[info header="Header"]Text[/info]

', + props: { + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + } + } + }, + { + type: 'text', + text: '

[info header="Header"]Text[/info]

', + path: '/main/1', + props: { + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + }, + mode: undefined + } + } + ], + name: 'left' + }, + right: { + components: [ + { + descriptor: 'com.enonic.app.react4xp:example', + path: '/main/4/right/0', + type: 'part', + props: { + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + } + } + }, + { + descriptor: 'com.enonic.app.react4xp:example', + path: '/main/3', + type: 'part', + props: { + data: { + processedHtml: '', + macros: [ + { + config: { + info: { + header: 'Header', + body: 'Text' + } + }, + ref: '1', + name: 'info', + descriptor: 'whatever:info' + } + ] + } + } + } + ], + name: 'right' + } + } + }); + }); + + it('the layout component should not have a config property', () => { + // console.info('layoutComponent:%s', stringify(layoutComponent, { maxItems: Infinity })); + expect(layoutComponent.config).toBeUndefined(); + }); + + it("is able to process a layout component fragment", () => { + // console.info("layoutComponentFragment:%s", stringify(layoutComponentFragment, { maxItems: Infinity })); + expect(layoutComponentFragment.props).toEqual({ + regions: { + left: { + components: [ + { + path: "/left/0", + type: "text", + text: '

[info header="Header"]Text[/info]

', + props: { + data: { + processedHtml: + '', + macros: [ + { + config: { + info: { + header: "Header", + body: "Text", + }, + }, + ref: "1", + name: "info", + descriptor: "whatever:info", + }, + ], + }, + mode: undefined, + }, + }, + { + type: "text", + text: '

[info header="Header"]Text[/info]

', + // path: "/left/1", // TODO + path: "/main/1", + props: { + data: { + processedHtml: + '', + macros: [ + { + config: { + info: { + header: "Header", + body: "Text", + }, + }, + ref: "1", + name: "info", + descriptor: "whatever:info", + }, + ], + }, + mode: undefined, + }, + }, + ], + name: "left", + }, + right: { + components: [ + { + descriptor: "com.enonic.app.react4xp:example", + path: "/right/0", + type: "part", + props: { + data: { + processedHtml: + '', + macros: [ + { + config: { + info: { + header: "Header", + body: "Text", + }, + }, + ref: "1", + name: "info", + descriptor: "whatever:info", + }, + ], + }, + }, + }, + { + descriptor: "com.enonic.app.react4xp:example", + // path: "/right/1", // TODO + path: "/main/3", + type: "part", + props: { + data: { + processedHtml: + '', + macros: [ + { + config: { + info: { + header: "Header", + body: "Text", + }, + }, + ref: "1", + name: "info", + descriptor: "whatever:info", + }, + ], + }, + }, + }, + ], + name: "right", + }, + }, + }); + }); + + it('the layout component fragment should not have a config property', () => { + expect(layoutComponentFragment.config).toBeUndefined(); }); }); diff --git a/src/jest/server/data.ts b/src/jest/server/data.ts index 73029e3f..5f7005d0 100644 --- a/src/jest/server/data.ts +++ b/src/jest/server/data.ts @@ -56,20 +56,20 @@ export const LAYOUT_FRAGMENT_CONTENT_ID = '8c926279-39bd-4bff-a502-ffe921b95ada' export const PART_FRAGMENT_CONTENT_ID = '6a71fd9e-f9fc-4395-8954-1d67a5e35bf3'; export const TEXT_FRAGMENT_CONTENT_ID = 'a641b21d-1af3-4559-a936-9e1ab71a19c4'; -export const PART_FRAGMENT_COMPONENT: FragmentComponent = { +export const PART_COMPONENT_FRAGMENT: FragmentComponent = { path: "/main/0", type: "fragment", fragment: PART_FRAGMENT_CONTENT_ID, }; -export const TEXT_FRAGMENT_COMPONENT: FragmentComponent = { +export const TEXT_COMPONENT_FRAGMENT: FragmentComponent = { path: "/main/1", type: "fragment", fragment: TEXT_FRAGMENT_CONTENT_ID, }; -export const LAYOUT_FRAGMENT_COMPONENT: FragmentComponent = { - path: "/main/0", +export const LAYOUT_COMPONENT_FRAGMENT: FragmentComponent = { + path: "/main/5", type: "fragment", fragment: LAYOUT_FRAGMENT_CONTENT_ID, }; @@ -101,19 +101,26 @@ export const LAYOUT_FRAGMENT_CONTENT = { regions: { left: { components: [ + {...TEXT_COMPONENT, path: "/left/0"}, { - path: "/left/0", + path: "/left/1", type: "fragment", fragment: TEXT_FRAGMENT_CONTENT_ID, }, + ], + name: "left", + }, + right: { + components: [ + {...PART_COMPONENT, path: "/right/0"}, { - path: "/left/1", + path: "/right/1", type: "fragment", fragment: PART_FRAGMENT_CONTENT_ID, }, ], - name: "left", - }, + name: "right", + } }, }, attachments: {}, @@ -170,26 +177,23 @@ export const PART_FRAGMENT_CONTENT = { }; export const LAYOUT_COMPONENT: LayoutComponent = { - path: '/main/0', + path: '/main/4', type: 'layout', descriptor: TWO_COLUMNS_LAYOUT_DESCRIPTOR, config: CONFIG, regions: { left: { components: [ - // TEXT_COMPONENT, - // TEXT_FRAGMENT_COMPONENT, - {...PART_COMPONENT, path: '/main/0/left/0'}, - // PART_FRAGMENT_COMPONENT, + {...TEXT_COMPONENT, path: '/main/4/left/0'}, + {...TEXT_COMPONENT_FRAGMENT, path: '/main/4/left/1'}, + ], name: "left", }, right: { components: [ - // { - // ...PART_FRAGMENT_COMPONENT, - // path: "/main/0/right/0", - // } + {...PART_COMPONENT, path: '/main/4/right/0'}, + {...PART_COMPONENT_FRAGMENT, path: '/main/4/right/1'}, ], name: "right", }, @@ -206,15 +210,12 @@ export const PAGE_COMPONENT: PageComponent = { regions: { main: { components: [ - // TEXT_COMPONENT, - // TEXT_FRAGMENT_COMPONENT, - // LAYOUT_COMPONENT, - {...PART_COMPONENT, path: "/main/0"}, - // LAYOUT_FRAGMENT_COMPONENT, - // { - // ...PART_FRAGMENT_COMPONENT, - // path: "/main/1", - // } + {...TEXT_COMPONENT, path: "/main/0"}, + {...TEXT_COMPONENT_FRAGMENT, path: "/main/1"}, + {...PART_COMPONENT, path: "/main/2"}, + {...PART_COMPONENT_FRAGMENT, path: "/main/3"}, + {...LAYOUT_COMPONENT, path: "/main/4"}, + {...LAYOUT_COMPONENT_FRAGMENT, path: "/main/5"}, ], name: "main", }, diff --git a/src/jest/server/mockXP.ts b/src/jest/server/mockXP.ts index d619157c..f39451e0 100644 --- a/src/jest/server/mockXP.ts +++ b/src/jest/server/mockXP.ts @@ -1,3 +1,6 @@ +// import type { +// Content, +// } from '@enonic-types/core'; import type { get as getContentByKeyType, } from '@enonic-types/lib-content'; @@ -35,17 +38,18 @@ import { // DEFAULT_PAGE_DESCRIPTOR, // EXAMPLE_PART_DESCRIPTOR, // LAYOUT_COMPONENT, - // LAYOUT_FRAGMENT_CONTENT_ID, - // LAYOUT_FRAGMENT_CONTENT, + LAYOUT_FRAGMENT_CONTENT_ID, + LAYOUT_FRAGMENT_CONTENT, // PAGE_COMPONENT, // PAGE_CONTENT, // PART_COMPONENT, - // PART_FRAGMENT_CONTENT_ID, - // PART_FRAGMENT_CONTENT, + PART_FRAGMENT_CONTENT_ID, + PART_FRAGMENT_CONTENT, PROCESSED_HTML, - // TEXT_FRAGMENT_CONTENT_ID, - // TEXT_FRAGMENT_CONTENT, + TEXT_FRAGMENT_CONTENT_ID, + TEXT_FRAGMENT_CONTENT, // TWO_COLUMNS_LAYOUT_DESCRIPTOR, + // UNPROCESSED_HTML, } from './data'; import { LAYOUT_SCHEMA, @@ -60,7 +64,7 @@ const SITE_NAME = 'mysite'; export const server = new Server({ - loglevel: 'debug' + loglevel: 'warn' }).createProject({ projectName: PROJECT_NAME }).setContext({ @@ -93,18 +97,56 @@ const siteContent = libContent.create({ parentPath: '/', }); +// const textComponentFragmentContent = libContent.create({ +// // _path: "/mysite/fragment-info-header-header-text-info", +// // attachments: {}, +// // createdTime: "2024-11-05T09:13:48.488533Z", +// // creator: "user:system:su", +// // hasChildren: false, +// // modifiedTime: "2024-11-05T09:13:48.568588Z", +// // modifier: "user:system:su", +// // owner: "user:system:su", +// // publish: {}, +// // valid: true, +// childOrder: "modifiedtime DESC", +// contentType: 'portal:fragment', +// data: {}, +// displayName: '[info header="Header"]Text[/info]', +// // @ts-expect-error TODO Support in @enonic/mock-xp +// fragment: { +// type: 'text', +// text: UNPROCESSED_HTML, +// }, +// name: "fragment-info-header-header-text-info", +// parentPath: siteContent._path, +// x: {}, +// }); +// console.error('textComponentFragmentContent', textComponentFragmentContent); + export const libPortal = new LibPortal({ app, server }); +const mode = 'edit'; +// const mode = 'inline'; +// const mode = 'live'; +// const mode = 'preview'; libPortal.request = new Request({ repositoryId: server.context.repository, - path: `/admin/site/preview/${PROJECT_NAME}/draft/${SITE_NAME}` + mode, // For some reason this makes getSiteConfig fail??? + path: `/admin/site/${mode}/${PROJECT_NAME}/draft/${SITE_NAME}` }); jest.mock('/lib/xp/content', () => ({ - get: jest.fn((params) => libContent.get(params)), + // @ts-expect-error + get: jest.fn((params) => { + const {key} = params; + if (key === TEXT_FRAGMENT_CONTENT_ID) return TEXT_FRAGMENT_CONTENT; + if (key === PART_FRAGMENT_CONTENT_ID) return PART_FRAGMENT_CONTENT; + if (key === LAYOUT_FRAGMENT_CONTENT_ID) return LAYOUT_FRAGMENT_CONTENT; + return libContent.get(params); + }), }), { virtual: true }); jest.mock('/lib/xp/portal', () => ({ diff --git a/src/main/resources/lib/enonic/react4xp/DataFetcher.ts b/src/main/resources/lib/enonic/react4xp/DataFetcher.ts index 35a51fea..098e5c08 100644 --- a/src/main/resources/lib/enonic/react4xp/DataFetcher.ts +++ b/src/main/resources/lib/enonic/react4xp/DataFetcher.ts @@ -21,16 +21,17 @@ import type { MixinSchema, } from '@enonic-types/lib-schema'; import type { - DecoratedComponent, - DecoratedLayoutComponent, - DecoratedPageComponent, - DecoratedPartComponent, - DecoratedTextComponent, + RenderableComponent, + RenderableLayoutComponent, + RenderablePageComponent, + RenderablePartComponent, + RenderableTextComponent, } from '@enonic/react-components'; import {getIn} from '@enonic/js-utils/object/getIn'; import {setIn} from '@enonic/js-utils/object/setIn'; +import {toStr} from '@enonic/js-utils/value/toStr'; // import {stringify} from 'q-i'; import {get as getContentByKey} from '/lib/xp/content'; @@ -73,7 +74,7 @@ export interface GetComponentReturnType { interface LayoutComponentToPropsParams { component: LayoutComponent; content?: PageContent; - processedComponent: DecoratedLayoutComponent; + processedComponent: ProcessedLayoutComponent; processedConfig: Record; siteConfig?: Record | null; request: Request; @@ -82,12 +83,19 @@ interface LayoutComponentToPropsParams { interface PageComponentToPropsParams { component: PageComponent; content?: PageContent; - processedComponent: DecoratedPageComponent; + processedComponent: ProcessedPageComponent; processedConfig: Record; siteConfig?: Record | null; request: Request; } +export type ProcessedLayoutComponent = LayoutComponent & { + props?: Record; +} +export type ProcessedPageComponent = PageComponent & { + props?: Record; +} + export type PageContent< Data = Record, Type extends string = string, @@ -307,13 +315,23 @@ export class DataFetcher { content?: PageContent; request: Request; siteConfig?: Record | null; - }): DecoratedLayoutComponent { - const decoratedComponent: DecoratedLayoutComponent = JSON.parse(JSON.stringify(component)); - const {descriptor} = component; + }): RenderableLayoutComponent { + const { + descriptor, + path, + regions + } = component; + const renderableComponent: RenderableLayoutComponent = { + // Do not ass config, it should not be exposed to client-side + descriptor, + path, + regions: JSON.parse(JSON.stringify(regions)), + type: 'layout', + }; const toProps = this.layouts[descriptor]; if (!toProps) { log.warning(`processLayout: toProps not found for descriptor: ${descriptor}!`); - return decoratedComponent; + return renderableComponent; } const {form} = getComponentSchema({ @@ -321,24 +339,25 @@ export class DataFetcher { type: 'LAYOUT', }) as GetComponentReturnType; - const processedComponent = this.processWithRegions({ + const processedLayoutComponent = this.processWithRegions({ component, content, form, request, - }) as DecoratedLayoutComponent; + }) as ProcessedLayoutComponent; + // log.info('DataFetcher processLayout processedLayoutComponent:%s', toStr(processedLayoutComponent)) - decoratedComponent.props = toProps({ + renderableComponent.props = toProps({ component, content, - processedComponent: processedComponent, - processedConfig: processedComponent.config, + processedComponent: processedLayoutComponent, + processedConfig: processedLayoutComponent.config, siteConfig, // : this.getCurrentSiteConfig && this.getCurrentSiteConfig() as Record, request, }); - // decoratedComponent.processedConfig = processedComponent.config; - return decoratedComponent; - } + // renderableComponent.processedConfig = processedLayoutComponent.config; + return renderableComponent; + } // processLayout private processPage({ component, @@ -350,14 +369,24 @@ export class DataFetcher { content?: PageContent; request: Request; siteConfig?: Record | null; - }): DecoratedPageComponent { + }): RenderablePageComponent { // log.debug('processPage component:', component); - const decoratedComponent: DecoratedPageComponent = JSON.parse(JSON.stringify(component)); - const {descriptor} = component; + const { + descriptor, + path, + regions + } = component; + const renderableComponent: RenderablePageComponent = { + // Do not ass config, it should not be exposed to client-side + descriptor, + path, + regions: JSON.parse(JSON.stringify(regions)), + type: 'page', + }; const toProps = this.pages[descriptor]; if (!toProps) { log.warning(`processPage: toProps not found for descriptor: ${descriptor}!`); - return decoratedComponent; + return renderableComponent; } const {form} = getComponentSchema({ @@ -370,9 +399,9 @@ export class DataFetcher { content, form, request, - }) as DecoratedPageComponent; + }) as ProcessedPageComponent; - decoratedComponent.props = toProps({ + renderableComponent.props = toProps({ component, content, processedComponent: processedComponent, @@ -380,8 +409,8 @@ export class DataFetcher { siteConfig, // : this.getCurrentSiteConfig && this.getCurrentSiteConfig() as Record, request, }); - // decoratedComponent.processedConfig = processedComponent.config; - return decoratedComponent; + // renderableComponent.processedConfig = processedComponent.config; + return renderableComponent; } private processPart({ @@ -394,14 +423,21 @@ export class DataFetcher { content?: PageContent; request: Request; siteConfig?: Record | null; - }): DecoratedPartComponent { - const {descriptor} = component; + }): RenderablePartComponent { + const { + descriptor, + path + } = component; - const decoratedComponent: DecoratedPartComponent = JSON.parse(JSON.stringify(component)); + const renderableComponent: RenderablePartComponent = { + descriptor, + path, + type: 'part', + } const toProps = this.parts[descriptor]; if (!toProps) { log.warning(`processPart: toProps not found for descriptor: ${descriptor}!`); - return decoratedComponent; + return renderableComponent; } const {form} = getComponentSchema({ @@ -434,15 +470,15 @@ export class DataFetcher { } } // for - decoratedComponent.props = toProps({ + renderableComponent.props = toProps({ component, content, processedConfig: processedComponent.config, siteConfig,//: this.getCurrentSiteConfig && this.getCurrentSiteConfig() as Record, request, }); - // decoratedComponent.processedConfig = processedComponent.config; - return decoratedComponent; + // renderableComponent.processedConfig = processedComponent.config; + return renderableComponent; } private processTextComponent({ @@ -451,17 +487,17 @@ export class DataFetcher { }: { component: TextComponent mode: LiteralUnion - }): DecoratedTextComponent { + }): RenderableTextComponent { const {text} = component; const processedHtml = processHtml({ value: text }); - const decoratedTextComponent: DecoratedTextComponent = JSON.parse(JSON.stringify(component)); - decoratedTextComponent.props = { + const renderableTextComponent: RenderableTextComponent = JSON.parse(JSON.stringify(component)); + renderableTextComponent.props = { data: replaceMacroComments(processedHtml), mode }; - return decoratedTextComponent; + return renderableTextComponent; } private processWithRegions({ @@ -474,14 +510,14 @@ export class DataFetcher { content?: PageContent; form: NestedPartial[]; request: Request; - }): DecoratedLayoutComponent | DecoratedPageComponent { + }): ProcessedLayoutComponent | ProcessedPageComponent { const htmlAreas = this.getHtmlAreas({ ancestor: 'config', form, }); // log.info('processWithRegions htmlAreas:', htmlAreas); - const decoratedLayoutOrPageComponent: DecoratedLayoutComponent | DecoratedPageComponent = JSON.parse(JSON.stringify(layoutOrPageComponent)); + const processedLayoutOrPageComponent: ProcessedLayoutComponent | ProcessedPageComponent = JSON.parse(JSON.stringify(layoutOrPageComponent)); //────────────────────────────────────────────────────────────────────── // This modifies layoutOrPage.config: @@ -500,10 +536,10 @@ export class DataFetcher { value: html }); const data = replaceMacroComments(processedHtml); - setIn(decoratedLayoutOrPageComponent, path, data); + setIn(processedLayoutOrPageComponent, path, data); } } // for - // log.debug('processWithRegions config:', decoratedLayoutOrPageComponent.config); + // log.debug('processWithRegions config:', processedLayoutOrPageComponent.config); //────────────────────────────────────────────────────────────────────── // This modifies layoutOrPage.regions: @@ -518,15 +554,15 @@ export class DataFetcher { for (let j = 0; j < components.length; j++) { const component = components[j]; // @ts-expect-error Too complex/strict type generics. - decoratedLayoutOrPageComponent.regions[regionName].components[j] = this.process({ + processedLayoutOrPageComponent.regions[regionName].components[j] = this.process({ component, content, request, }); } } - // log.debug('processWithRegions regions:', stringify(decoratedLayoutOrPageComponent.regions, {maxItems: Infinity})); - return decoratedLayoutOrPageComponent; + // log.debug('processWithRegions regions:', stringify(processedLayoutOrPageComponent.regions, {maxItems: Infinity})); + return processedLayoutOrPageComponent; } // processWithRegions public addLayout(descriptor: string, { @@ -564,7 +600,7 @@ export class DataFetcher { component?: Component; content?: PageContent; request: Request; - }): DecoratedComponent { + }): RenderableComponent { // content = this.getCurrentContent && this.getCurrentContent() as PageContent if (!content) { content = getCurrentContent!() as PageContent; @@ -625,7 +661,7 @@ export function fetchData({ component?: Component; content?: PageContent; request: Request; -}): DecoratedComponent { +}): RenderableComponent { const processor = new DataFetcher(); return processor.process({ component, diff --git a/src/main/resources/lib/enonic/react4xp/React4xp.ts b/src/main/resources/lib/enonic/react4xp/React4xp.ts index bbe21a3d..a316641d 100644 --- a/src/main/resources/lib/enonic/react4xp/React4xp.ts +++ b/src/main/resources/lib/enonic/react4xp/React4xp.ts @@ -125,7 +125,8 @@ export class React4xp< request: Request = null, options: RenderOptions = {} ): Response { - // log.debug('render entry:%s options:%s', toStr(entry), toStr(options)); + // log.debug('render entry:%s props:%s options:%s request:%s', toStr(entry), toStr(props), toStr(options), toStr(request)); + // log.info('render entry:%s props:%s options:%s', toStr(entry), toStr(props), toStr(options)); let react4xp: Instance = null; try { const dereffedOptions = JSON.parse(JSON.stringify(options)) as typeof options & {