@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
@@ -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
+ }
@@ -2,4 +2,4 @@ export * from './utils';
2
2
  export * from './guillotine/fetchContent';
3
3
  export * from './guillotine/getMetaData';
4
4
  export * from './ComponentRegistry';
5
- export * from './UrlProcessor';
5
+ export * from './UrlProcessor';
@@ -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
- export var ENV_VARS;
5
- (function (ENV_VARS) {
6
- ENV_VARS["PROJECTS"] = "ENONIC_PROJECTS";
7
- ENV_VARS["API"] = "ENONIC_API";
8
- ENV_VARS["API_TOKEN"] = "ENONIC_API_TOKEN";
9
- })(ENV_VARS || (ENV_VARS = {}));
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)
@@ -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
- export var XP_REQUEST_TYPE;
65
- (function (XP_REQUEST_TYPE) {
66
- XP_REQUEST_TYPE["COMPONENT"] = "component";
67
- XP_REQUEST_TYPE["TYPE"] = "type";
68
- XP_REQUEST_TYPE["PAGE"] = "page";
69
- })(XP_REQUEST_TYPE || (XP_REQUEST_TYPE = {}));
70
- export var RENDER_MODE;
71
- (function (RENDER_MODE) {
72
- RENDER_MODE["INLINE"] = "inline";
73
- RENDER_MODE["EDIT"] = "edit";
74
- RENDER_MODE["PREVIEW"] = "preview";
75
- RENDER_MODE["LIVE"] = "live";
76
- RENDER_MODE["ADMIN"] = "admin";
77
- RENDER_MODE["NEXT"] = "next"; // Fallback: not using XP proxy but rendering directly with next.js
78
- })(RENDER_MODE || (RENDER_MODE = {}));
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 var XP_COMPONENT_TYPE;
81
- (function (XP_COMPONENT_TYPE) {
82
- XP_COMPONENT_TYPE["PART"] = "part";
83
- XP_COMPONENT_TYPE["LAYOUT"] = "layout";
84
- XP_COMPONENT_TYPE["TEXT"] = "text";
85
- XP_COMPONENT_TYPE["FRAGMENT"] = "fragment";
86
- XP_COMPONENT_TYPE["PAGE"] = "page";
87
- })(XP_COMPONENT_TYPE || (XP_COMPONENT_TYPE = {}));
88
- export const getRenderMode = (context) => {
89
- var _a;
90
- const value = (((_a = context === null || context === void 0 ? void 0 : context.req) === null || _a === void 0 ? void 0 : _a.headers) || {})[RENDER_MODE_HEADER];
91
- const enumValue = RENDER_MODE[value === null || value === void 0 ? void 0 : value.toUpperCase()];
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
- export function normalizeLocale(locale) {
157
+
158
+ export function normalizeLocale(locale: string): string | undefined {
95
159
  return locale !== 'default' ? locale : undefined;
96
160
  }
97
- export function getLocaleProjectConfig(context) {
98
- var _a;
99
- const projectId = (((_a = context === null || context === void 0 ? void 0 : context.req) === null || _a === void 0 ? void 0 : _a.headers) || {})[PROJECT_ID_HEADER];
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 = (context === null || context === void 0 ? void 0 : context.locale) || (context === null || context === void 0 ? void 0 : context.defaultLocale);
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
- export function getLocaleProjectConfigById(projectId, useDefault = true) {
116
- const projects = getLocaleProjectConfigs();
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
- var _a;
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
- export function getLocaleProjectConfigs() {
194
+
195
+ export function getLocaleProjectConfigs(): LocaleProjectConfigs {
127
196
  const str = PROJECTS;
128
197
  const envVarName = ENV_VARS.PROJECTS;
129
- if (!(str === null || str === void 0 ? void 0 : str.length)) {
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 === null || prjStr === void 0 ? void 0 : prjStr.trim());
134
- if (!(matches === null || matches === void 0 ? void 0 : matches.length)) {
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
- export function fixDoubleSlashes(str) {
231
+
232
+ export function fixDoubleSlashes(str: string) {
164
233
  return str.replace(/(^|[^:/])\/{2,}/g, '$1/');
165
234
  }
166
- export function getContentApiUrl(context) {
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
- export function addJsessionHeaders(headers = {}, context) {
172
- var _a;
173
- const jsessionid = (_a = context === null || context === void 0 ? void 0 : context.req) === null || _a === void 0 ? void 0 : _a.headers[JSESSIONID_HEADER];
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
- export function addLocaleHeaders(headers = {}, context) {
179
- const locale = context === null || context === void 0 ? void 0 : context.locale;
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 === null || context === void 0 ? void 0 : context.locales;
255
+ const locales = context?.locales;
184
256
  if (locales) {
185
257
  headers[LOCALES_HEADER] = JSON.stringify(locales);
186
258
  }
187
- const defaultLocale = context === null || context === void 0 ? void 0 : context.defaultLocale;
259
+ const defaultLocale = context?.defaultLocale;
188
260
  if (defaultLocale) {
189
261
  headers[DEFAULT_LOCALE_HEADER] = defaultLocale;
190
262
  }
191
263
  }
192
- export const getXpBaseUrl = (context) => {
193
- var _a;
194
- const header = ((((_a = context === null || context === void 0 ? void 0 : context.req) === null || _a === void 0 ? void 0 : _a.headers) || {})[XP_BASE_URL_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
- export const commonChars = (s1, s2) => {
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 s1Element = s1[i];
205
- if (s2[i] === s1Element) {
206
- result += s1Element;
281
+ const s1El = s1[i];
282
+ if (wordSeparators?.indexOf(s1El) >= 0) {
283
+ lastSeparatorIndex = i;
207
284
  }
208
- else {
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;