@enonic/nextjs-adapter 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslint-childrc.js +17 -0
- package/.eslintrc.js +15 -0
- package/.github/dependabot.yml +20 -0
- package/.github/pull-merger.yml +1 -0
- package/.github/workflows/release.yml +39 -0
- package/LICENSE +201 -0
- package/RELEASE.md +75 -0
- package/package.json +3 -2
- package/server/.eslintrc.js +11 -0
- package/server/ClearableFileCache.ts +410 -0
- package/server/cache.ts +50 -0
- package/server/index.ts +74 -0
- package/server/tsconfig.json +23 -0
- package/setversion.js +8 -0
- package/src/.eslintrc.js +11 -0
- package/src/ComponentRegistry.ts +189 -0
- package/{UrlProcessor.js → src/UrlProcessor.ts} +54 -36
- package/{baseMappings.js → src/baseMappings.ts} +13 -3
- package/{guillotine/fetchContent.js → src/guillotine/fetchContent.ts} +493 -243
- package/src/guillotine/getMetaData.ts +246 -0
- package/{index.d.ts → src/index.ts} +1 -1
- package/src/tsconfig.json +20 -0
- package/{utils.js → src/utils.ts} +158 -68
- package/{views/BaseComponent.jsx → src/views/BaseComponent.tsx} +73 -39
- package/{views/BaseContent.jsx → src/views/BaseContent.tsx} +24 -17
- package/src/views/BaseLayout.tsx +45 -0
- package/{views/BaseMacro.jsx → src/views/BaseMacro.tsx} +45 -29
- package/src/views/BasePage.tsx +56 -0
- package/src/views/BasePart.tsx +50 -0
- package/{views/Empty.jsx → src/views/Empty.tsx} +2 -0
- package/src/views/Fragment.tsx +45 -0
- package/src/views/MainView.tsx +21 -0
- package/src/views/Region.tsx +89 -0
- package/{views/RichTextView.jsx → src/views/RichTextView.tsx} +31 -10
- package/{views/SingleComponent.jsx → src/views/SingleComponent.tsx} +8 -2
- package/src/views/StaticContent.tsx +40 -0
- package/src/views/Text.tsx +15 -0
- package/src/views/macros/DefaultMacro.tsx +9 -0
- package/src/views/macros/DisableMacro.tsx +8 -0
- package/ComponentRegistry.d.ts +0 -64
- package/ComponentRegistry.js +0 -121
- package/UrlProcessor.d.ts +0 -25
- package/baseMappings.d.ts +0 -1
- package/guillotine/fetchContent.d.ts +0 -49
- package/guillotine/getMetaData.d.ts +0 -94
- package/guillotine/getMetaData.js +0 -128
- package/index.js +0 -5
- package/query/Shortcut.d.ts +0 -1
- package/server/ClearableFileCache.d.ts +0 -31
- package/server/ClearableFileCache.js +0 -472
- package/server/cache.d.ts +0 -2
- package/server/cache.js +0 -129
- package/server/index.d.ts +0 -5
- package/server/index.js +0 -132
- package/utils.d.ts +0 -114
- package/views/BaseComponent.d.ts +0 -25
- package/views/BaseContent.d.ts +0 -4
- package/views/BaseLayout.d.ts +0 -17
- package/views/BaseLayout.jsx +0 -24
- package/views/BaseMacro.d.ts +0 -16
- package/views/BasePage.d.ts +0 -19
- package/views/BasePage.jsx +0 -34
- package/views/BasePart.d.ts +0 -19
- package/views/BasePart.jsx +0 -24
- package/views/Empty.d.ts +0 -3
- package/views/Fragment.d.ts +0 -10
- package/views/Fragment.jsx +0 -30
- package/views/MainView.d.ts +0 -4
- package/views/MainView.jsx +0 -14
- package/views/Region.d.ts +0 -20
- package/views/Region.jsx +0 -52
- package/views/RichTextView.d.ts +0 -16
- package/views/SingleComponent.d.ts +0 -5
- package/views/StaticContent.d.ts +0 -15
- package/views/StaticContent.jsx +0 -36
- package/views/Text.d.ts +0 -9
- package/views/Text.jsx +0 -4
- package/views/macros/DefaultMacro.d.ts +0 -4
- package/views/macros/DefaultMacro.jsx +0 -3
- package/views/macros/DisableMacro.d.ts +0 -3
- package/views/macros/DisableMacro.jsx +0 -2
- /package/{query/Shortcut.js → src/query/Shortcut.ts} +0 -0
|
@@ -1,31 +1,45 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
import {
|
|
2
|
+
|
|
3
|
+
import {IS_DEV_MODE, PORTAL_COMPONENT_ATTRIBUTE, RENDER_MODE, XP_COMPONENT_TYPE} from '../utils';
|
|
4
|
+
import {MetaData, PageComponent} from '../guillotine/getMetaData';
|
|
5
|
+
import {ComponentRegistry} from '../ComponentRegistry';
|
|
4
6
|
import Empty from './Empty';
|
|
5
7
|
import * as ReactDOMServer from 'react-dom/server';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export type BaseComponentProps = {
|
|
11
|
+
component: PageComponent;
|
|
12
|
+
meta: MetaData;
|
|
13
|
+
common?: any; // Content is passed down for optional consumption in componentviews.
|
|
14
|
+
// TODO: Use a react contextprovider instead of "manually" passing everything down
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const BaseComponent = ({component, meta, common}: BaseComponentProps) => {
|
|
18
|
+
const {type, error} = component;
|
|
9
19
|
const cmpData = component[type];
|
|
10
20
|
const descriptor = cmpData && 'descriptor' in cmpData ? cmpData.descriptor : undefined;
|
|
11
|
-
|
|
21
|
+
|
|
22
|
+
let ComponentView: JSX.Element | null;
|
|
23
|
+
|
|
12
24
|
if (error) {
|
|
13
25
|
// renders error component when rendering whole page
|
|
14
26
|
console.warn(`BaseComponent: ${type} '${descriptor}' error: ${error}`);
|
|
15
|
-
ComponentView = shouldShowErrorView(meta) ? <ErrorComponent type={type} reason={error} descriptor={descriptor}/> : <Empty
|
|
16
|
-
|
|
17
|
-
else {
|
|
18
|
-
|
|
27
|
+
ComponentView = shouldShowErrorView(meta) ? <ErrorComponent type={type} reason={error} descriptor={descriptor}/> : <Empty/>;
|
|
28
|
+
|
|
29
|
+
} else {
|
|
30
|
+
|
|
31
|
+
const ViewFn = ComponentRegistry.getComponent(type)?.view;
|
|
19
32
|
if (!ViewFn) {
|
|
20
33
|
// show missing component kind, i.e. 'part', 'layout', 'text'
|
|
21
34
|
console.warn(`BaseComponent: can not render component '${type}': no next view or catch-all defined`);
|
|
22
|
-
ComponentView = shouldShowMissingView(meta) ? <MissingComponent type={type} descriptor={descriptor}/> : <Empty
|
|
23
|
-
|
|
24
|
-
else {
|
|
35
|
+
ComponentView = shouldShowMissingView(meta) ? <MissingComponent type={type} descriptor={descriptor}/> : <Empty/>;
|
|
36
|
+
|
|
37
|
+
} else {
|
|
25
38
|
const cmpAttrs = createComponentAttrs(component, meta, common);
|
|
26
39
|
ComponentView = <ViewFn {...cmpAttrs}/>;
|
|
27
40
|
}
|
|
28
41
|
}
|
|
42
|
+
|
|
29
43
|
// need to display a placeholder if descriptor is empty as component is not initialized yet
|
|
30
44
|
if (descriptor && shouldShowPlaceholderView(meta)) {
|
|
31
45
|
const outputHTML = ReactDOMServer.renderToString(ComponentView);
|
|
@@ -34,81 +48,101 @@ const BaseComponent = ({ component, meta, common }) => {
|
|
|
34
48
|
ComponentView = <PlaceholderComponent type={type} descriptor={descriptor}/>;
|
|
35
49
|
}
|
|
36
50
|
}
|
|
51
|
+
|
|
37
52
|
if (meta.renderMode === RENDER_MODE.EDIT) {
|
|
38
|
-
return (
|
|
53
|
+
return (
|
|
54
|
+
<div {...createEditorAttrs(type)}>
|
|
39
55
|
{ComponentView}
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
else {
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
} else {
|
|
43
59
|
// do not make component wrappers in live mode
|
|
44
60
|
return ComponentView;
|
|
45
61
|
}
|
|
46
62
|
};
|
|
47
63
|
export default BaseComponent;
|
|
64
|
+
|
|
48
65
|
/*
|
|
49
66
|
* Used for displaying components with missing implementations on the page, while rendering whole page
|
|
50
67
|
*/
|
|
51
|
-
export const MissingComponent = ({ descriptor, type }) => {
|
|
52
|
-
return (
|
|
68
|
+
export const MissingComponent = ({descriptor, type}: { descriptor?: string, type: string }) => {
|
|
69
|
+
return (
|
|
70
|
+
<div style={{
|
|
53
71
|
border: '2px dashed lightgrey',
|
|
54
72
|
padding: '16px',
|
|
55
73
|
}}>
|
|
56
|
-
<h3 style={{
|
|
57
|
-
<p style={{
|
|
58
|
-
</div>
|
|
74
|
+
<h3 style={{margin: 0}}>Missing component</h3>
|
|
75
|
+
<p style={{marginBottom: 0, color: 'grey'}}>{`Missing ${type} with descriptor: ${descriptor}`}</p>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
59
78
|
};
|
|
60
|
-
|
|
79
|
+
|
|
80
|
+
export function shouldShowMissingView(meta: MetaData): boolean {
|
|
61
81
|
return IS_DEV_MODE || meta.renderMode !== RENDER_MODE.NEXT;
|
|
62
82
|
}
|
|
83
|
+
|
|
63
84
|
/*
|
|
64
85
|
* Used for displaying components with errors on the page, while rendering whole page
|
|
65
86
|
*/
|
|
66
|
-
export const ErrorComponent = ({ type, descriptor, reason }) => {
|
|
67
|
-
return (
|
|
87
|
+
export const ErrorComponent = ({type, descriptor, reason}: { type?: string, descriptor?: string, reason: string }) => {
|
|
88
|
+
return (
|
|
89
|
+
<div style={{
|
|
68
90
|
border: '2px solid red',
|
|
69
91
|
padding: '16px',
|
|
70
92
|
}}>
|
|
71
|
-
<h3 style={{
|
|
72
|
-
{descriptor && <p style={{
|
|
73
|
-
</div>
|
|
93
|
+
<h3 style={{margin: 0, textTransform: 'capitalize'}}>{reason ? reason : 'Unknown error'}</h3>
|
|
94
|
+
{descriptor && <p style={{color: 'grey'}}>{`${type}: ${descriptor}`}</p>}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
74
97
|
};
|
|
75
|
-
|
|
98
|
+
|
|
99
|
+
export function shouldShowErrorView(meta: MetaData): boolean {
|
|
76
100
|
return meta.renderMode === RENDER_MODE.EDIT;
|
|
77
101
|
}
|
|
78
|
-
|
|
102
|
+
|
|
103
|
+
function createEditorAttrs(type: XP_COMPONENT_TYPE): { [key: string]: string } {
|
|
79
104
|
return {
|
|
80
105
|
[PORTAL_COMPONENT_ATTRIBUTE]: type,
|
|
81
106
|
};
|
|
82
107
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
108
|
+
|
|
109
|
+
function createComponentAttrs(component: PageComponent, meta: MetaData, common?: any): { [key: string]: any } {
|
|
110
|
+
const {type, data, error} = component;
|
|
111
|
+
const cmpAttrs: { [key: string]: any } = {
|
|
86
112
|
component: component[type],
|
|
87
113
|
path: component.path,
|
|
88
114
|
meta,
|
|
89
115
|
common,
|
|
90
116
|
};
|
|
117
|
+
|
|
91
118
|
if (data) {
|
|
92
119
|
cmpAttrs.data = data;
|
|
93
120
|
}
|
|
121
|
+
|
|
94
122
|
if (error) {
|
|
95
123
|
cmpAttrs.error = error;
|
|
96
124
|
}
|
|
125
|
+
|
|
97
126
|
if (component.type === XP_COMPONENT_TYPE.LAYOUT) {
|
|
98
127
|
// add regions to layout because they are not present in component[component.type] above
|
|
99
128
|
cmpAttrs.regions = component.regions;
|
|
100
129
|
}
|
|
101
130
|
return cmpAttrs;
|
|
102
131
|
}
|
|
103
|
-
|
|
104
|
-
|
|
132
|
+
|
|
133
|
+
export const PlaceholderComponent = ({type, descriptor}: { type?: string, descriptor?: string }) => {
|
|
134
|
+
return (
|
|
135
|
+
<div style={{
|
|
105
136
|
border: '2px solid lightgrey',
|
|
106
137
|
padding: '16px',
|
|
107
138
|
}}>
|
|
108
|
-
<h3 style={{
|
|
109
|
-
{descriptor && <p style={{
|
|
110
|
-
</div>
|
|
139
|
+
<h3 style={{margin: 0, textTransform: 'capitalize'}}>Empty output</h3>
|
|
140
|
+
{descriptor && <p style={{color: 'grey'}}>{`${type} '${descriptor}' output is empty`}</p>}
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
111
143
|
};
|
|
112
|
-
|
|
144
|
+
|
|
145
|
+
export function shouldShowPlaceholderView(meta: MetaData): boolean {
|
|
113
146
|
return meta.renderMode === RENDER_MODE.EDIT;
|
|
114
147
|
}
|
|
148
|
+
|
|
@@ -1,41 +1,48 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import {FetchContentResult} from '../guillotine/fetchContent';
|
|
3
|
+
import {ComponentRegistry} from '../ComponentRegistry';
|
|
4
|
+
import BasePage, {BasePageProps} from './BasePage';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const BaseContent = (props: FetchContentResult) => {
|
|
8
|
+
const {common, meta, page} = props;
|
|
9
|
+
|
|
10
|
+
if (!meta?.type) {
|
|
7
11
|
console.warn("BaseContent props are missing 'meta.type'");
|
|
8
12
|
return null;
|
|
9
13
|
}
|
|
10
|
-
|
|
11
|
-
const
|
|
14
|
+
|
|
15
|
+
const pageData = page?.page;
|
|
16
|
+
const pageDesc = pageData?.descriptor;
|
|
12
17
|
const typeDef = ComponentRegistry.getContentType(meta.type);
|
|
13
|
-
const ContentTypeView = typeDef
|
|
14
|
-
|
|
18
|
+
const ContentTypeView = typeDef?.view;
|
|
19
|
+
|
|
20
|
+
if (ContentTypeView && !typeDef?.catchAll) {
|
|
15
21
|
// console.info(`BaseContent: rendering '${meta.type}' with content type: ${ContentTypeView.name}`);
|
|
16
22
|
return <ContentTypeView {...props}/>;
|
|
17
|
-
}
|
|
18
|
-
else if (pageDesc) {
|
|
23
|
+
} else if (pageDesc) {
|
|
19
24
|
// console.info(`BaseContent: rendering '${meta.type}' with page: ${BasePage.name}`);
|
|
20
|
-
const pageAttrs = {
|
|
25
|
+
const pageAttrs: BasePageProps = {
|
|
21
26
|
component: pageData,
|
|
22
|
-
path: page
|
|
27
|
+
path: page?.path,
|
|
23
28
|
meta,
|
|
24
29
|
common,
|
|
25
30
|
};
|
|
26
|
-
if (page
|
|
31
|
+
if (page?.data) {
|
|
27
32
|
pageAttrs.data = page.data;
|
|
28
33
|
}
|
|
29
|
-
if (page
|
|
34
|
+
if (page?.error) {
|
|
30
35
|
pageAttrs.error = page.error;
|
|
31
36
|
}
|
|
37
|
+
|
|
32
38
|
return <BasePage {...pageAttrs}/>;
|
|
33
|
-
}
|
|
34
|
-
else if (ContentTypeView) {
|
|
39
|
+
} else if (ContentTypeView) {
|
|
35
40
|
// console.info(`BaseContent: rendering '${meta.type}' with content type catch-all: ${ContentTypeView.name}`);
|
|
36
41
|
return <ContentTypeView {...props}/>;
|
|
37
42
|
}
|
|
43
|
+
|
|
38
44
|
console.log(`BaseContent: can not render ${meta.type}: no content type or page view defined`);
|
|
39
45
|
return null;
|
|
40
46
|
};
|
|
47
|
+
|
|
41
48
|
export default BaseContent;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {LayoutData, MetaData, RegionTree} from '../guillotine/getMetaData';
|
|
3
|
+
import {ComponentRegistry} from '../ComponentRegistry';
|
|
4
|
+
import {XP_COMPONENT_TYPE} from '../utils';
|
|
5
|
+
import {MissingComponent, shouldShowMissingView} from './BaseComponent';
|
|
6
|
+
|
|
7
|
+
export interface LayoutProps {
|
|
8
|
+
layout: LayoutData;
|
|
9
|
+
path: string;
|
|
10
|
+
common: any;
|
|
11
|
+
meta: MetaData;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface BaseLayoutProps {
|
|
15
|
+
component?: LayoutData;
|
|
16
|
+
path: string;
|
|
17
|
+
regions?: RegionTree;
|
|
18
|
+
common?: any;
|
|
19
|
+
meta: MetaData;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const BaseLayout = (props: BaseLayoutProps) => {
|
|
23
|
+
|
|
24
|
+
const {component, common, regions, meta, path} = props;
|
|
25
|
+
let layoutSelection;
|
|
26
|
+
if (component) {
|
|
27
|
+
layoutSelection = ComponentRegistry.getLayout(component.descriptor);
|
|
28
|
+
}
|
|
29
|
+
const SelectedLayoutView = layoutSelection?.view;
|
|
30
|
+
if (SelectedLayoutView) {
|
|
31
|
+
return <SelectedLayoutView layout={{descriptor: component?.descriptor, regions: regions || {}, config: component?.config}}
|
|
32
|
+
path={path}
|
|
33
|
+
common={common}
|
|
34
|
+
meta={meta}/>;
|
|
35
|
+
} else if (component?.descriptor) {
|
|
36
|
+
// empty descriptor usually means uninitialized layout
|
|
37
|
+
console.warn(`BaseLayout: can not render layout '${component?.descriptor}': no next view or catch-all defined`);
|
|
38
|
+
if (shouldShowMissingView(meta)) {
|
|
39
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.LAYOUT} descriptor={component.descriptor}/>;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default BaseLayout;
|
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import {MacroConfig, MacroData, MetaData} from '../guillotine/getMetaData';
|
|
3
|
+
import {ComponentRegistry} from '../ComponentRegistry';
|
|
4
|
+
import {RENDER_MODE, sanitizeGraphqlName} from '../utils';
|
|
4
5
|
import HTMLReactParser from 'html-react-parser';
|
|
6
|
+
|
|
5
7
|
const unescape = require('unescape');
|
|
8
|
+
|
|
6
9
|
export const MACRO_DISABLE = 'system:disable';
|
|
7
10
|
export const MACRO_EMBED = 'system:embed';
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
interface BaseMacroProps {
|
|
13
|
+
data: MacroData;
|
|
14
|
+
meta: MetaData;
|
|
15
|
+
renderInEditMode?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MacroProps {
|
|
19
|
+
name: string;
|
|
20
|
+
config: MacroConfig;
|
|
21
|
+
meta: MetaData;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BaseMacro = (props: BaseMacroProps) => {
|
|
25
|
+
const {data, meta, renderInEditMode} = props;
|
|
26
|
+
|
|
10
27
|
let config = data.config[sanitizeGraphqlName(data.name)] || {};
|
|
11
28
|
if (data.descriptor !== MACRO_DISABLE) {
|
|
12
29
|
// do not do any processing for disable macro
|
|
13
30
|
config = normalizeValue(config);
|
|
14
31
|
}
|
|
15
|
-
|
|
32
|
+
|
|
33
|
+
if (!renderInEditMode && meta?.renderMode === RENDER_MODE.EDIT) {
|
|
16
34
|
const attrs = formatAttributes(config);
|
|
17
35
|
if (config.body) {
|
|
18
36
|
// do not parse system macros (embed, disable) in edit mode
|
|
@@ -24,62 +42,60 @@ const BaseMacro = (props) => {
|
|
|
24
42
|
{bodyString}
|
|
25
43
|
{`[/${data.name}]`}
|
|
26
44
|
</>;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
45
|
+
} else {
|
|
29
46
|
return <>
|
|
30
47
|
{`[${data.name}${attrs}/]`}
|
|
31
48
|
</>;
|
|
32
49
|
}
|
|
33
50
|
}
|
|
51
|
+
|
|
34
52
|
const macro = ComponentRegistry.getMacro(data.descriptor);
|
|
35
|
-
const MacroView = macro
|
|
53
|
+
const MacroView = macro?.view;
|
|
36
54
|
if (MacroView) {
|
|
37
55
|
return <MacroView name={data.name} config={config} meta={meta}/>;
|
|
38
|
-
}
|
|
39
|
-
else if (data.descriptor) {
|
|
56
|
+
} else if (data.descriptor) {
|
|
40
57
|
console.warn(`BaseMacro: can not render macro '${data.descriptor}': no next view or catch-all defined`);
|
|
41
58
|
}
|
|
42
59
|
return null;
|
|
43
60
|
};
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
|
|
62
|
+
function normalizeValue(value: any): any {
|
|
63
|
+
let parsedVal: any;
|
|
46
64
|
if (value instanceof Array) {
|
|
47
65
|
parsedVal = value.map(normalizeValue);
|
|
48
|
-
}
|
|
49
|
-
else if (value instanceof Object) {
|
|
66
|
+
} else if (value instanceof Object) {
|
|
50
67
|
parsedVal = {};
|
|
51
68
|
Object.entries(value).forEach(entry => parsedVal[entry[0]] = normalizeValue(entry[1]));
|
|
52
|
-
}
|
|
53
|
-
else if (typeof value === 'string') {
|
|
69
|
+
} else if (typeof value === 'string') {
|
|
54
70
|
const unescapedValue = unescape(value);
|
|
55
71
|
if (unescapedValue.startsWith('[') && unescapedValue.endsWith(']')) {
|
|
56
72
|
// Wicked array notation: "[Value 1, Value 2]"
|
|
57
73
|
parsedVal = unescapedValue.substring(1, unescapedValue.length - 1).split(/,\s*/);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
74
|
+
} else {
|
|
60
75
|
try {
|
|
61
76
|
parsedVal = JSON.parse(unescapedValue);
|
|
62
|
-
}
|
|
63
|
-
catch (e) {
|
|
77
|
+
} catch (e) {
|
|
64
78
|
// simple string or wrongly formatted JSON
|
|
65
79
|
parsedVal = unescapedValue;
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
82
|
+
} else {
|
|
70
83
|
// most likely a number or unknown type
|
|
71
84
|
parsedVal = value;
|
|
72
85
|
}
|
|
73
86
|
return parsedVal;
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
|
|
89
|
+
function formatAttributes(config: MacroConfig): string {
|
|
90
|
+
const attrs: string[] = Object.entries(config)
|
|
77
91
|
.filter(entry => entry[0] !== 'body')
|
|
78
|
-
.reduce((accum, entry) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
.reduce<string[]>((accum, entry) => {
|
|
93
|
+
// concat to make sure we account for arrays
|
|
94
|
+
[].concat(normalizeValue(entry[1])).forEach(val => accum.push(`${entry[0]}="${val}"`));
|
|
95
|
+
return accum;
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
83
98
|
return attrs.length ? ` ${attrs.join(' ')}` : '';
|
|
84
99
|
}
|
|
100
|
+
|
|
85
101
|
export default BaseMacro;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {ComponentRegistry} from '../ComponentRegistry';
|
|
3
|
+
import {MetaData, PageData} from '../guillotine/getMetaData';
|
|
4
|
+
import {XP_COMPONENT_TYPE} from '../utils';
|
|
5
|
+
import {ErrorComponent, MissingComponent, shouldShowErrorView, shouldShowMissingView} from './BaseComponent';
|
|
6
|
+
|
|
7
|
+
export interface PageProps {
|
|
8
|
+
page: PageData;
|
|
9
|
+
path: string;
|
|
10
|
+
data?: any;
|
|
11
|
+
common?: any; // Content is passed down to componentviews. TODO: Use a react contextprovider instead?
|
|
12
|
+
meta: MetaData;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BasePageProps {
|
|
16
|
+
component?: PageData;
|
|
17
|
+
path?: string;
|
|
18
|
+
common?: any;
|
|
19
|
+
data?: any;
|
|
20
|
+
error?: string;
|
|
21
|
+
meta: MetaData;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BasePage = (props: BasePageProps) => {
|
|
25
|
+
const {component, data, common, error, meta, path} = props;
|
|
26
|
+
const desc = component?.descriptor;
|
|
27
|
+
if (error) {
|
|
28
|
+
console.warn(`BasePage: '${desc}' error: ${error}`);
|
|
29
|
+
if (shouldShowErrorView(meta)) {
|
|
30
|
+
return <ErrorComponent reason={error} descriptor={desc} type={XP_COMPONENT_TYPE.PAGE}/>;
|
|
31
|
+
} else {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
let pageDef;
|
|
36
|
+
if (desc) {
|
|
37
|
+
pageDef = ComponentRegistry.getPage(desc);
|
|
38
|
+
}
|
|
39
|
+
const PageView = pageDef?.view;
|
|
40
|
+
if (PageView) {
|
|
41
|
+
return <PageView page={component}
|
|
42
|
+
path={path}
|
|
43
|
+
data={data}
|
|
44
|
+
common={common}
|
|
45
|
+
meta={meta}/>;
|
|
46
|
+
} else if (component?.descriptor) {
|
|
47
|
+
// empty descriptor usually means uninitialized page
|
|
48
|
+
console.warn(`BasePage: can not render page '${desc}': no next view or catch-all defined`);
|
|
49
|
+
if (shouldShowMissingView(meta)) {
|
|
50
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.PAGE} descriptor={component.descriptor}/>;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default BasePage;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {MetaData, PartData} from '../guillotine/getMetaData';
|
|
3
|
+
import {ComponentRegistry} from '../ComponentRegistry';
|
|
4
|
+
import {XP_COMPONENT_TYPE} from '../utils';
|
|
5
|
+
import {MissingComponent, shouldShowMissingView} from './BaseComponent';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export interface PartProps {
|
|
9
|
+
part: PartData;
|
|
10
|
+
path: string;
|
|
11
|
+
data?: any;
|
|
12
|
+
common?: any; // Content is passed down to componentviews. TODO: Use a react contextprovider instead?
|
|
13
|
+
meta: MetaData;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface BasePartProps {
|
|
17
|
+
component?: PartData;
|
|
18
|
+
path: string;
|
|
19
|
+
common?: any;
|
|
20
|
+
data?: any;
|
|
21
|
+
error?: string;
|
|
22
|
+
meta: MetaData;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BasePart = (props: BasePartProps) => {
|
|
26
|
+
const {component, data, common, meta, path} = props;
|
|
27
|
+
|
|
28
|
+
let partSelection;
|
|
29
|
+
if (component) {
|
|
30
|
+
partSelection = ComponentRegistry.getPart(component.descriptor);
|
|
31
|
+
}
|
|
32
|
+
const SelectedPartView = partSelection?.view;
|
|
33
|
+
if (SelectedPartView) {
|
|
34
|
+
return <SelectedPartView part={component}
|
|
35
|
+
path={path}
|
|
36
|
+
data={data}
|
|
37
|
+
common={common}
|
|
38
|
+
meta={meta}/>;
|
|
39
|
+
} else if (component?.descriptor) {
|
|
40
|
+
// empty descriptor usually means uninitialized part
|
|
41
|
+
console.warn(`BasePart: can not render part '${component.descriptor}': no next view or catch-all defined`);
|
|
42
|
+
if (shouldShowMissingView(meta)) {
|
|
43
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.PART} descriptor={component.descriptor}/>;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default BasePart;
|
|
50
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import BaseComponent, {MissingComponent, shouldShowMissingView} from './BaseComponent';
|
|
3
|
+
import {FragmentData, MetaData, PageComponent, PageData} from '../guillotine/getMetaData';
|
|
4
|
+
import {FRAGMENT_DEFAULT_REGION_NAME, XP_COMPONENT_TYPE} from '../utils';
|
|
5
|
+
|
|
6
|
+
interface FragmentProps {
|
|
7
|
+
page?: PageData;
|
|
8
|
+
component?: FragmentData;
|
|
9
|
+
common?: any;
|
|
10
|
+
meta: MetaData;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FragmentView = (props: FragmentProps) => {
|
|
14
|
+
const {component, page} = props;
|
|
15
|
+
const comps: PageComponent[] = [];
|
|
16
|
+
if (page) {
|
|
17
|
+
// rendering whole page
|
|
18
|
+
const regions = page.regions || {};
|
|
19
|
+
const regionComps = regions[FRAGMENT_DEFAULT_REGION_NAME]?.components;
|
|
20
|
+
if (regionComps) {
|
|
21
|
+
comps.push(...regionComps);
|
|
22
|
+
}
|
|
23
|
+
} else if (component?.fragment) {
|
|
24
|
+
// rendering a part of a page
|
|
25
|
+
comps.push(...component.fragment.components);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!comps.length && shouldShowMissingView(props.meta)) {
|
|
29
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.FRAGMENT} descriptor={page?.descriptor || component?.id}/>;
|
|
30
|
+
} else {
|
|
31
|
+
const {common, meta} = props;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
{
|
|
36
|
+
comps.map((comp: PageComponent, i: number) => (
|
|
37
|
+
<BaseComponent key={i} component={comp} common={common} meta={meta}/>
|
|
38
|
+
))
|
|
39
|
+
}
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default FragmentView;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {FetchContentResult} from '../guillotine/fetchContent';
|
|
4
|
+
import SingleComponent from './SingleComponent';
|
|
5
|
+
import BaseContent from './BaseContent';
|
|
6
|
+
import {XP_REQUEST_TYPE} from '../utils';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const _MainView = (props: FetchContentResult) => {
|
|
10
|
+
const {meta} = props;
|
|
11
|
+
|
|
12
|
+
// Single-component render:
|
|
13
|
+
if (meta?.requestType === XP_REQUEST_TYPE.COMPONENT) {
|
|
14
|
+
return <SingleComponent {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// meta.requestType="type", and hence content-type based, is standard view for now.
|
|
18
|
+
return <BaseContent {...props} />;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default _MainView;
|