@enonic/nextjs-adapter 1.0.2 → 2.0.0-B1

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.
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import { PageComponent } from './guillotine/getMetaData';
3
- import { Context } from './guillotine/fetchContent';
3
+ import { Context } from './utils';
4
4
  /**
5
5
  * Object that configures the handling of a particular content type. All attributes are optional (see examples below), and missing values will fall back to default behavior:
6
6
  * - 'query' (used in fetchContent.ts) Guillotine query for fetching content data, may also have a function that supplies guillotine variables. So, 'query' can EITHER be only a query string, OR also add a get-guillotine-variables function. In the latter case, 'query' can be an object with 'query' and 'variables' attributes, or an array where the query string is first and the get-variables function is second. Either way, the get-variables function takes two arguments: path (content path, mandatory) and context (next.js-supplied Context from getServerSideProps etc. Optional, and requires that fetchContent is called with the context, of course).
@@ -10,7 +10,7 @@ function toSelectorName(type) {
10
10
  }
11
11
  }
12
12
  export const CATCH_ALL = '*';
13
- class ComponentRegistry {
13
+ export class ComponentRegistry {
14
14
  static getSelector(name) {
15
15
  switch (name) {
16
16
  case 'contentType':
@@ -119,4 +119,3 @@ ComponentRegistry.components = {};
119
119
  ComponentRegistry.parts = {};
120
120
  ComponentRegistry.layouts = {};
121
121
  ComponentRegistry.macros = {};
122
- export { ComponentRegistry };
package/UrlProcessor.d.ts CHANGED
@@ -11,14 +11,15 @@ export declare class UrlProcessor {
11
11
  private static startSlashPattern;
12
12
  private static localhostPattern;
13
13
  private static siteKey;
14
- static process(url: string, meta: MetaData): string;
14
+ static process(url: string, meta: MetaData, serverSide?: boolean): string;
15
15
  static setSiteKey(key: string): void;
16
16
  static isMediaLink(ref: string, linkData: LinkData[]): boolean;
17
17
  static isContentImage(ref: string, linkData: ImageData[]): boolean;
18
18
  static processSrcSet(srcset: string, meta: MetaData): string;
19
19
  private static stripApiUrl;
20
- private static isPageUrl;
20
+ private static isAttachmentUrl;
21
21
  private static startsWithHash;
22
+ private static isRelative;
22
23
  private static getApiUrl;
23
24
  }
24
- export declare const getUrl: (url: string, meta: MetaData) => string;
25
+ export declare const getUrl: (url: string, meta: MetaData, serverSide?: boolean) => string;
package/UrlProcessor.js CHANGED
@@ -1,29 +1,34 @@
1
- import { commonChars, RENDER_MODE, SITE_KEY } from './utils';
2
- import { useRouter } from 'next/router';
3
- class UrlProcessor {
4
- static process(url, meta) {
1
+ import { commonChars, fixDoubleSlashes, getLocaleProjectConfigs, RENDER_MODE } from './utils';
2
+ import { addBasePath } from 'next/dist/client/add-base-path';
3
+ export class UrlProcessor {
4
+ static process(url, meta, serverSide = false) {
5
+ if (this.startsWithHash(url) || !meta || (this.isAttachmentUrl(url)) && meta.renderMode === RENDER_MODE.NEXT) {
6
+ // do not process if:
7
+ // - url starts with #
8
+ // - meta is absent
9
+ // - attachment urls in NEXT mode
10
+ return url;
11
+ }
5
12
  let result;
6
- if (this.startsWithHash(url) || !meta || !this.isPageUrl(url) && meta.renderMode === RENDER_MODE.NEXT) {
7
- // keep page-relative urls starting with hash
8
- // keep urls absolute in next mode for everything but page urls
9
- result = url;
13
+ const apiUrl = this.getApiUrl(meta);
14
+ const strippedUrl = this.stripApiUrl(url, apiUrl);
15
+ const baseUrl = (meta === null || meta === void 0 ? void 0 : meta.baseUrl) && (meta === null || meta === void 0 ? void 0 : meta.baseUrl) !== '/' ? meta.baseUrl : '';
16
+ if (meta.renderMode === RENDER_MODE.NEXT) {
17
+ // only add basePath and locale in next mode
18
+ result = `/${strippedUrl}`;
19
+ if (!this.isRelative(url) && meta.locale !== meta.defaultLocale) {
20
+ // do not append locale to local assets (having relative urls)
21
+ result = `/${meta.locale}${result}`;
22
+ }
23
+ if (!serverSide) {
24
+ // no need for baseurl and basepath on server
25
+ result = addBasePath(`${baseUrl}${result}`);
26
+ }
10
27
  }
11
28
  else {
12
- const apiUrl = this.getApiUrl(meta);
13
- const strippedUrl = this.stripApiUrl(url, apiUrl);
14
- let basePath;
15
- // only add basePath in next
16
- if (meta.renderMode === RENDER_MODE.NEXT) {
17
- try {
18
- basePath = useRouter().basePath;
19
- }
20
- catch (e) {
21
- console.error('Error getting basePath from nextjs router', e);
22
- }
23
- }
24
- result = (basePath && basePath !== '/' ? basePath : '') + ((meta === null || meta === void 0 ? void 0 : meta.baseUrl) || '') + strippedUrl;
29
+ result = `${baseUrl}/${strippedUrl}`;
25
30
  }
26
- return result;
31
+ return fixDoubleSlashes(result);
27
32
  }
28
33
  static setSiteKey(key) {
29
34
  this.siteKey = key;
@@ -58,12 +63,15 @@ class UrlProcessor {
58
63
  const remaining = common.length > 0 ? normalUrl.substring(common.length) : normalUrl;
59
64
  return (remaining.length > 0 && remaining.charAt(0) === '/') ? remaining.substring(1) : remaining;
60
65
  }
61
- static isPageUrl(url) {
62
- return !this.IMG_ATMT_REGEXP.test(url);
66
+ static isAttachmentUrl(url) {
67
+ return this.IMG_ATMT_REGEXP.test(url);
63
68
  }
64
69
  static startsWithHash(url) {
65
70
  return (url === null || url === void 0 ? void 0 : url.charAt(0)) == '#';
66
71
  }
72
+ static isRelative(url) {
73
+ return !/^(?:ht|f)tps?:\/\/[^ :\r\n\t]+/.test(url);
74
+ }
67
75
  static getApiUrl(meta) {
68
76
  var _a, _b;
69
77
  let combinedUrl = ((_a = meta.apiUrl) === null || _a === void 0 ? void 0 : _a.replace(this.endSlashPattern, '')) || '';
@@ -83,6 +91,7 @@ UrlProcessor.IMG_ATMT_REGEXP = /_\/image\/|_\/attachment\//;
83
91
  UrlProcessor.endSlashPattern = /\/+$/;
84
92
  UrlProcessor.startSlashPattern = /^\/+/;
85
93
  UrlProcessor.localhostPattern = /localhost/;
86
- export { UrlProcessor };
87
- UrlProcessor.setSiteKey(SITE_KEY);
94
+ // Actual site is going to be set in fetchContent method, but set a default fallback here too
95
+ const defaultConfig = getLocaleProjectConfigs().default;
96
+ UrlProcessor.setSiteKey(defaultConfig.site);
88
97
  export const getUrl = UrlProcessor.process.bind(UrlProcessor);
package/baseMappings.js CHANGED
@@ -7,10 +7,14 @@ import BaseLayout from './views/BaseLayout';
7
7
  import TextView from './views/Text';
8
8
  import DefaultMacro from './views/macros/DefaultMacro';
9
9
  import DisableMacro from './views/macros/DisableMacro';
10
+ import { getShortcutQuery } from './query/Shortcut';
10
11
  // Base Content Types
11
12
  ComponentRegistry.addContentType(FRAGMENT_CONTENTTYPE_NAME, {
12
13
  view: FragmentView,
13
14
  });
15
+ ComponentRegistry.addContentType('base:shortcut', {
16
+ query: getShortcutQuery,
17
+ });
14
18
  // Base Components
15
19
  ComponentRegistry.addComponent(XP_COMPONENT_TYPE.PAGE, {
16
20
  view: BasePage,
@@ -1,17 +1,13 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import { MetaData, PageComponent } from './getMetaData';
4
- import { ParsedUrlQuery } from 'node:querystring';
5
- import { type GetServerSidePropsContext } from 'next';
6
- import { IncomingMessage } from 'http';
7
- import { type NextApiRequestCookies } from 'next/dist/server/api-utils';
2
+ import { ContentPathItem, Context, LocaleProjectConfig } from '../utils';
3
+ import { GetStaticPropsResult } from 'next';
8
4
  type Result = {
9
5
  error?: {
10
6
  code: string;
11
7
  message: string;
12
8
  } | null;
13
9
  };
14
- type GuillotineResult = Result & {
10
+ export type GuillotineResult = Result & {
15
11
  [dataKey: string]: any;
16
12
  };
17
13
  export type FetchContentResult = Result & {
@@ -20,37 +16,23 @@ export type FetchContentResult = Result & {
20
16
  meta: MetaData;
21
17
  page: PageComponent | null;
22
18
  };
23
- export interface ServerSideParams extends ParsedUrlQuery {
24
- contentPath?: string[];
25
- mode?: string;
26
- }
27
- export interface PreviewParams {
28
- contentPath: string[];
29
- headers: Record<string, string>;
30
- params: Record<string, string>;
31
- }
32
- export type Context = GetServerSidePropsContext<ServerSideParams, PreviewParams>;
33
- export interface MinimalContext {
34
- req: IncomingMessage & {
35
- cookies: NextApiRequestCookies;
36
- };
37
- }
38
19
  /**
39
20
  * Sends one query to the guillotine API and asks for content type, then uses the type to select a second query and variables, which is sent to the API and fetches content data.
40
21
  * @param contentPath string or string array: pre-split or slash-delimited _path to a content available on the API
41
22
  * @returns FetchContentResult object: {data?: T, error?: {code, message}}
42
23
  */
43
- export type ContentFetcher = (contentPath: string | string[], context: Context) => Promise<FetchContentResult>;
24
+ export type ContentFetcher = (contentPath: string | string[], context?: Context) => Promise<FetchContentResult>;
44
25
  export type ContentApiBaseBody = {
45
26
  query?: string;
46
27
  variables?: {
47
28
  path?: string;
29
+ [key: string]: string | number | undefined;
48
30
  };
49
31
  };
50
32
  /** Generic fetch */
51
- export declare const fetchFromApi: (apiUrl: string, body: ContentApiBaseBody, headers?: {}, method?: string) => Promise<any>;
33
+ export declare const fetchFromApi: (apiUrl: string, body: ContentApiBaseBody, projectConfig: LocaleProjectConfig, headers?: {}, method?: string) => Promise<any>;
52
34
  /** Guillotine-specialized fetch, using the generic fetch above */
53
- export declare const fetchGuillotine: (contentApiUrl: string, body: ContentApiBaseBody, headers?: {}) => Promise<GuillotineResult>;
35
+ export declare const fetchGuillotine: (contentApiUrl: string, body: ContentApiBaseBody, projectConfig: LocaleProjectConfig, headers?: {}) => Promise<GuillotineResult>;
54
36
  /**
55
37
  * Default fetchContent function, built with params from imports.
56
38
  * It runs custom content-type-specific guillotine calls against an XP guillotine endpoint, returns content data, error and some meta data
@@ -60,4 +42,7 @@ export declare const fetchGuillotine: (contentApiUrl: string, body: ContentApiBa
60
42
  * @returns FetchContentResult object: {data?: T, error?: {code, message}}
61
43
  */
62
44
  export declare const fetchContent: ContentFetcher;
45
+ export declare function createResponse(props: FetchContentResult, context?: Context): GetStaticPropsResult<FetchContentResult>;
46
+ export declare function fetchContentPathsForAllLocales(path: string, query?: string, countPerLocale?: number): Promise<ContentPathItem[]>;
47
+ export declare function fetchContentPathsForLocale(path: string, config: LocaleProjectConfig, query?: string, count?: number): Promise<ContentPathItem[]>;
63
48
  export {};
@@ -8,20 +8,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { getMetaQuery, pageFragmentQuery } from './getMetaData';
11
- import adapterConstants, { APP_NAME, APP_NAME_DASHED, FRAGMENT_CONTENTTYPE_NAME, FRAGMENT_DEFAULT_REGION_NAME, getContentApiUrl, getJsessionHeaders, getRenderMode, getXpBaseUrl, IS_DEV_MODE, PAGE_TEMPLATE_CONTENTTYPE_NAME, PAGE_TEMPLATE_FOLDER, RENDER_MODE, sanitizeGraphqlName, SITE_KEY, XP_COMPONENT_TYPE, XP_REQUEST_TYPE, } from '../utils';
11
+ import adapterConstants, { addJsessionHeaders, addLocaleHeaders, APP_NAME, APP_NAME_DASHED, FRAGMENT_CONTENTTYPE_NAME, FRAGMENT_DEFAULT_REGION_NAME, GET_STATIC_PATHS_QUERY, getContentApiUrl, getLocaleProjectConfig, getLocaleProjectConfigs, getRenderMode, getXpBaseUrl, IS_DEV_MODE, normalizeLocale, PAGE_TEMPLATE_CONTENTTYPE_NAME, PAGE_TEMPLATE_FOLDER, RENDER_MODE, sanitizeGraphqlName, XP_COMPONENT_TYPE, XP_REQUEST_TYPE, } from '../utils';
12
12
  import { ComponentRegistry } from '../ComponentRegistry';
13
+ import { getUrl, UrlProcessor } from '../UrlProcessor';
13
14
  const NO_PROPS_PROCESSOR = (props) => __awaiter(void 0, void 0, void 0, function* () { var _a; return (_a = yield props) !== null && _a !== void 0 ? _a : {}; });
14
15
  const ALIAS_PREFIX = 'request';
15
16
  const GUILLOTINE_QUERY_REGEXP = /^\s*query\s*(?:\((.*)*\))?\s*{\s*guillotine\s*{((?:.|\s)+)}\s*}\s*$/;
16
17
  const GRAPHQL_FRAGMENTS_REGEXP = /fragment\s+.+\s+on\s+.+\s*{[\s\w{}().,:"'`]+}/;
17
18
  /** Generic fetch */
18
- export const fetchFromApi = (apiUrl, body, headers, method = 'POST') => __awaiter(void 0, void 0, void 0, function* () {
19
+ export const fetchFromApi = (apiUrl, body, projectConfig, headers, method = 'POST') => __awaiter(void 0, void 0, void 0, function* () {
19
20
  const options = {
20
21
  method,
21
22
  headers: {
22
23
  'Accept': 'application/json',
23
24
  'Content-Type': 'application/json',
24
- 'X-Guillotine-SiteKey': SITE_KEY,
25
+ 'X-Guillotine-SiteKey': projectConfig.site,
25
26
  },
26
27
  body: JSON.stringify(body),
27
28
  };
@@ -64,7 +65,7 @@ export const fetchFromApi = (apiUrl, body, headers, method = 'POST') => __awaite
64
65
  return json;
65
66
  });
66
67
  /** Guillotine-specialized fetch, using the generic fetch above */
67
- export const fetchGuillotine = (contentApiUrl, body, headers) => __awaiter(void 0, void 0, void 0, function* () {
68
+ export const fetchGuillotine = (contentApiUrl, body, projectConfig, headers) => __awaiter(void 0, void 0, void 0, function* () {
68
69
  var _b;
69
70
  if (typeof body.query !== 'string' || !body.query.trim()) {
70
71
  return {
@@ -75,20 +76,20 @@ export const fetchGuillotine = (contentApiUrl, body, headers) => __awaiter(void
75
76
  };
76
77
  }
77
78
  const path = (_b = body.variables) === null || _b === void 0 ? void 0 : _b.path;
78
- const pathMessage = path ? `(path = ${JSON.stringify(path)})` : '';
79
- const result = yield fetchFromApi(contentApiUrl, body, headers)
79
+ const pathMessage = path ? JSON.stringify(path) : '';
80
+ const result = yield fetchFromApi(contentApiUrl, body, projectConfig, headers)
80
81
  .then(json => {
81
82
  let errors = (json || {}).errors;
82
83
  if (errors) {
83
84
  if (!Array.isArray(errors)) {
84
85
  errors = [errors];
85
86
  }
86
- console.warn(`${errors.length} error(s) when trying to fetch data ${pathMessage}:`);
87
+ console.error(`${errors.length} error(s) when fetching data from: ${contentApiUrl}`);
88
+ console.error(`headers: ${JSON.stringify(headers, null, 2)}
89
+ variables: ${JSON.stringify(body.variables, null, 2)}`);
87
90
  errors.forEach(error => {
88
91
  console.error(error);
89
92
  });
90
- console.warn(`Query:\n${body.query}`);
91
- console.warn(`Variables: ${JSON.stringify(body.variables, null, 2)}`);
92
93
  return {
93
94
  error: {
94
95
  code: '500',
@@ -110,7 +111,7 @@ export const fetchGuillotine = (contentApiUrl, body, headers) => __awaiter(void
110
111
  return result;
111
112
  });
112
113
  // /////////////////////////////////////////////////////////////////////////////// Specific fetch wrappers:
113
- const fetchMetaData = (contentApiUrl, xpContentPath, headers) => __awaiter(void 0, void 0, void 0, function* () {
114
+ const fetchMetaData = (contentApiUrl, xpContentPath, projectConfig, headers) => __awaiter(void 0, void 0, void 0, function* () {
114
115
  var _c;
115
116
  const body = {
116
117
  query: getMetaQuery(pageFragmentQuery()),
@@ -118,7 +119,7 @@ const fetchMetaData = (contentApiUrl, xpContentPath, headers) => __awaiter(void
118
119
  path: xpContentPath,
119
120
  },
120
121
  };
121
- const metaResult = yield fetchGuillotine(contentApiUrl, body, headers);
122
+ const metaResult = yield fetchGuillotine(contentApiUrl, body, projectConfig, headers);
122
123
  if (metaResult.error) {
123
124
  return metaResult;
124
125
  }
@@ -128,12 +129,12 @@ const fetchMetaData = (contentApiUrl, xpContentPath, headers) => __awaiter(void
128
129
  };
129
130
  }
130
131
  });
131
- const fetchContentData = (contentApiUrl, xpContentPath, query, variables, headers) => __awaiter(void 0, void 0, void 0, function* () {
132
+ const fetchContentData = (contentApiUrl, xpContentPath, projectConfig, query, variables, headers) => __awaiter(void 0, void 0, void 0, function* () {
132
133
  const body = { query };
133
134
  if (variables && Object.keys(variables).length > 0) {
134
135
  body.variables = variables;
135
136
  }
136
- const contentResults = yield fetchGuillotine(contentApiUrl, body, headers);
137
+ const contentResults = yield fetchGuillotine(contentApiUrl, body, projectConfig, headers);
137
138
  if (contentResults.error) {
138
139
  return contentResults;
139
140
  }
@@ -245,8 +246,8 @@ function buildComponentTree(comps, rootComp, contentType) {
245
246
  if (isFragmentType) {
246
247
  rootComp.regions = rootTree;
247
248
  }
248
- comps.forEach(cmp => {
249
- var _a;
249
+ (comps || []).forEach(cmp => {
250
+ var _a, _b, _c;
250
251
  const cmpPath = parseComponentPath(contentType, cmp.path);
251
252
  if (cmp.path === '/') {
252
253
  if (cmp.type === XP_COMPONENT_TYPE.PAGE && rootComp.type === XP_COMPONENT_TYPE.PAGE) {
@@ -264,12 +265,12 @@ function buildComponentTree(comps, rootComp, contentType) {
264
265
  }
265
266
  if (cmp.type === XP_COMPONENT_TYPE.FRAGMENT) {
266
267
  // build component subtree for fragment
267
- cmp = buildComponentTree(cmp.fragment.fragment.components, cmp, contentType);
268
+ cmp = buildComponentTree((_b = (_a = cmp.fragment) === null || _a === void 0 ? void 0 : _a.fragment) === null || _b === void 0 ? void 0 : _b.components, cmp, contentType);
268
269
  }
269
270
  const region = getParentRegion(rootTree, contentType, cmpPath, comps, true);
270
271
  if (region) {
271
272
  // getting the index of component from string like '/main/0/left/11'
272
- const cmpIndex = (_a = cmpPath[cmpPath.length - 1]) === null || _a === void 0 ? void 0 : _a.index;
273
+ const cmpIndex = (_c = cmpPath[cmpPath.length - 1]) === null || _c === void 0 ? void 0 : _c.index;
273
274
  if (cmpIndex >= 0) {
274
275
  region.components.splice(cmpIndex, 0, cmp);
275
276
  }
@@ -369,6 +370,7 @@ function getComponentConfig(cmp) {
369
370
  return cmpData && 'config' in cmpData ? cmpData.config : undefined;
370
371
  }
371
372
  function collectComponentDescriptors(components, componentRegistry, xpContentPath, context) {
373
+ var _a, _b;
372
374
  const descriptors = [];
373
375
  for (const cmp of (components || [])) {
374
376
  processComponentConfig(APP_NAME, APP_NAME_DASHED, cmp);
@@ -391,7 +393,7 @@ function collectComponentDescriptors(components, componentRegistry, xpContentPat
391
393
  }
392
394
  else {
393
395
  // look for parts inside fragments
394
- const fragPartDescs = collectComponentDescriptors(cmp.fragment.fragment.components, componentRegistry, xpContentPath, context);
396
+ const fragPartDescs = collectComponentDescriptors((_b = (_a = cmp.fragment) === null || _a === void 0 ? void 0 : _a.fragment) === null || _b === void 0 ? void 0 : _b.components, componentRegistry, xpContentPath, context);
395
397
  if (fragPartDescs.length) {
396
398
  descriptors.push(...fragPartDescs);
397
399
  }
@@ -467,7 +469,7 @@ function createPageData(contentType, components, componentPath) {
467
469
  }
468
470
  return page;
469
471
  }
470
- function createMetaData(contentType, contentPath, requestType, renderMode, apiUrl, baseUrl, requestedComponentPath, pageCmp, components = []) {
472
+ function createMetaData(contentType, contentPath, requestType, renderMode, apiUrl, baseUrl, locale, defaultLocale, requestedComponentPath, pageCmp, components = []) {
471
473
  var _a;
472
474
  // .meta will be visible in final rendered inline props.
473
475
  // Only adding some .meta attributes here on certain conditions
@@ -481,6 +483,8 @@ function createMetaData(contentType, contentPath, requestType, renderMode, apiUr
481
483
  catchAll: false,
482
484
  apiUrl,
483
485
  baseUrl,
486
+ locale,
487
+ defaultLocale,
484
488
  };
485
489
  if (requestedComponentPath) {
486
490
  meta.requestedComponent = components.find(cmp => cmp.path === requestedComponentPath);
@@ -504,7 +508,7 @@ function createMetaData(contentType, contentPath, requestType, renderMode, apiUr
504
508
  }
505
509
  return meta;
506
510
  }
507
- function errorResponse(code = '500', message = 'Unknown error', requestType, renderMode, apiUrl, baseUrl, contentPath) {
511
+ function errorResponse(code = '500', message = 'Unknown error', requestType, renderMode, apiUrl, baseUrl, locale, defaultLocale, contentPath) {
508
512
  return {
509
513
  error: {
510
514
  code,
@@ -522,6 +526,8 @@ function errorResponse(code = '500', message = 'Unknown error', requestType, ren
522
526
  catchAll: false,
523
527
  apiUrl,
524
528
  baseUrl,
529
+ locale,
530
+ defaultLocale,
525
531
  },
526
532
  };
527
533
  }
@@ -578,11 +584,18 @@ const buildContentFetcher = (config) => {
578
584
  const { APP_NAME, APP_NAME_DASHED, componentRegistry, } = config;
579
585
  return (contentPathOrArray, context) => __awaiter(void 0, void 0, void 0, function* () {
580
586
  var _a, _b;
581
- const headers = getJsessionHeaders(context);
587
+ if (context.preview) {
588
+ populateEnonicHeaders(context);
589
+ }
590
+ const headers = {};
591
+ addJsessionHeaders(headers, context);
592
+ addLocaleHeaders(headers, context);
582
593
  const xpBaseUrl = getXpBaseUrl(context);
583
594
  const contentApiUrl = getContentApiUrl(context);
595
+ const projectConfig = getLocaleProjectConfig(context);
584
596
  const renderMode = getRenderMode(context);
585
597
  let requestType = XP_REQUEST_TYPE.TYPE;
598
+ UrlProcessor.setSiteKey(projectConfig.site);
586
599
  try {
587
600
  const requestContentPath = getCleanContentPathArrayOrThrow400(contentPathOrArray);
588
601
  const [siteRelativeContentPath, componentPath] = getContentAndComponentPaths(requestContentPath, context);
@@ -591,30 +604,30 @@ const buildContentFetcher = (config) => {
591
604
  requestType = XP_REQUEST_TYPE.COMPONENT;
592
605
  }
593
606
  // ///////////// FIRST GUILLOTINE CALL FOR METADATA /////////////////
594
- const metaResult = yield fetchMetaData(contentApiUrl, '${site}/' + siteRelativeContentPath, headers);
607
+ const metaResult = yield fetchMetaData(contentApiUrl, '${site}/' + siteRelativeContentPath, projectConfig, headers);
595
608
  // ///////////////////////////////////////////////////////////////////////
596
609
  const { _path, type } = metaResult.meta || {};
597
610
  const contentPath = _path || siteRelativeContentPath;
598
611
  if (metaResult.error) {
599
612
  console.error(metaResult.error);
600
- return errorResponse(metaResult.error.code, metaResult.error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
613
+ return errorResponse(metaResult.error.code, metaResult.error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
601
614
  }
602
615
  if (!metaResult.meta) {
603
- return errorResponse('404', 'No meta data found for content, most likely content does not exist', requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
616
+ return errorResponse('404', 'No meta data found for content, most likely content does not exist', requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
604
617
  }
605
618
  else if (!type) {
606
- return errorResponse('500', "Server responded with incomplete meta data: missing content 'type' attribute.", requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
619
+ return errorResponse('500', "Server responded with incomplete meta data: missing content 'type' attribute.", requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
607
620
  }
608
621
  else if (renderMode === RENDER_MODE.NEXT && !IS_DEV_MODE &&
609
622
  (type === FRAGMENT_CONTENTTYPE_NAME ||
610
623
  type === PAGE_TEMPLATE_CONTENTTYPE_NAME ||
611
624
  type === PAGE_TEMPLATE_FOLDER)) {
612
- return errorResponse('404', `Content type [${type}] is not accessible in ${renderMode} mode`, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
625
+ return errorResponse('404', `Content type [${type}] is not accessible in ${renderMode} mode`, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
613
626
  }
614
627
  const components = restrictComponentsToPath(type, metaResult.meta.components, componentPath);
615
628
  if (componentPath && !components.length) {
616
629
  // component was not found
617
- return errorResponse('404', `Component ${componentPath} was not found`, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
630
+ return errorResponse('404', `Component ${componentPath} was not found`, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
618
631
  }
619
632
  if (requestType !== XP_REQUEST_TYPE.COMPONENT && components.length > 0) {
620
633
  requestType = XP_REQUEST_TYPE.PAGE;
@@ -653,14 +666,14 @@ const buildContentFetcher = (config) => {
653
666
  }
654
667
  const { query, variables } = combineMultipleQueries(allDescriptors);
655
668
  if (!query.trim()) {
656
- return errorResponse('400', `Missing or empty query override for content type ${type}`, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
669
+ return errorResponse('400', `Missing or empty query override for content type ${type}`, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
657
670
  }
658
671
  // /////////////// SECOND GUILLOTINE CALL FOR DATA //////////////////////
659
- const contentResults = yield fetchContentData(contentApiUrl, contentPath, query, variables, headers);
672
+ const contentResults = yield fetchContentData(contentApiUrl, contentPath, projectConfig, query, variables, headers);
660
673
  // ///////////////////////////////////////////////////////////////////////////
661
674
  if (contentResults.error) {
662
675
  console.error(contentResults.error);
663
- return errorResponse(contentResults.error.code, contentResults.error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
676
+ return errorResponse(contentResults.error.code, contentResults.error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
664
677
  }
665
678
  // Apply processors to every component
666
679
  const datas = yield applyProcessors(allDescriptors, contentResults, context);
@@ -696,7 +709,7 @@ const buildContentFetcher = (config) => {
696
709
  }
697
710
  }
698
711
  const page = createPageData(type, components);
699
- const meta = createMetaData(type, siteRelativeContentPath, requestType, renderMode, contentApiUrl, xpBaseUrl, componentPath, page, components);
712
+ const meta = createMetaData(type, siteRelativeContentPath, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, componentPath, page, components);
700
713
  return {
701
714
  data: contentData,
702
715
  common,
@@ -716,7 +729,7 @@ const buildContentFetcher = (config) => {
716
729
  message: e.message,
717
730
  };
718
731
  }
719
- return errorResponse(error.code, error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPathOrArray.toString());
732
+ return errorResponse(error.code, error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPathOrArray.toString());
720
733
  }
721
734
  });
722
735
  };
@@ -730,3 +743,80 @@ const buildContentFetcher = (config) => {
730
743
  * @returns FetchContentResult object: {data?: T, error?: {code, message}}
731
744
  */
732
745
  export const fetchContent = buildContentFetcher(Object.assign(Object.assign({}, adapterConstants), { componentRegistry: ComponentRegistry }));
746
+ export function createResponse(props, context) {
747
+ var _a, _b, _c, _d;
748
+ const { data, meta, error } = props;
749
+ const pageUrl = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.get) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.target) === null || _c === void 0 ? void 0 : _c.pageUrl;
750
+ if (meta.type === 'base:shortcut' && pageUrl) {
751
+ if (meta.renderMode !== RENDER_MODE.NEXT) {
752
+ // console.debug(`Returning 404 for shortcut in ${RENDER_MODE.NEXT} mode`);
753
+ // do not show shortcut targets in preview/edit mode
754
+ return {
755
+ notFound: true,
756
+ };
757
+ }
758
+ const destination = getUrl(pageUrl, meta, true);
759
+ // console.debug('Redirecting to', destination);
760
+ return {
761
+ redirect: {
762
+ statusCode: 307,
763
+ destination,
764
+ },
765
+ };
766
+ }
767
+ // we can not set 418 for static paths,
768
+ // but we can show 404 instead to be handled in CS
769
+ const canNotRender = meta && !meta.canRender && meta.renderMode !== RENDER_MODE.EDIT;
770
+ const catchAllInNextProdMode = (meta === null || meta === void 0 ? void 0 : meta.renderMode) === RENDER_MODE.NEXT && !IS_DEV_MODE && (meta === null || meta === void 0 ? void 0 : meta.catchAll);
771
+ const notFound = (error && error.code === '404') || ((_d = context.res) === null || _d === void 0 ? void 0 : _d.statusCode) === 404 || canNotRender || catchAllInNextProdMode || undefined;
772
+ // console.debug('Returning view ' + (notFound ? '(not found)' : ''));
773
+ return {
774
+ notFound,
775
+ props,
776
+ revalidate: 3600, // In seconds, meaning every hour
777
+ };
778
+ }
779
+ function populateEnonicHeaders(context) {
780
+ const pd = context.previewData;
781
+ if (!pd) {
782
+ return;
783
+ }
784
+ const req = context.req || {};
785
+ req.headers = Object.assign(req.headers || {}, pd.headers);
786
+ context.params = Object.assign(context.params || {}, pd.params);
787
+ context.req = req;
788
+ }
789
+ export function fetchContentPathsForAllLocales(path, query = GET_STATIC_PATHS_QUERY, countPerLocale) {
790
+ return __awaiter(this, void 0, void 0, function* () {
791
+ const promises = Object.values(getLocaleProjectConfigs()).map(config => fetchContentPathsForLocale(path, config, query, countPerLocale));
792
+ return Promise.all(promises).then(results => {
793
+ return results.reduce((all, localePaths) => all.concat(localePaths), []);
794
+ });
795
+ });
796
+ }
797
+ export function fetchContentPathsForLocale(path, config, query = GET_STATIC_PATHS_QUERY, count) {
798
+ return __awaiter(this, void 0, void 0, function* () {
799
+ const contentApiUrl = getContentApiUrl({ locale: config.locale });
800
+ const body = {
801
+ query,
802
+ variables: {
803
+ path,
804
+ count,
805
+ },
806
+ };
807
+ return fetchGuillotine(contentApiUrl, body, config).then((results) => {
808
+ return results.guillotine.query.reduce((prev, child) => {
809
+ var _a;
810
+ const regexp = new RegExp(`/${(_a = child.site) === null || _a === void 0 ? void 0 : _a._name}/?`);
811
+ const contentPath = child._path.replace(regexp, '');
812
+ prev.push({
813
+ params: {
814
+ contentPath: contentPath.split('/'),
815
+ },
816
+ locale: normalizeLocale(config.locale),
817
+ });
818
+ return prev;
819
+ }, []);
820
+ });
821
+ });
822
+ }
@@ -74,6 +74,7 @@ export interface PageData {
74
74
  regions?: RegionTree;
75
75
  }
76
76
  export interface FragmentData {
77
+ id: string;
77
78
  fragment: {
78
79
  components: PageComponent[];
79
80
  };
@@ -88,4 +89,6 @@ export interface MetaData {
88
89
  catchAll: boolean;
89
90
  apiUrl: string;
90
91
  baseUrl: string;
92
+ locale: string;
93
+ defaultLocale: string;
91
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enonic/nextjs-adapter",
3
- "version": "1.0.2",
3
+ "version": "2.0.0-B1",
4
4
  "license": "Apache-2.0",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -13,22 +13,22 @@
13
13
  "lint:fix": "eslint --fix server && eslint --fix src"
14
14
  },
15
15
  "dependencies": {
16
- "html-react-parser": "^3.0.16",
17
- "lru-cache": "^9.1.1",
18
- "next": "^13.3.1",
19
- "react": "^18.2.0",
20
- "react-dom": "^18.2.0",
16
+ "html-react-parser": "^4.2.0",
17
+ "lru-cache": "^10.0.0",
18
+ "next": "13.4.12",
19
+ "react": "18.2.0",
20
+ "react-dom": "18.2.0",
21
21
  "unescape": "^1.0.1"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@enonic/eslint-config": "^1.1.0",
25
- "@types/node": "^18.16.0",
26
- "@types/react": "^18.0.38",
27
- "@types/react-dom": "^18.0.11",
28
- "@typescript-eslint/eslint-plugin": "^5.59.0",
29
- "@typescript-eslint/parser": "^5.59.0",
30
- "eslint": "^8.39.0",
31
- "eslint-plugin-react": "^7.32.2",
32
- "typescript": "^5.0.4"
25
+ "@types/node": "^20.4.4",
26
+ "@types/react": "18.2.15",
27
+ "@types/react-dom": "18.2.7",
28
+ "@typescript-eslint/eslint-plugin": "^5.61.0",
29
+ "@typescript-eslint/parser": "^5.62.0",
30
+ "eslint": "^8.45.0",
31
+ "eslint-plugin-react": "^7.33.0",
32
+ "typescript": "^5.1.6"
33
33
  }
34
34
  }
@@ -0,0 +1 @@
1
+ export declare const getShortcutQuery = "\n query($path:ID!){\n guillotine {\n get(key:$path) {\n ... on base_Shortcut {\n data {\n target {\n pageUrl(type: absolute)\n }\n }\n }\n }\n }\n }";
@@ -0,0 +1,14 @@
1
+ export const getShortcutQuery = `
2
+ query($path:ID!){
3
+ guillotine {
4
+ get(key:$path) {
5
+ ... on base_Shortcut {
6
+ data {
7
+ target {
8
+ pageUrl(type: absolute)
9
+ }
10
+ }
11
+ }
12
+ }
13
+ }
14
+ }`;