@enonic/nextjs-adapter 3.0.0-B2 → 3.0.0-B4
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/LICENSE +201 -0
- package/baseMappings.js.map +1 -1
- package/client.js.map +1 -1
- package/common/ComponentRegistry.js.map +1 -1
- package/common/UrlProcessor.js.map +1 -1
- package/common/constants.js.map +1 -1
- package/common/env.js +14 -13
- package/common/env.js.map +1 -1
- package/guillotine/applyProcessors.js.map +1 -1
- package/guillotine/buildComponentTree.js.map +1 -1
- package/guillotine/buildErrorResponse.js.map +1 -1
- package/guillotine/buildPage.js.map +1 -1
- package/guillotine/collectComponentDescriptors.js.map +1 -1
- package/guillotine/combineMultipleQueries.js.map +1 -1
- package/guillotine/createMetaData.js.map +1 -1
- package/guillotine/fetchContent.d.ts +2 -2
- package/guillotine/fetchContent.js +172 -170
- package/guillotine/fetchContent.js.map +1 -1
- package/guillotine/fetchContentData.js.map +1 -1
- package/guillotine/fetchContentPathsForAllLocales.js.map +1 -1
- package/guillotine/fetchContentPathsForLocale.js.map +1 -1
- package/guillotine/fetchFromApi.d.ts +1 -1
- package/guillotine/fetchFromApi.js +56 -54
- package/guillotine/fetchFromApi.js.map +1 -1
- package/guillotine/fetchGuillotine.d.ts +1 -1
- package/guillotine/fetchGuillotine.js +38 -36
- package/guillotine/fetchGuillotine.js.map +1 -1
- package/guillotine/fetchMetaData.js.map +1 -1
- package/guillotine/getCleanContentPathArrayOrThrow400.js.map +1 -1
- package/guillotine/getComponentConfig.js.map +1 -1
- package/guillotine/getContentAndComponentPaths.js.map +1 -1
- package/guillotine/getMetaData.js.map +1 -1
- package/guillotine/getParentRegion.js.map +1 -1
- package/guillotine/getQueryAndVariables.js.map +1 -1
- package/guillotine/metadata/configQuery.js.map +1 -1
- package/guillotine/metadata/richTextQuery.js.map +1 -1
- package/guillotine/parseComponentPath.js.map +1 -1
- package/guillotine/prefixLayoutPath.js.map +1 -1
- package/guillotine/processComponentConfig.js.map +1 -1
- package/guillotine/restrictComponentsToPath.js.map +1 -1
- package/guillotine/validateData.js.map +1 -1
- package/guillotine/validateNotFound.js.map +1 -1
- package/guillotine/validateShortcut.js.map +1 -1
- package/i18n/LocaleContext.jsx.map +1 -1
- package/i18n/i18n.js.map +1 -1
- package/index.d.ts +1 -5
- package/index.js +1 -5
- package/index.js.map +1 -1
- package/package.json +15 -16
- package/query/Shortcut.js.map +1 -1
- package/server.d.ts +5 -0
- package/server.js +6 -0
- package/server.js.map +1 -0
- package/src/baseMappings.ts +53 -0
- package/src/client.ts +2 -0
- package/src/common/ComponentRegistry.ts +162 -0
- package/src/common/UrlProcessor.ts +142 -0
- package/src/common/constants.ts +85 -0
- package/src/common/env.ts +39 -0
- package/src/guillotine/applyProcessors.ts +38 -0
- package/src/guillotine/buildComponentTree.ts +63 -0
- package/src/guillotine/buildErrorResponse.ts +39 -0
- package/src/guillotine/buildPage.ts +15 -0
- package/src/guillotine/collectComponentDescriptors.ts +63 -0
- package/src/guillotine/combineMultipleQueries.ts +75 -0
- package/src/guillotine/createMetaData.ts +73 -0
- package/src/guillotine/fetchContent.ts +250 -0
- package/src/guillotine/fetchContentData.ts +33 -0
- package/src/guillotine/fetchContentPathsForAllLocales.ts +21 -0
- package/src/guillotine/fetchContentPathsForLocale.ts +42 -0
- package/src/guillotine/fetchFromApi.ts +69 -0
- package/src/guillotine/fetchGuillotine.ts +61 -0
- package/src/guillotine/fetchMetaData.ts +32 -0
- package/src/guillotine/getCleanContentPathArrayOrThrow400.ts +22 -0
- package/src/guillotine/getComponentConfig.ts +7 -0
- package/src/guillotine/getContentAndComponentPaths.ts +11 -0
- package/src/guillotine/getMetaData.ts +73 -0
- package/src/guillotine/getParentRegion.ts +59 -0
- package/src/guillotine/getQueryAndVariables.ts +52 -0
- package/src/guillotine/metadata/configQuery.ts +46 -0
- package/src/guillotine/metadata/richTextQuery.ts +34 -0
- package/src/guillotine/parseComponentPath.ts +29 -0
- package/src/guillotine/prefixLayoutPath.ts +16 -0
- package/src/guillotine/processComponentConfig.ts +38 -0
- package/src/guillotine/restrictComponentsToPath.ts +45 -0
- package/src/guillotine/validateData.ts +11 -0
- package/src/guillotine/validateNotFound.ts +20 -0
- package/src/guillotine/validateShortcut.ts +37 -0
- package/src/i18n/LocaleContext.tsx +54 -0
- package/src/i18n/i18n.ts +54 -0
- package/src/index.ts +42 -0
- package/src/query/Shortcut.ts +14 -0
- package/src/server.ts +5 -0
- package/src/types/component.ts +147 -0
- package/src/types/componentProps.ts +142 -0
- package/src/types/guillotine.ts +426 -0
- package/src/types/i18n.ts +8 -0
- package/src/types/index.ts +58 -0
- package/src/types/next.ts +7 -0
- package/src/types/util.ts +5 -0
- package/src/utils/addLineNumbers.ts +3 -0
- package/src/utils/buildGuillotineRequestHeaders.ts +32 -0
- package/src/utils/commonChars.ts +16 -0
- package/src/utils/fixDoubleSlashes.ts +3 -0
- package/src/utils/getContentApiUrl.ts +20 -0
- package/src/utils/getProjectLocaleConfig.ts +24 -0
- package/src/utils/getProjectLocaleConfigById.ts +21 -0
- package/src/utils/getProjectLocaleConfigByLocale.ts +19 -0
- package/src/utils/getProjectLocaleConfigs.ts +36 -0
- package/src/utils/getProjectLocales.ts +6 -0
- package/src/utils/getRenderMode.ts +14 -0
- package/src/utils/getRequestLocaleInfo.ts +29 -0
- package/src/utils/getXpBaseUrl.ts +12 -0
- package/src/utils/indent.ts +4 -0
- package/src/utils/sanitizeGraphqlName.ts +11 -0
- package/src/views/BaseComponent.tsx +153 -0
- package/src/views/BaseContent.tsx +47 -0
- package/src/views/BaseLayout.tsx +33 -0
- package/src/views/BaseMacro.tsx +94 -0
- package/src/views/BasePage.tsx +42 -0
- package/src/views/BasePart.tsx +34 -0
- package/src/views/Empty.tsx +5 -0
- package/src/views/Fragment.tsx +41 -0
- package/src/views/MainView.tsx +22 -0
- package/src/views/PropsView.tsx +38 -0
- package/src/views/Region.tsx +76 -0
- package/src/views/RichTextView.tsx +75 -0
- package/src/views/SingleComponent.tsx +19 -0
- package/src/views/StaticContent.tsx +39 -0
- package/src/views/Text.tsx +19 -0
- package/src/views/getClientHydratedHtml.tsx +12 -0
- package/src/views/macros/DefaultMacro.tsx +9 -0
- package/src/views/macros/DisableMacro.tsx +8 -0
- package/types/component.js.map +1 -1
- package/types/componentProps.js.map +1 -1
- package/types/guillotine.js.map +1 -1
- package/types/i18n.js.map +1 -1
- package/types/index.js.map +1 -1
- package/types/next.js.map +1 -1
- package/types/util.js.map +1 -1
- package/utils/addLineNumbers.js.map +1 -1
- package/utils/buildGuillotineRequestHeaders.js.map +1 -1
- package/utils/commonChars.js.map +1 -1
- package/utils/fixDoubleSlashes.js.map +1 -1
- package/utils/getContentApiUrl.js.map +1 -1
- package/utils/getProjectLocaleConfig.js.map +1 -1
- package/utils/getProjectLocaleConfigById.js.map +1 -1
- package/utils/getProjectLocaleConfigByLocale.js.map +1 -1
- package/utils/getProjectLocaleConfigs.js.map +1 -1
- package/utils/getProjectLocales.js.map +1 -1
- package/utils/getRenderMode.js.map +1 -1
- package/utils/getRequestLocaleInfo.js.map +1 -1
- package/utils/getXpBaseUrl.js.map +1 -1
- package/utils/indent.js.map +1 -1
- package/utils/sanitizeGraphqlName.js.map +1 -1
- package/views/BaseComponent.jsx.map +1 -1
- package/views/BaseContent.jsx.map +1 -1
- package/views/BaseLayout.jsx.map +1 -1
- package/views/BaseMacro.jsx.map +1 -1
- package/views/BasePage.jsx.map +1 -1
- package/views/BasePart.jsx.map +1 -1
- package/views/Empty.jsx.map +1 -1
- package/views/Fragment.jsx.map +1 -1
- package/views/MainView.jsx.map +1 -1
- package/views/PropsView.jsx.map +1 -1
- package/views/Region.jsx.map +1 -1
- package/views/RichTextView.jsx.map +1 -1
- package/views/SingleComponent.jsx.map +1 -1
- package/views/StaticContent.jsx.map +1 -1
- package/views/Text.jsx.map +1 -1
- package/views/getClientHydratedHtml.jsx.map +1 -1
- package/views/macros/DefaultMacro.jsx.map +1 -1
- package/views/macros/DisableMacro.jsx.map +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type {Context} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import {RENDER_MODE} from '../common/constants';
|
|
5
|
+
import {API_URL} from '../common/env';
|
|
6
|
+
import {getProjectLocaleConfig} from './getProjectLocaleConfig';
|
|
7
|
+
import {fixDoubleSlashes} from './fixDoubleSlashes';
|
|
8
|
+
import {getRenderMode} from './getRenderMode';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export function getContentApiUrl(context: Context): string {
|
|
12
|
+
const project = getProjectLocaleConfig(context).project;
|
|
13
|
+
const branch = getRenderMode(context) === RENDER_MODE.NEXT
|
|
14
|
+
? 'master'
|
|
15
|
+
: getRenderMode(context) === RENDER_MODE.LIVE
|
|
16
|
+
? 'master'
|
|
17
|
+
: 'draft';
|
|
18
|
+
|
|
19
|
+
return fixDoubleSlashes(`${API_URL}/${project}/${branch}`);
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {Context, ProjectLocaleConfig} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import {PROJECT_ID_HEADER} from '../common/constants';
|
|
5
|
+
import {getProjectLocaleConfigById} from './getProjectLocaleConfigById';
|
|
6
|
+
import {getProjectLocaleConfigByLocale} from './getProjectLocaleConfigByLocale';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export function getProjectLocaleConfig(context: Context): ProjectLocaleConfig {
|
|
10
|
+
const projectId = context.headers?.get(PROJECT_ID_HEADER);
|
|
11
|
+
|
|
12
|
+
let config: ProjectLocaleConfig;
|
|
13
|
+
if (projectId) {
|
|
14
|
+
// first use project id header
|
|
15
|
+
config = getProjectLocaleConfigById(projectId, false);
|
|
16
|
+
}
|
|
17
|
+
if (!config) {
|
|
18
|
+
// next try to use locale from url
|
|
19
|
+
// it will fall back to default locale if not found
|
|
20
|
+
config = getProjectLocaleConfigByLocale(context.locale);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return config;
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {ProjectLocaleConfig} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import {getProjectLocaleConfigs} from './getProjectLocaleConfigs';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function getProjectLocaleConfigById(projectId?: string, useDefault = true): ProjectLocaleConfig {
|
|
8
|
+
const configs = getProjectLocaleConfigs();
|
|
9
|
+
|
|
10
|
+
let config: ProjectLocaleConfig;
|
|
11
|
+
if (projectId) {
|
|
12
|
+
config = Object.values(configs).find(p => {
|
|
13
|
+
return p?.project?.toLowerCase() === projectId.toLowerCase();
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (!config && useDefault) {
|
|
17
|
+
config = Object.values(configs).find(c => c.default);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return config;
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type {ProjectLocaleConfig} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import {getProjectLocaleConfigs} from './getProjectLocaleConfigs';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function getProjectLocaleConfigByLocale(locale?: string, useDefault = true): ProjectLocaleConfig {
|
|
8
|
+
const configs = getProjectLocaleConfigs();
|
|
9
|
+
|
|
10
|
+
let config: ProjectLocaleConfig;
|
|
11
|
+
if (locale) {
|
|
12
|
+
config = configs[locale];
|
|
13
|
+
}
|
|
14
|
+
if (!config && useDefault) {
|
|
15
|
+
config = Object.values(configs).find(c => c.default);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type {ProjectLocalesConfig} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import {ENV_VARS} from '../common/constants';
|
|
5
|
+
import {PROJECTS} from '../common/env';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// NOTE: Dissallowing slash and any whitespace in 2nd capture group.
|
|
9
|
+
const PROJECT_CONFIG_REGEXP = /^([\w-]+):([^/\s]+)(\/[\w.-]+)?$/i;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export function getProjectLocaleConfigs(): ProjectLocalesConfig {
|
|
13
|
+
const str = PROJECTS;
|
|
14
|
+
if (!str?.length) {
|
|
15
|
+
throw new Error(`Did you forget to define "${ENV_VARS.PROJECTS}" environmental variable?
|
|
16
|
+
Format: <default-language>:<default-repository-name>/<default-site-path>,<language>:<repository-name>/<site-path>,...`);
|
|
17
|
+
}
|
|
18
|
+
return str.split(',').reduce((config, prjStr, index: number) => {
|
|
19
|
+
const trimmedprjStr = prjStr.trim();
|
|
20
|
+
if (!trimmedprjStr.length) {
|
|
21
|
+
return config;
|
|
22
|
+
}
|
|
23
|
+
const matches: RegExpExecArray = PROJECT_CONFIG_REGEXP.exec(trimmedprjStr);
|
|
24
|
+
if (!matches?.length) {
|
|
25
|
+
throw new Error(`Project "${prjStr}" doesn't match format: <default-language>:<default-repository-name>/<default-site-path>,<language>:<repository-name>/<site-path>`);
|
|
26
|
+
}
|
|
27
|
+
const [_full, lang, project, site] = matches;
|
|
28
|
+
config[lang] = {
|
|
29
|
+
default: index === 0,
|
|
30
|
+
project,
|
|
31
|
+
site,
|
|
32
|
+
locale: lang,
|
|
33
|
+
};
|
|
34
|
+
return config;
|
|
35
|
+
}, {});
|
|
36
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type {Context} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
RENDER_MODE,
|
|
6
|
+
RENDER_MODE_HEADER,
|
|
7
|
+
} from '../common/constants';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const getRenderMode = (context: Context): RENDER_MODE => {
|
|
11
|
+
const value = context.headers?.get(RENDER_MODE_HEADER);
|
|
12
|
+
const enumValue = RENDER_MODE[(value as keyof typeof RENDER_MODE)?.toUpperCase()];
|
|
13
|
+
return enumValue || RENDER_MODE[process.env.RENDER_MODE] || RENDER_MODE.NEXT;
|
|
14
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {Context} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import Negotiator from 'negotiator';
|
|
5
|
+
import {match as localeMatcher} from '@formatjs/intl-localematcher';
|
|
6
|
+
import {PROJECT_ID_HEADER} from '../common/constants';
|
|
7
|
+
import {getProjectLocaleConfigs} from './getProjectLocaleConfigs';
|
|
8
|
+
import {getProjectLocaleConfigById} from './getProjectLocaleConfigById';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export function getRequestLocaleInfo(context: Context) {
|
|
12
|
+
let locale: string;
|
|
13
|
+
const configs = getProjectLocaleConfigs();
|
|
14
|
+
const locales = Object.keys(configs);
|
|
15
|
+
const defaultLocale = locales.find((locale) => configs[locale].default);
|
|
16
|
+
const projectId = context.headers?.get(PROJECT_ID_HEADER);
|
|
17
|
+
if (projectId) {
|
|
18
|
+
locale = getProjectLocaleConfigById(projectId, false)?.locale;
|
|
19
|
+
|
|
20
|
+
} else {
|
|
21
|
+
locale = context.locale;
|
|
22
|
+
if (!locale) {
|
|
23
|
+
const acceptLang = context.headers?.get('accept-language') || '';
|
|
24
|
+
const langs = new Negotiator({headers: {'accept-language': acceptLang}}).languages();
|
|
25
|
+
locale = localeMatcher(langs, locales, defaultLocale);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {locale, locales, defaultLocale};
|
|
29
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {Context} from '../types';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import {XP_BASE_URL_HEADER} from '../common/constants';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const getXpBaseUrl = (context: Context): string => {
|
|
8
|
+
const header = context.headers?.get(XP_BASE_URL_HEADER) || '/';
|
|
9
|
+
|
|
10
|
+
// TODO: workaround for XP pattern controller mapping not picked up in edit mode
|
|
11
|
+
return header.replace(/\/edit\//, '/inline/');
|
|
12
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// sanitizes text according to graphql naming spec http://spec.graphql.org/October2021/#sec-Names
|
|
2
|
+
export const sanitizeGraphqlName = (text: string) => {
|
|
3
|
+
if (!text || text.trim().length === 0) {
|
|
4
|
+
return '';
|
|
5
|
+
}
|
|
6
|
+
let result = text.replace(/([^0-9A-Za-z])+/g, '_');
|
|
7
|
+
if (result.length > 0 && /[0-9]/.test(result.charAt(0))) {
|
|
8
|
+
result = '_' + result;
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type {BaseComponentProps, MetaData, PageComponent} from '../types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {PORTAL_COMPONENT_ATTRIBUTE, RENDER_MODE, XP_COMPONENT_TYPE} from '../common/constants';
|
|
6
|
+
import {IS_DEV_MODE} from '../common/env';
|
|
7
|
+
import {ComponentRegistry} from '../common/ComponentRegistry';
|
|
8
|
+
import Empty from './Empty';
|
|
9
|
+
import getClientHydratedHtml from './getClientHydratedHtml';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const BaseComponent = ({component, meta, common}: BaseComponentProps) => {
|
|
13
|
+
const {type, error} = component;
|
|
14
|
+
const cmpData = component[type];
|
|
15
|
+
const descriptor = cmpData && 'descriptor' in cmpData ? cmpData.descriptor : undefined;
|
|
16
|
+
|
|
17
|
+
let ComponentView: JSX.Element | null;
|
|
18
|
+
|
|
19
|
+
if (error) {
|
|
20
|
+
// renders error component when rendering whole page
|
|
21
|
+
console.warn(`BaseComponent: ${type} '${descriptor}' error: ${error}`);
|
|
22
|
+
ComponentView = shouldShowErrorView(meta) ? <ErrorComponent type={type} reason={error} descriptor={descriptor}/> : <Empty/>;
|
|
23
|
+
|
|
24
|
+
} else {
|
|
25
|
+
|
|
26
|
+
const ViewFn = ComponentRegistry.getComponent(type)?.view;
|
|
27
|
+
if (!ViewFn) {
|
|
28
|
+
// show missing component kind, i.e. 'part', 'layout', 'text'
|
|
29
|
+
console.warn(`BaseComponent: can not render component '${type}': no next view or catch-all defined`);
|
|
30
|
+
ComponentView = shouldShowMissingView(meta) ? <MissingComponent type={type} descriptor={descriptor}/> : <Empty/>;
|
|
31
|
+
|
|
32
|
+
} else {
|
|
33
|
+
const cmpAttrs = createComponentAttrs(component, meta, common);
|
|
34
|
+
ComponentView = <ViewFn {...cmpAttrs}/>;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// need to display a placeholder if descriptor is empty as component is not initialized yet
|
|
39
|
+
if (descriptor && shouldShowPlaceholderView(meta)) {
|
|
40
|
+
const outputHTML = renderComponent(ComponentView);
|
|
41
|
+
if (!outputHTML?.trim().length) {
|
|
42
|
+
// render some placeholder in case of empty output
|
|
43
|
+
ComponentView = <PlaceholderComponent type={type} descriptor={descriptor}/>;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (meta.renderMode === RENDER_MODE.EDIT) {
|
|
48
|
+
return (
|
|
49
|
+
<div {...createEditorAttrs(type)}>
|
|
50
|
+
{ComponentView}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
// do not make component wrappers in live mode
|
|
55
|
+
return ComponentView;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
export default BaseComponent;
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Used for displaying components with missing implementations on the page, while rendering whole page
|
|
62
|
+
*/
|
|
63
|
+
export const MissingComponent = ({descriptor, type}: { descriptor?: string, type: string }) => {
|
|
64
|
+
return (
|
|
65
|
+
<div style={{
|
|
66
|
+
border: '2px dashed lightgrey',
|
|
67
|
+
padding: '16px',
|
|
68
|
+
}}>
|
|
69
|
+
<h3 style={{margin: 0}}>Missing component</h3>
|
|
70
|
+
<p style={{marginBottom: 0, color: 'grey'}}>{`Missing ${type} with descriptor: ${descriptor}`}</p>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const renderComponent = (component: React.ReactElement) => {
|
|
76
|
+
if (typeof document !== 'undefined') {
|
|
77
|
+
// render component on the client to see if it's empty
|
|
78
|
+
return getClientHydratedHtml(component);
|
|
79
|
+
} else {
|
|
80
|
+
// report non-empty output for server-side rendering
|
|
81
|
+
return '<div>Server-side rendered component</div>';
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function shouldShowMissingView(meta: MetaData): boolean {
|
|
86
|
+
return IS_DEV_MODE || meta.renderMode !== RENDER_MODE.NEXT;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* Used for displaying components with errors on the page, while rendering whole page
|
|
91
|
+
*/
|
|
92
|
+
export const ErrorComponent = ({type, descriptor, reason}: { type?: string, descriptor?: string, reason: string }) => {
|
|
93
|
+
return (
|
|
94
|
+
<div style={{
|
|
95
|
+
border: '2px solid red',
|
|
96
|
+
padding: '16px',
|
|
97
|
+
}}>
|
|
98
|
+
<h3 style={{margin: 0, textTransform: 'capitalize'}}>{reason ? reason : 'Unknown error'}</h3>
|
|
99
|
+
{descriptor && <p style={{color: 'grey'}}>{`${type}: ${descriptor}`}</p>}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export function shouldShowErrorView(meta: MetaData): boolean {
|
|
105
|
+
return meta.renderMode === RENDER_MODE.EDIT;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createEditorAttrs(type: XP_COMPONENT_TYPE): Record<string,string> {
|
|
109
|
+
return {
|
|
110
|
+
[PORTAL_COMPONENT_ATTRIBUTE]: type,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createComponentAttrs(component: PageComponent, meta: MetaData, common?: any): Record<string,any> {
|
|
115
|
+
const {type, data, error} = component;
|
|
116
|
+
const cmpAttrs: Record<string,any> = {
|
|
117
|
+
component: component[type],
|
|
118
|
+
path: component.path,
|
|
119
|
+
meta,
|
|
120
|
+
common,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (data) {
|
|
124
|
+
cmpAttrs.data = data;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (error) {
|
|
128
|
+
cmpAttrs.error = error;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (component.type === XP_COMPONENT_TYPE.LAYOUT) {
|
|
132
|
+
// add regions to layout because they are not present in component[component.type] above
|
|
133
|
+
cmpAttrs.regions = component.regions;
|
|
134
|
+
}
|
|
135
|
+
return cmpAttrs;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const PlaceholderComponent = ({type, descriptor}: { type?: string, descriptor?: string }) => {
|
|
139
|
+
return (
|
|
140
|
+
<div style={{
|
|
141
|
+
border: '2px solid lightgrey',
|
|
142
|
+
padding: '16px',
|
|
143
|
+
}}>
|
|
144
|
+
<h3 style={{margin: 0, textTransform: 'capitalize'}}>Empty output</h3>
|
|
145
|
+
{descriptor && <p style={{color: 'grey'}}>{`${type} '${descriptor}' output is empty`}</p>}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export function shouldShowPlaceholderView(meta: MetaData): boolean {
|
|
151
|
+
return meta.renderMode === RENDER_MODE.EDIT;
|
|
152
|
+
}
|
|
153
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type {BasePageProps, FetchContentResult} from '../types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {ComponentRegistry} from '../common/ComponentRegistry';
|
|
6
|
+
import BasePage from './BasePage';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const BaseContent = (props: FetchContentResult) => {
|
|
10
|
+
const {common, meta, page} = props;
|
|
11
|
+
|
|
12
|
+
if (!meta?.type) {
|
|
13
|
+
console.warn("BaseContent props are missing 'meta.type'");
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pageData = page?.page;
|
|
18
|
+
const pageDesc = pageData?.descriptor;
|
|
19
|
+
const typeDef = ComponentRegistry.getContentType(meta.type);
|
|
20
|
+
const ContentTypeView = typeDef?.view;
|
|
21
|
+
|
|
22
|
+
if (ContentTypeView && !typeDef?.catchAll) {
|
|
23
|
+
return <ContentTypeView {...props}/>;
|
|
24
|
+
} else if (pageDesc) {
|
|
25
|
+
const pageAttrs: BasePageProps = {
|
|
26
|
+
component: pageData,
|
|
27
|
+
path: page?.path,
|
|
28
|
+
meta,
|
|
29
|
+
common,
|
|
30
|
+
};
|
|
31
|
+
if (page?.data) {
|
|
32
|
+
pageAttrs.data = page.data;
|
|
33
|
+
}
|
|
34
|
+
if (page?.error) {
|
|
35
|
+
pageAttrs.error = page.error;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return <BasePage {...pageAttrs}/>;
|
|
39
|
+
} else if (ContentTypeView) {
|
|
40
|
+
return <ContentTypeView {...props}/>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`BaseContent: can not render ${meta.type}: no content type or page view defined`);
|
|
44
|
+
return null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default BaseContent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type {BaseLayoutProps} from '../types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {XP_COMPONENT_TYPE} from '../common/constants';
|
|
6
|
+
import {ComponentRegistry} from '../common/ComponentRegistry';
|
|
7
|
+
import {MissingComponent, shouldShowMissingView} from './BaseComponent';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const BaseLayout = (props: BaseLayoutProps) => {
|
|
11
|
+
|
|
12
|
+
const {component, common, regions, meta, path} = props;
|
|
13
|
+
let layoutSelection;
|
|
14
|
+
if (component) {
|
|
15
|
+
layoutSelection = ComponentRegistry.getLayout(component.descriptor);
|
|
16
|
+
}
|
|
17
|
+
const SelectedLayoutView = layoutSelection?.view;
|
|
18
|
+
if (SelectedLayoutView) {
|
|
19
|
+
return <SelectedLayoutView layout={{descriptor: component?.descriptor, regions: regions || {}, config: component?.config}}
|
|
20
|
+
path={path}
|
|
21
|
+
common={common}
|
|
22
|
+
meta={meta}/>;
|
|
23
|
+
} else if (component?.descriptor) {
|
|
24
|
+
// empty descriptor usually means uninitialized layout
|
|
25
|
+
console.warn(`BaseLayout: can not render layout '${component?.descriptor}': no next view or catch-all defined`);
|
|
26
|
+
if (shouldShowMissingView(meta)) {
|
|
27
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.LAYOUT} descriptor={component.descriptor}/>;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default BaseLayout;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type {BaseMacroProps, MacroConfig} from '../types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {ComponentRegistry} from '../common/ComponentRegistry';
|
|
6
|
+
import {RENDER_MODE} from '../common/constants';
|
|
7
|
+
import {sanitizeGraphqlName} from '../utils/sanitizeGraphqlName';
|
|
8
|
+
import HTMLReactParser from 'html-react-parser';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const unescape = require('unescape');
|
|
12
|
+
|
|
13
|
+
export const MACRO_DISABLE = 'system:disable';
|
|
14
|
+
export const MACRO_EMBED = 'system:embed';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const BaseMacro = (props: BaseMacroProps) => {
|
|
18
|
+
const {data, meta, renderInEditMode} = props;
|
|
19
|
+
|
|
20
|
+
let config = data.config[sanitizeGraphqlName(data.name)] || {};
|
|
21
|
+
if (data.descriptor !== MACRO_DISABLE) {
|
|
22
|
+
// do not do any processing for disable macro
|
|
23
|
+
config = normalizeValue(config);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!renderInEditMode && meta?.renderMode === RENDER_MODE.EDIT) {
|
|
27
|
+
const attrs = formatAttributes(config);
|
|
28
|
+
if (config.body) {
|
|
29
|
+
// do not parse system macros (embed, disable) in edit mode
|
|
30
|
+
// but parse others because panel macros (for instance) wants its content to be html, not string
|
|
31
|
+
// TODO: should we make this behavior configurable ?
|
|
32
|
+
const bodyString = !data.descriptor.startsWith('system:') ? HTMLReactParser(config.body) : config.body;
|
|
33
|
+
return <>
|
|
34
|
+
{`[${data.name}${attrs}]`}
|
|
35
|
+
{bodyString}
|
|
36
|
+
{`[/${data.name}]`}
|
|
37
|
+
</>;
|
|
38
|
+
} else {
|
|
39
|
+
return <>
|
|
40
|
+
{`[${data.name}${attrs}/]`}
|
|
41
|
+
</>;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const macro = ComponentRegistry.getMacro(data.descriptor);
|
|
46
|
+
const MacroView = macro?.view;
|
|
47
|
+
if (MacroView) {
|
|
48
|
+
return <MacroView name={data.name} config={config} meta={meta}/>;
|
|
49
|
+
} else if (data.descriptor) {
|
|
50
|
+
console.warn(`BaseMacro: can not render macro '${data.descriptor}': no next view or catch-all defined`);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function normalizeValue(value: any): any {
|
|
56
|
+
let parsedVal: any;
|
|
57
|
+
if (value instanceof Array) {
|
|
58
|
+
parsedVal = value.map(normalizeValue);
|
|
59
|
+
} else if (value instanceof Object) {
|
|
60
|
+
parsedVal = {};
|
|
61
|
+
Object.entries(value).forEach(entry => parsedVal[entry[0]] = normalizeValue(entry[1]));
|
|
62
|
+
} else if (typeof value === 'string') {
|
|
63
|
+
const unescapedValue = unescape(value);
|
|
64
|
+
if (unescapedValue.startsWith('[') && unescapedValue.endsWith(']')) {
|
|
65
|
+
// Wicked array notation: "[Value 1, Value 2]"
|
|
66
|
+
parsedVal = unescapedValue.substring(1, unescapedValue.length - 1).split(/,\s*/);
|
|
67
|
+
} else {
|
|
68
|
+
try {
|
|
69
|
+
parsedVal = JSON.parse(unescapedValue);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// simple string or wrongly formatted JSON
|
|
72
|
+
parsedVal = unescapedValue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// most likely a number or unknown type
|
|
77
|
+
parsedVal = value;
|
|
78
|
+
}
|
|
79
|
+
return parsedVal;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatAttributes(config: MacroConfig): string {
|
|
83
|
+
const attrs: string[] = Object.entries(config)
|
|
84
|
+
.filter(entry => entry[0] !== 'body')
|
|
85
|
+
.reduce<string[]>((accum, entry) => {
|
|
86
|
+
// concat to make sure we account for arrays
|
|
87
|
+
[].concat(normalizeValue(entry[1])).forEach(val => accum.push(`${entry[0]}="${val}"`));
|
|
88
|
+
return accum;
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
return attrs.length ? ` ${attrs.join(' ')}` : '';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default BaseMacro;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type {BasePageProps} from '../types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {ComponentRegistry} from '../common/ComponentRegistry';
|
|
6
|
+
import {XP_COMPONENT_TYPE} from '../common/constants';
|
|
7
|
+
import {ErrorComponent, MissingComponent, shouldShowErrorView, shouldShowMissingView} from './BaseComponent';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const BasePage = (props: BasePageProps) => {
|
|
11
|
+
const {component, data, common, error, meta, path} = props;
|
|
12
|
+
const desc = component?.descriptor;
|
|
13
|
+
if (error) {
|
|
14
|
+
console.warn(`BasePage: '${desc}' error: ${error}`);
|
|
15
|
+
if (shouldShowErrorView(meta)) {
|
|
16
|
+
return <ErrorComponent reason={error} descriptor={desc} type={XP_COMPONENT_TYPE.PAGE}/>;
|
|
17
|
+
} else {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let pageDef;
|
|
22
|
+
if (desc) {
|
|
23
|
+
pageDef = ComponentRegistry.getPage(desc);
|
|
24
|
+
}
|
|
25
|
+
const PageView = pageDef?.view;
|
|
26
|
+
if (PageView) {
|
|
27
|
+
return <PageView page={component}
|
|
28
|
+
path={path}
|
|
29
|
+
data={data}
|
|
30
|
+
common={common}
|
|
31
|
+
meta={meta}/>;
|
|
32
|
+
} else if (component?.descriptor) {
|
|
33
|
+
// empty descriptor usually means uninitialized page
|
|
34
|
+
console.warn(`BasePage: can not render page '${desc}': no next view or catch-all defined`);
|
|
35
|
+
if (shouldShowMissingView(meta)) {
|
|
36
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.PAGE} descriptor={component.descriptor}/>;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default BasePage;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type {BasePartProps} from '../types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {ComponentRegistry} from '../common/ComponentRegistry';
|
|
6
|
+
import {XP_COMPONENT_TYPE} from '../common/constants';
|
|
7
|
+
import {MissingComponent, shouldShowMissingView} from './BaseComponent';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const BasePart = (props: BasePartProps) => {
|
|
11
|
+
const {component, data, common, meta, path} = props;
|
|
12
|
+
|
|
13
|
+
let partSelection;
|
|
14
|
+
if (component) {
|
|
15
|
+
partSelection = ComponentRegistry.getPart(component.descriptor);
|
|
16
|
+
}
|
|
17
|
+
const SelectedPartView = partSelection?.view;
|
|
18
|
+
if (SelectedPartView) {
|
|
19
|
+
return <SelectedPartView part={component}
|
|
20
|
+
path={path}
|
|
21
|
+
data={data}
|
|
22
|
+
common={common}
|
|
23
|
+
meta={meta}/>;
|
|
24
|
+
} else if (component?.descriptor) {
|
|
25
|
+
// empty descriptor usually means uninitialized part
|
|
26
|
+
console.warn(`BasePart: can not render part '${component.descriptor}': no next view or catch-all defined`);
|
|
27
|
+
if (shouldShowMissingView(meta)) {
|
|
28
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.PART} descriptor={component.descriptor}/>;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default BasePart;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type {FragmentProps, PageComponent} from '../types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import {FRAGMENT_DEFAULT_REGION_NAME, XP_COMPONENT_TYPE} from '../common/constants';
|
|
6
|
+
import BaseComponent, {MissingComponent, shouldShowMissingView} from './BaseComponent';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const FragmentView = (props: FragmentProps) => {
|
|
10
|
+
const {component, page} = props;
|
|
11
|
+
const comps: PageComponent[] = [];
|
|
12
|
+
if (page) {
|
|
13
|
+
// rendering whole page
|
|
14
|
+
const regions = page.regions || {};
|
|
15
|
+
const regionComps = regions[FRAGMENT_DEFAULT_REGION_NAME]?.components;
|
|
16
|
+
if (regionComps) {
|
|
17
|
+
comps.push(...regionComps);
|
|
18
|
+
}
|
|
19
|
+
} else if (component?.fragment) {
|
|
20
|
+
// rendering a part of a page
|
|
21
|
+
comps.push(...component.fragment.components);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!comps.length && shouldShowMissingView(props.meta)) {
|
|
25
|
+
return <MissingComponent type={XP_COMPONENT_TYPE.FRAGMENT} descriptor={page?.descriptor || component?.id}/>;
|
|
26
|
+
} else {
|
|
27
|
+
const {common, meta} = props;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
{
|
|
32
|
+
comps.map((comp: PageComponent, i: number) => (
|
|
33
|
+
<BaseComponent key={i} component={comp} common={common} meta={meta}/>
|
|
34
|
+
))
|
|
35
|
+
}
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default FragmentView;
|