@enonic/nextjs-adapter 2.0.0-B3 → 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 +8 -7
- 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} +496 -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} +160 -70
- 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 -48
- 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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import {RENDER_MODE, sanitizeGraphqlName, XP_COMPONENT_TYPE, XP_REQUEST_TYPE} from '../utils';
|
|
2
|
+
import {ComponentDefinition, ComponentRegistry} from '../ComponentRegistry';
|
|
3
|
+
|
|
4
|
+
const macroConfigQuery = (): string => {
|
|
5
|
+
return configQuery(ComponentRegistry.getMacros(), false, false);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const partConfigQuery = (): string => {
|
|
9
|
+
return configQuery(ComponentRegistry.getParts());
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const layoutConfigQuery = (): string => {
|
|
13
|
+
return configQuery(ComponentRegistry.getLayouts());
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const pageConfigQuery = (): string => {
|
|
17
|
+
return configQuery(ComponentRegistry.getPages());
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const configQuery = (list: [string, ComponentDefinition][], includeAppName = true, canUseConfigAsJson = true): string => {
|
|
21
|
+
const hasQueryList = list?.filter(([key, def]) => def.configQuery) || [];
|
|
22
|
+
if (hasQueryList.length === 0) {
|
|
23
|
+
return canUseConfigAsJson ? 'configAsJson' : '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const configsByApp: { [app: string]: string[] } = {};
|
|
27
|
+
hasQueryList
|
|
28
|
+
.forEach((entry) => {
|
|
29
|
+
const nameParts = entry[0].split(':');
|
|
30
|
+
if (nameParts.length === 2) {
|
|
31
|
+
const sanitizedAppName = sanitizeGraphqlName(nameParts[0]);
|
|
32
|
+
let existingConfigs = configsByApp[sanitizedAppName];
|
|
33
|
+
if (!existingConfigs) {
|
|
34
|
+
existingConfigs = [];
|
|
35
|
+
configsByApp[sanitizedAppName] = existingConfigs;
|
|
36
|
+
}
|
|
37
|
+
existingConfigs.push(`${sanitizeGraphqlName(nameParts[1])}${entry[1].configQuery}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Still query for configAsJson if at least one item has no configQuery defined
|
|
42
|
+
const configAsJsonQuery = canUseConfigAsJson && hasQueryList.length < list.length ? 'configAsJson' : '';
|
|
43
|
+
|
|
44
|
+
if (!includeAppName) {
|
|
45
|
+
return `${configAsJsonQuery}
|
|
46
|
+
config {
|
|
47
|
+
${Object.values(configsByApp).reduce((arr, curr) => arr.concat(curr), []).join('\n')}
|
|
48
|
+
}`;
|
|
49
|
+
} else {
|
|
50
|
+
return `${configAsJsonQuery}
|
|
51
|
+
config {
|
|
52
|
+
${Object.entries(configsByApp).map(entry => entry[0] + '{\n' + entry[1].join('\n') + '\n}')}
|
|
53
|
+
}`;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
IMPORTANT: make sure to transform your queries into functions too when using this function inside them !!!
|
|
59
|
+
*/
|
|
60
|
+
export const richTextQuery = (fieldName: string) => {
|
|
61
|
+
return `${fieldName}(processHtml:{type:absolute, imageWidths:[400, 800, 1200], imageSizes:"(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"}) {
|
|
62
|
+
processedHtml
|
|
63
|
+
macros {
|
|
64
|
+
ref
|
|
65
|
+
name
|
|
66
|
+
descriptor
|
|
67
|
+
${macroConfigQuery()}
|
|
68
|
+
}
|
|
69
|
+
links {
|
|
70
|
+
ref
|
|
71
|
+
media {
|
|
72
|
+
content {
|
|
73
|
+
_id
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
images {
|
|
78
|
+
ref
|
|
79
|
+
image {
|
|
80
|
+
_id
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}`;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const componentsQuery = () => `
|
|
87
|
+
type
|
|
88
|
+
path
|
|
89
|
+
page {
|
|
90
|
+
descriptor
|
|
91
|
+
${pageConfigQuery()}
|
|
92
|
+
template {
|
|
93
|
+
_path
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
layout {
|
|
97
|
+
descriptor
|
|
98
|
+
${layoutConfigQuery()}
|
|
99
|
+
}
|
|
100
|
+
text {
|
|
101
|
+
${richTextQuery('value')}
|
|
102
|
+
}
|
|
103
|
+
part {
|
|
104
|
+
descriptor
|
|
105
|
+
${partConfigQuery()}
|
|
106
|
+
}
|
|
107
|
+
image {
|
|
108
|
+
caption
|
|
109
|
+
image {
|
|
110
|
+
imageUrl (type:absolute, scale: "width-768")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
// THIS QUERY DOES NOT SUPPORT NESTED FRAGMENTS
|
|
116
|
+
export const pageFragmentQuery = () => `
|
|
117
|
+
components(resolveFragment: false, resolveTemplate: true) {
|
|
118
|
+
fragment {
|
|
119
|
+
id
|
|
120
|
+
fragment {
|
|
121
|
+
components {
|
|
122
|
+
${componentsQuery()}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
${componentsQuery()}
|
|
127
|
+
}`;
|
|
128
|
+
|
|
129
|
+
export function getMetaQuery(pageFragment?: string): string {
|
|
130
|
+
return `query($path:ID!){
|
|
131
|
+
guillotine {
|
|
132
|
+
get(key:$path) {
|
|
133
|
+
_path
|
|
134
|
+
type
|
|
135
|
+
${pageFragment || ''}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface PageComponent {
|
|
142
|
+
[key: string]: any; // keeps ts happy when accessing component data field by XP_COMPONENT_TYPE type
|
|
143
|
+
type: XP_COMPONENT_TYPE;
|
|
144
|
+
path: string;
|
|
145
|
+
page?: PageData;
|
|
146
|
+
part?: PartData;
|
|
147
|
+
layout?: LayoutData;
|
|
148
|
+
fragment?: FragmentData;
|
|
149
|
+
text?: TextData;
|
|
150
|
+
image?: any;
|
|
151
|
+
regions?: RegionTree;
|
|
152
|
+
data?: any;
|
|
153
|
+
error?: any;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface TextData {
|
|
157
|
+
value: RichTextData;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface RichTextData {
|
|
161
|
+
processedHtml: string,
|
|
162
|
+
links: LinkData[],
|
|
163
|
+
macros: MacroData[],
|
|
164
|
+
images: ImageData[],
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface LinkData {
|
|
168
|
+
ref: string,
|
|
169
|
+
media: {
|
|
170
|
+
content: {
|
|
171
|
+
id: string,
|
|
172
|
+
}
|
|
173
|
+
} | null,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface MacroData {
|
|
177
|
+
ref: string;
|
|
178
|
+
name: string;
|
|
179
|
+
descriptor: string;
|
|
180
|
+
config: {
|
|
181
|
+
[name: string]: MacroConfig;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface ImageData {
|
|
186
|
+
ref: string;
|
|
187
|
+
image: {
|
|
188
|
+
id: string,
|
|
189
|
+
} | null,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface MacroConfig {
|
|
193
|
+
[key: string]: any;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
export interface RegionTree {
|
|
198
|
+
[key: string]: PageRegion;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface PageRegion {
|
|
202
|
+
name: string;
|
|
203
|
+
components: PageComponent[];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface PartData {
|
|
207
|
+
descriptor: string;
|
|
208
|
+
config: any;
|
|
209
|
+
|
|
210
|
+
[customKeysFromQuery: string]: any;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface LayoutData {
|
|
214
|
+
descriptor: string;
|
|
215
|
+
config?: any;
|
|
216
|
+
|
|
217
|
+
[customKeysFromQuery: string]: any;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface PageData {
|
|
221
|
+
descriptor: string;
|
|
222
|
+
config?: any;
|
|
223
|
+
template?: string | null;
|
|
224
|
+
regions?: RegionTree;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface FragmentData {
|
|
228
|
+
id: string;
|
|
229
|
+
fragment: {
|
|
230
|
+
components: PageComponent[];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface MetaData {
|
|
235
|
+
type: string,
|
|
236
|
+
path: string,
|
|
237
|
+
requestType: XP_REQUEST_TYPE,
|
|
238
|
+
renderMode: RENDER_MODE,
|
|
239
|
+
requestedComponent?: PageComponent,
|
|
240
|
+
canRender: boolean,
|
|
241
|
+
catchAll: boolean,
|
|
242
|
+
apiUrl: string,
|
|
243
|
+
baseUrl: string,
|
|
244
|
+
locale: string,
|
|
245
|
+
defaultLocale: string,
|
|
246
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES6",
|
|
4
|
+
"esModuleInterop": true,
|
|
5
|
+
"forceConsistentCasingInFileNames": true,
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"jsx": "preserve",
|
|
12
|
+
"outDir": "../dist",
|
|
13
|
+
"declaration": true
|
|
14
|
+
},
|
|
15
|
+
"include": [
|
|
16
|
+
"**/*.ts",
|
|
17
|
+
"**/*.tsx"
|
|
18
|
+
],
|
|
19
|
+
"exclude": []
|
|
20
|
+
}
|
|
@@ -1,39 +1,60 @@
|
|
|
1
|
+
/** Import config values from .env, .env.development and .env.production */
|
|
2
|
+
import {GetServerSidePropsContext} from 'next';
|
|
3
|
+
import {ParsedUrlQuery} from 'node:querystring';
|
|
4
|
+
import {IncomingHttpHeaders} from 'http';
|
|
5
|
+
|
|
1
6
|
const mode = process.env.MODE || process.env.NEXT_PUBLIC_MODE;
|
|
2
7
|
export const IS_DEV_MODE = (mode === 'development');
|
|
8
|
+
|
|
3
9
|
export const PURGE_CACHE_URL = '/_/enonic/cache/purge';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
+
|
|
11
|
+
export enum ENV_VARS {
|
|
12
|
+
PROJECTS = 'ENONIC_PROJECTS',
|
|
13
|
+
API = 'ENONIC_API',
|
|
14
|
+
API_TOKEN = 'ENONIC_API_TOKEN',
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
/** URL to the guillotine API */
|
|
11
18
|
const API_URL = (process.env.ENONIC_API || process.env.NEXT_PUBLIC_ENONIC_API);
|
|
19
|
+
|
|
12
20
|
/** Locales and Enonic XP projects correspondence list */
|
|
13
21
|
const PROJECTS = (process.env.ENONIC_PROJECTS || process.env.NEXT_PUBLIC_ENONIC_PROJECTS);
|
|
22
|
+
|
|
14
23
|
/** Optional utility value - defining in one place the name of the target app (the app that defines the content types, the app name is therefore part of the content type strings used both in typeselector and in query introspections) */
|
|
15
24
|
export const APP_NAME = (process.env.ENONIC_APP_NAME || process.env.NEXT_PUBLIC_ENONIC_APP_NAME);
|
|
25
|
+
|
|
16
26
|
/** Optional utility value - derived from APP_NAME, only with underscores instead of dots */
|
|
17
27
|
export const APP_NAME_UNDERSCORED = (APP_NAME || '').replace(/\./g, '_');
|
|
28
|
+
|
|
18
29
|
/** Optional utility value - derived from APP_NAME, only with dashes instead of dots */
|
|
19
30
|
export const APP_NAME_DASHED = (APP_NAME || '').replace(/\./g, '-');
|
|
31
|
+
|
|
20
32
|
const PROJECT_CONFIG_REGEXP = new RegExp('^(?:([\\w-]+):)?([^\\/\\s]+)(\\/[\\w-.]+)?$', 'i');
|
|
33
|
+
|
|
34
|
+
|
|
21
35
|
// //////////////////////////////////////////////////////////////////////// Hardcode-able constants
|
|
36
|
+
|
|
22
37
|
// URI parameter marking that a request is for a preview for CS. MUST MATCH THE VALUE OF 'FROM_XP_PARAM' on XP side.
|
|
23
38
|
export const XP_BASE_URL_HEADER = 'xpbaseurl';
|
|
24
39
|
export const RENDER_MODE_HEADER = 'content-studio-mode';
|
|
25
40
|
export const PROJECT_ID_HEADER = 'content-studio-project';
|
|
26
41
|
export const JSESSIONID_HEADER = 'jsessionid';
|
|
42
|
+
|
|
27
43
|
export const PORTAL_COMPONENT_ATTRIBUTE = 'data-portal-component-type';
|
|
28
44
|
export const PORTAL_REGION_ATTRIBUTE = 'data-portal-region';
|
|
45
|
+
|
|
29
46
|
export const FRAGMENT_CONTENTTYPE_NAME = 'portal:fragment';
|
|
30
47
|
export const FRAGMENT_DEFAULT_REGION_NAME = 'fragment';
|
|
48
|
+
|
|
31
49
|
export const PAGE_TEMPLATE_CONTENTTYPE_NAME = 'portal:page-template';
|
|
32
50
|
export const PAGE_TEMPLATE_FOLDER = 'portal:template-folder';
|
|
51
|
+
|
|
33
52
|
export const SITE_CONTENTTYPE_NAME = 'portal:site';
|
|
53
|
+
|
|
34
54
|
export const LOCALE_HEADER = 'locale';
|
|
35
55
|
export const LOCALES_HEADER = 'locales';
|
|
36
56
|
export const DEFAULT_LOCALE_HEADER = 'default-locale';
|
|
57
|
+
|
|
37
58
|
/** excluded contents:
|
|
38
59
|
* - folder
|
|
39
60
|
* - shortcuts (redirect can't be used in prerender)
|
|
@@ -50,8 +71,8 @@ export const GET_STATIC_PATHS_QUERY = `query ($count: Int) {
|
|
|
50
71
|
}
|
|
51
72
|
query: {boolean: {mustNot: [
|
|
52
73
|
{in: {field: "type", stringValues: ["base:folder", "base:shortcut"]}}
|
|
53
|
-
{like: {field: "type", value:
|
|
54
|
-
{like: {field: "_path", value:
|
|
74
|
+
{like: {field: "type", value: "media:*"}}
|
|
75
|
+
{like: {field: "_path", value: "*/_*"}}
|
|
55
76
|
]}}
|
|
56
77
|
) {
|
|
57
78
|
_name
|
|
@@ -60,49 +81,93 @@ export const GET_STATIC_PATHS_QUERY = `query ($count: Int) {
|
|
|
60
81
|
}
|
|
61
82
|
}
|
|
62
83
|
}`;
|
|
84
|
+
|
|
85
|
+
|
|
63
86
|
// ------------------------------- Exports and auxillary functions derived from values above ------------------------------------
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
87
|
+
|
|
88
|
+
export enum XP_REQUEST_TYPE {
|
|
89
|
+
COMPONENT = 'component',
|
|
90
|
+
TYPE = 'type',
|
|
91
|
+
PAGE = 'page',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export enum RENDER_MODE {
|
|
95
|
+
INLINE = 'inline',
|
|
96
|
+
EDIT = 'edit',
|
|
97
|
+
PREVIEW = 'preview',
|
|
98
|
+
LIVE = 'live',
|
|
99
|
+
ADMIN = 'admin',
|
|
100
|
+
NEXT = 'next' // Fallback: not using XP proxy but rendering directly with next.js
|
|
101
|
+
}
|
|
102
|
+
|
|
79
103
|
// TODO: Use these instead of hardcoded strings everywhere
|
|
80
|
-
export
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
export
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
export enum XP_COMPONENT_TYPE {
|
|
105
|
+
PART = 'part',
|
|
106
|
+
LAYOUT = 'layout',
|
|
107
|
+
TEXT = 'text',
|
|
108
|
+
FRAGMENT = 'fragment',
|
|
109
|
+
PAGE = 'page',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ServerSideParams
|
|
113
|
+
extends ParsedUrlQuery {
|
|
114
|
+
// String array catching a sub-path assumed to match the site-relative path of an XP content.
|
|
115
|
+
contentPath?: string[];
|
|
116
|
+
mode?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface PreviewParams {
|
|
120
|
+
contentPath: string[];
|
|
121
|
+
headers: Record<string, string>;
|
|
122
|
+
params: Record<string, string>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type Context = GetServerSidePropsContext<ServerSideParams, PreviewParams>;
|
|
126
|
+
|
|
127
|
+
export type LocaleProjectConfig = {
|
|
128
|
+
project: string;
|
|
129
|
+
site: string;
|
|
130
|
+
locale: string;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type LocaleProjectConfigs = {
|
|
134
|
+
default: LocaleProjectConfig;
|
|
135
|
+
[locale: string]: LocaleProjectConfig;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export interface MinimalContext {
|
|
139
|
+
req?: {
|
|
140
|
+
headers: IncomingHttpHeaders;
|
|
141
|
+
}
|
|
142
|
+
locale?: string;
|
|
143
|
+
defaultLocale?: string;
|
|
144
|
+
locales?: string[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface ContentPathItem {
|
|
148
|
+
params: { contentPath: string[] },
|
|
149
|
+
locale?: string,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const getRenderMode = (context?: MinimalContext): RENDER_MODE => {
|
|
153
|
+
const value = (context?.req?.headers || {})[RENDER_MODE_HEADER] as string | undefined;
|
|
154
|
+
const enumValue = RENDER_MODE[<keyof typeof RENDER_MODE>value?.toUpperCase()];
|
|
92
155
|
return enumValue || RENDER_MODE[process.env.RENDER_MODE] || RENDER_MODE.NEXT;
|
|
93
156
|
};
|
|
94
|
-
|
|
157
|
+
|
|
158
|
+
export function normalizeLocale(locale: string): string | undefined {
|
|
95
159
|
return locale !== 'default' ? locale : undefined;
|
|
96
160
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const projectId = (
|
|
161
|
+
|
|
162
|
+
export function getLocaleProjectConfig(context?: MinimalContext): LocaleProjectConfig {
|
|
163
|
+
const projectId = (context?.req?.headers || {})[PROJECT_ID_HEADER] as string | undefined;
|
|
100
164
|
if (projectId) {
|
|
101
165
|
return getLocaleProjectConfigById(projectId);
|
|
102
166
|
}
|
|
167
|
+
|
|
103
168
|
const projectsConfig = getLocaleProjectConfigs();
|
|
104
|
-
const locale =
|
|
105
|
-
let config = projectsConfig[locale];
|
|
169
|
+
const locale = context?.locale || context?.defaultLocale;
|
|
170
|
+
let config: LocaleProjectConfig = projectsConfig[locale];
|
|
106
171
|
if (!config) {
|
|
107
172
|
config = projectsConfig.default;
|
|
108
173
|
}
|
|
@@ -110,28 +175,32 @@ export function getLocaleProjectConfig(context) {
|
|
|
110
175
|
throw new Error(`No config for locale "${locale}" found in "${ENV_VARS.PROJECTS}" environmental variable.
|
|
111
176
|
Format: <default-repository-name>/<site-path>,<language>:<repository-name>/<site-path>,...`);
|
|
112
177
|
}
|
|
178
|
+
|
|
113
179
|
return config;
|
|
114
180
|
}
|
|
115
|
-
|
|
116
|
-
|
|
181
|
+
|
|
182
|
+
export function getLocaleProjectConfigById(projectId?: string, useDefault = true): LocaleProjectConfig {
|
|
183
|
+
const projects: LocaleProjectConfigs = getLocaleProjectConfigs();
|
|
117
184
|
if (!projectId) {
|
|
118
185
|
return useDefault ? projects.default : undefined;
|
|
119
186
|
}
|
|
187
|
+
|
|
120
188
|
const match = Object.values(projects).find(p => {
|
|
121
|
-
|
|
122
|
-
return ((_a = p === null || p === void 0 ? void 0 : p.project) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === projectId.toLowerCase();
|
|
189
|
+
return p?.project?.toLowerCase() === projectId.toLowerCase();
|
|
123
190
|
});
|
|
191
|
+
|
|
124
192
|
return match || (useDefault ? projects.default : undefined);
|
|
125
193
|
}
|
|
126
|
-
|
|
194
|
+
|
|
195
|
+
export function getLocaleProjectConfigs(): LocaleProjectConfigs {
|
|
127
196
|
const str = PROJECTS;
|
|
128
197
|
const envVarName = ENV_VARS.PROJECTS;
|
|
129
|
-
if (!
|
|
198
|
+
if (!str?.length) {
|
|
130
199
|
throw Error(`"${envVarName}" environmental variable is required.`);
|
|
131
200
|
}
|
|
132
|
-
const result = str.split(',').reduce((config, prjStr) => {
|
|
133
|
-
const matches = PROJECT_CONFIG_REGEXP.exec(prjStr
|
|
134
|
-
if (!
|
|
201
|
+
const result: LocaleProjectConfigs = str.split(',').reduce((config, prjStr) => {
|
|
202
|
+
const matches: RegExpExecArray = PROJECT_CONFIG_REGEXP.exec(prjStr?.trim());
|
|
203
|
+
if (!matches?.length) {
|
|
135
204
|
return config;
|
|
136
205
|
}
|
|
137
206
|
const [full, lang = 'default', project, site] = matches;
|
|
@@ -141,8 +210,7 @@ export function getLocaleProjectConfigs() {
|
|
|
141
210
|
site,
|
|
142
211
|
locale: lang,
|
|
143
212
|
};
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
213
|
+
} else {
|
|
146
214
|
throw Error(`Wrong configuration in "${envVarName}": ${prjStr}.
|
|
147
215
|
Format: <default-repository-name>/<site-path>,<language>:<repository-name>/<site-path>,...`);
|
|
148
216
|
}
|
|
@@ -160,59 +228,77 @@ export function getLocaleProjectConfigs() {
|
|
|
160
228
|
}
|
|
161
229
|
return result;
|
|
162
230
|
}
|
|
163
|
-
|
|
231
|
+
|
|
232
|
+
export function fixDoubleSlashes(str: string) {
|
|
164
233
|
return str.replace(/(^|[^:/])\/{2,}/g, '$1/');
|
|
165
234
|
}
|
|
166
|
-
|
|
235
|
+
|
|
236
|
+
export function getContentApiUrl(context?: MinimalContext): string {
|
|
167
237
|
const project = getLocaleProjectConfig(context).project;
|
|
168
238
|
const branch = getRenderMode(context) === RENDER_MODE.NEXT ? 'master' : 'draft';
|
|
239
|
+
|
|
169
240
|
return fixDoubleSlashes(`${API_URL}/${project}/${branch}`);
|
|
170
241
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const jsessionid =
|
|
242
|
+
|
|
243
|
+
export function addJsessionHeaders(headers: Object = {}, context?: MinimalContext): void {
|
|
244
|
+
const jsessionid = context?.req?.headers[JSESSIONID_HEADER];
|
|
174
245
|
if (jsessionid) {
|
|
175
246
|
headers['Cookie'] = `${JSESSIONID_HEADER}=${jsessionid}`;
|
|
176
247
|
}
|
|
177
248
|
}
|
|
178
|
-
|
|
179
|
-
|
|
249
|
+
|
|
250
|
+
export function addLocaleHeaders(headers: Object = {}, context?: Context): void {
|
|
251
|
+
const locale = context?.locale;
|
|
180
252
|
if (locale) {
|
|
181
253
|
headers[LOCALE_HEADER] = locale;
|
|
182
254
|
}
|
|
183
|
-
const locales = context
|
|
255
|
+
const locales = context?.locales;
|
|
184
256
|
if (locales) {
|
|
185
257
|
headers[LOCALES_HEADER] = JSON.stringify(locales);
|
|
186
258
|
}
|
|
187
|
-
const defaultLocale = context
|
|
259
|
+
const defaultLocale = context?.defaultLocale;
|
|
188
260
|
if (defaultLocale) {
|
|
189
261
|
headers[DEFAULT_LOCALE_HEADER] = defaultLocale;
|
|
190
262
|
}
|
|
191
263
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const header = ((
|
|
264
|
+
|
|
265
|
+
export const getXpBaseUrl = (context?: MinimalContext): string => {
|
|
266
|
+
const header = ((context?.req?.headers || {})[XP_BASE_URL_HEADER] || '') as string;
|
|
267
|
+
|
|
195
268
|
// TODO: workaround for XP pattern controller mapping not picked up in edit mode
|
|
196
269
|
return (header || '/').replace(/\/edit\//, '/inline/');
|
|
197
270
|
};
|
|
198
|
-
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
export const commonChars = (s1?: string, s2?: string, wordSeparators?: string) => {
|
|
199
274
|
let result = '';
|
|
200
275
|
if (!s1 || s1.length === 0 || !s2 || s2.length === 0) {
|
|
201
276
|
return result;
|
|
202
277
|
}
|
|
278
|
+
let lastSeparatorIndex = -1;
|
|
279
|
+
let lastCommonIndex = -1;
|
|
203
280
|
for (let i = 0; i < s1.length; i++) {
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
|
|
281
|
+
const s1El = s1[i];
|
|
282
|
+
if (wordSeparators?.indexOf(s1El) >= 0) {
|
|
283
|
+
lastSeparatorIndex = i;
|
|
207
284
|
}
|
|
208
|
-
|
|
285
|
+
if (s2[i] === s1El) {
|
|
286
|
+
lastCommonIndex = i;
|
|
287
|
+
} else {
|
|
209
288
|
break;
|
|
210
289
|
}
|
|
211
290
|
}
|
|
291
|
+
|
|
292
|
+
if (lastSeparatorIndex > 0 || lastCommonIndex === lastSeparatorIndex) {
|
|
293
|
+
result = s1.substring(0, lastSeparatorIndex);
|
|
294
|
+
} else if (lastCommonIndex > -1) {
|
|
295
|
+
result = s1.substring(0, lastCommonIndex + 1);
|
|
296
|
+
}
|
|
297
|
+
|
|
212
298
|
return result;
|
|
213
299
|
};
|
|
214
300
|
// sanitizes text according to graphql naming spec http://spec.graphql.org/October2021/#sec-Names
|
|
215
|
-
export const sanitizeGraphqlName = (text) => {
|
|
301
|
+
export const sanitizeGraphqlName = (text: string) => {
|
|
216
302
|
if (!text || text.trim().length === 0) {
|
|
217
303
|
return '';
|
|
218
304
|
}
|
|
@@ -222,7 +308,9 @@ export const sanitizeGraphqlName = (text) => {
|
|
|
222
308
|
}
|
|
223
309
|
return result;
|
|
224
310
|
};
|
|
311
|
+
|
|
225
312
|
// ---------------------------------------------------------------------------------------------------------------- Export
|
|
313
|
+
|
|
226
314
|
const adapterConstants = {
|
|
227
315
|
IS_DEV_MODE,
|
|
228
316
|
APP_NAME,
|
|
@@ -231,6 +319,7 @@ const adapterConstants = {
|
|
|
231
319
|
PORTAL_COMPONENT_ATTRIBUTE,
|
|
232
320
|
PORTAL_REGION_ATTRIBUTE,
|
|
233
321
|
};
|
|
322
|
+
|
|
234
323
|
// Verify required values
|
|
235
324
|
const NOT_REQUIRED = ['IS_DEV_MODE'];
|
|
236
325
|
Object.keys(adapterConstants).forEach(key => {
|
|
@@ -238,4 +327,5 @@ Object.keys(adapterConstants).forEach(key => {
|
|
|
238
327
|
throw Error(`constants.ts: Config value '${key}' is missing (from .env?)`);
|
|
239
328
|
}
|
|
240
329
|
});
|
|
330
|
+
|
|
241
331
|
export default adapterConstants;
|