@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.
Files changed (82) hide show
  1. package/.eslint-childrc.js +17 -0
  2. package/.eslintrc.js +15 -0
  3. package/.github/dependabot.yml +20 -0
  4. package/.github/pull-merger.yml +1 -0
  5. package/.github/workflows/release.yml +39 -0
  6. package/LICENSE +201 -0
  7. package/RELEASE.md +75 -0
  8. package/package.json +3 -2
  9. package/server/.eslintrc.js +11 -0
  10. package/server/ClearableFileCache.ts +410 -0
  11. package/server/cache.ts +50 -0
  12. package/server/index.ts +74 -0
  13. package/server/tsconfig.json +23 -0
  14. package/setversion.js +8 -0
  15. package/src/.eslintrc.js +11 -0
  16. package/src/ComponentRegistry.ts +189 -0
  17. package/{UrlProcessor.js → src/UrlProcessor.ts} +54 -36
  18. package/{baseMappings.js → src/baseMappings.ts} +13 -3
  19. package/{guillotine/fetchContent.js → src/guillotine/fetchContent.ts} +493 -243
  20. package/src/guillotine/getMetaData.ts +246 -0
  21. package/{index.d.ts → src/index.ts} +1 -1
  22. package/src/tsconfig.json +20 -0
  23. package/{utils.js → src/utils.ts} +158 -68
  24. package/{views/BaseComponent.jsx → src/views/BaseComponent.tsx} +73 -39
  25. package/{views/BaseContent.jsx → src/views/BaseContent.tsx} +24 -17
  26. package/src/views/BaseLayout.tsx +45 -0
  27. package/{views/BaseMacro.jsx → src/views/BaseMacro.tsx} +45 -29
  28. package/src/views/BasePage.tsx +56 -0
  29. package/src/views/BasePart.tsx +50 -0
  30. package/{views/Empty.jsx → src/views/Empty.tsx} +2 -0
  31. package/src/views/Fragment.tsx +45 -0
  32. package/src/views/MainView.tsx +21 -0
  33. package/src/views/Region.tsx +89 -0
  34. package/{views/RichTextView.jsx → src/views/RichTextView.tsx} +31 -10
  35. package/{views/SingleComponent.jsx → src/views/SingleComponent.tsx} +8 -2
  36. package/src/views/StaticContent.tsx +40 -0
  37. package/src/views/Text.tsx +15 -0
  38. package/src/views/macros/DefaultMacro.tsx +9 -0
  39. package/src/views/macros/DisableMacro.tsx +8 -0
  40. package/ComponentRegistry.d.ts +0 -64
  41. package/ComponentRegistry.js +0 -121
  42. package/UrlProcessor.d.ts +0 -25
  43. package/baseMappings.d.ts +0 -1
  44. package/guillotine/fetchContent.d.ts +0 -49
  45. package/guillotine/getMetaData.d.ts +0 -94
  46. package/guillotine/getMetaData.js +0 -128
  47. package/index.js +0 -5
  48. package/query/Shortcut.d.ts +0 -1
  49. package/server/ClearableFileCache.d.ts +0 -31
  50. package/server/ClearableFileCache.js +0 -472
  51. package/server/cache.d.ts +0 -2
  52. package/server/cache.js +0 -129
  53. package/server/index.d.ts +0 -5
  54. package/server/index.js +0 -132
  55. package/utils.d.ts +0 -114
  56. package/views/BaseComponent.d.ts +0 -25
  57. package/views/BaseContent.d.ts +0 -4
  58. package/views/BaseLayout.d.ts +0 -17
  59. package/views/BaseLayout.jsx +0 -24
  60. package/views/BaseMacro.d.ts +0 -16
  61. package/views/BasePage.d.ts +0 -19
  62. package/views/BasePage.jsx +0 -34
  63. package/views/BasePart.d.ts +0 -19
  64. package/views/BasePart.jsx +0 -24
  65. package/views/Empty.d.ts +0 -3
  66. package/views/Fragment.d.ts +0 -10
  67. package/views/Fragment.jsx +0 -30
  68. package/views/MainView.d.ts +0 -4
  69. package/views/MainView.jsx +0 -14
  70. package/views/Region.d.ts +0 -20
  71. package/views/Region.jsx +0 -52
  72. package/views/RichTextView.d.ts +0 -16
  73. package/views/SingleComponent.d.ts +0 -5
  74. package/views/StaticContent.d.ts +0 -15
  75. package/views/StaticContent.jsx +0 -36
  76. package/views/Text.d.ts +0 -9
  77. package/views/Text.jsx +0 -4
  78. package/views/macros/DefaultMacro.d.ts +0 -4
  79. package/views/macros/DefaultMacro.jsx +0 -3
  80. package/views/macros/DisableMacro.d.ts +0 -3
  81. package/views/macros/DisableMacro.jsx +0 -2
  82. /package/{query/Shortcut.js → src/query/Shortcut.ts} +0 -0
@@ -1,31 +1,45 @@
1
1
  import React from 'react';
2
- import { IS_DEV_MODE, PORTAL_COMPONENT_ATTRIBUTE, RENDER_MODE, XP_COMPONENT_TYPE } from '../utils';
3
- import { ComponentRegistry } from '../ComponentRegistry';
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
- const BaseComponent = ({ component, meta, common }) => {
7
- var _a;
8
- const { type, error } = component;
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
- let ComponentView;
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
- const ViewFn = (_a = ComponentRegistry.getComponent(type)) === null || _a === void 0 ? void 0 : _a.view;
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 (<div {...createEditorAttrs(type)}>
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 (<div style={{
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={{ margin: 0 }}>Missing component</h3>
57
- <p style={{ marginBottom: 0, color: 'grey' }}>{`Missing ${type} with descriptor: ${descriptor}`}</p>
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
- export function shouldShowMissingView(meta) {
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 (<div style={{
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={{ margin: 0, textTransform: 'capitalize' }}>{reason ? reason : 'Unknown error'}</h3>
72
- {descriptor && <p style={{ color: 'grey' }}>{`${type}: ${descriptor}`}</p>}
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
- export function shouldShowErrorView(meta) {
98
+
99
+ export function shouldShowErrorView(meta: MetaData): boolean {
76
100
  return meta.renderMode === RENDER_MODE.EDIT;
77
101
  }
78
- function createEditorAttrs(type) {
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
- function createComponentAttrs(component, meta, common) {
84
- const { type, data, error } = component;
85
- const cmpAttrs = {
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
- export const PlaceholderComponent = ({ type, descriptor }) => {
104
- return (<div style={{
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={{ margin: 0, textTransform: 'capitalize' }}>Empty output</h3>
109
- {descriptor && <p style={{ color: 'grey' }}>{`${type} '${descriptor}' output is empty`}</p>}
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
- export function shouldShowPlaceholderView(meta) {
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 { ComponentRegistry } from '../ComponentRegistry';
3
- import BasePage from './BasePage';
4
- const BaseContent = (props) => {
5
- const { common, meta, page } = props;
6
- if (!(meta === null || meta === void 0 ? void 0 : meta.type)) {
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
- const pageData = page === null || page === void 0 ? void 0 : page.page;
11
- const pageDesc = pageData === null || pageData === void 0 ? void 0 : pageData.descriptor;
14
+
15
+ const pageData = page?.page;
16
+ const pageDesc = pageData?.descriptor;
12
17
  const typeDef = ComponentRegistry.getContentType(meta.type);
13
- const ContentTypeView = typeDef === null || typeDef === void 0 ? void 0 : typeDef.view;
14
- if (ContentTypeView && !(typeDef === null || typeDef === void 0 ? void 0 : typeDef.catchAll)) {
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 === null || page === void 0 ? void 0 : page.path,
27
+ path: page?.path,
23
28
  meta,
24
29
  common,
25
30
  };
26
- if (page === null || page === void 0 ? void 0 : page.data) {
31
+ if (page?.data) {
27
32
  pageAttrs.data = page.data;
28
33
  }
29
- if (page === null || page === void 0 ? void 0 : page.error) {
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 { ComponentRegistry } from '../ComponentRegistry';
3
- import { RENDER_MODE, sanitizeGraphqlName } from '../utils';
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
- const BaseMacro = (props) => {
9
- const { data, meta, renderInEditMode } = props;
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
- if (!renderInEditMode && (meta === null || meta === void 0 ? void 0 : meta.renderMode) === RENDER_MODE.EDIT) {
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 === null || macro === void 0 ? void 0 : macro.view;
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
- function normalizeValue(value) {
45
- let parsedVal;
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
- function formatAttributes(config) {
76
- const attrs = Object.entries(config)
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
- // concat to make sure we account for arrays
80
- [].concat(normalizeValue(entry[1])).forEach(val => accum.push(`${entry[0]}="${val}"`));
81
- return accum;
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
+
@@ -1,3 +1,5 @@
1
1
  import React from 'react';
2
+
2
3
  const Empty = () => <></>;
4
+
3
5
  export default Empty;
@@ -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;