@enonic/nextjs-adapter 2.0.0 → 3.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.
- package/UrlProcessor.d.ts +3 -2
- package/UrlProcessor.js +25 -10
- package/guillotine/fetchContent.d.ts +6 -8
- package/guillotine/fetchContent.js +98 -92
- package/i18n/LocaleContext.d.ts +13 -0
- package/i18n/LocaleContext.jsx +42 -0
- package/i18n/i18n.d.ts +13 -0
- package/i18n/i18n.js +56 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +15 -12
- package/utils.d.ts +27 -30
- package/utils.js +75 -61
- package/views/BaseComponent.d.ts +1 -1
- package/views/BaseComponent.jsx +19 -5
- package/views/RichTextView.jsx +4 -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/UrlProcessor.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ 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, serverSide?: boolean): string;
|
|
14
|
+
static process(url: string, meta: MetaData, serverSide?: boolean, isResource?: 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;
|
|
@@ -22,4 +22,5 @@ export declare class UrlProcessor {
|
|
|
22
22
|
private static isRelative;
|
|
23
23
|
private static getApiUrl;
|
|
24
24
|
}
|
|
25
|
-
export declare
|
|
25
|
+
export declare function getUrl(url: string, meta: MetaData): string;
|
|
26
|
+
export declare function getAsset(url: string, meta: MetaData): string;
|
package/UrlProcessor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { commonChars, fixDoubleSlashes,
|
|
1
|
+
import { commonChars, fixDoubleSlashes, getProjectLocaleConfigById, RENDER_MODE } from './utils';
|
|
2
2
|
import { addBasePath } from 'next/dist/client/add-base-path';
|
|
3
3
|
export class UrlProcessor {
|
|
4
|
-
static process(url, meta, serverSide = false) {
|
|
4
|
+
static process(url, meta, serverSide = false, isResource = false) {
|
|
5
5
|
if (this.startsWithHash(url) || !meta || (this.isAttachmentUrl(url)) && meta.renderMode === RENDER_MODE.NEXT) {
|
|
6
6
|
// do not process if:
|
|
7
7
|
// - url starts with #
|
|
@@ -9,15 +9,25 @@ export class UrlProcessor {
|
|
|
9
9
|
// - attachment urls in NEXT mode
|
|
10
10
|
return url;
|
|
11
11
|
}
|
|
12
|
+
let normalUrl;
|
|
12
13
|
let result;
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
if (this.isRelative(url)) {
|
|
15
|
+
normalUrl = url;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// url is absolute, try to make it relative by striping apiUrl
|
|
19
|
+
// NB: will fail if content api is not on the same domain as enonic xp
|
|
20
|
+
const apiUrl = this.getApiUrl(meta);
|
|
21
|
+
normalUrl = this.stripApiUrl(url, apiUrl);
|
|
22
|
+
}
|
|
15
23
|
const baseUrl = (meta === null || meta === void 0 ? void 0 : meta.baseUrl) && (meta === null || meta === void 0 ? void 0 : meta.baseUrl) !== '/' ? meta.baseUrl : '';
|
|
16
24
|
if (meta.renderMode === RENDER_MODE.NEXT) {
|
|
17
25
|
// only add basePath and locale in next mode
|
|
18
|
-
result = `/${
|
|
19
|
-
if (!
|
|
20
|
-
//
|
|
26
|
+
result = `/${normalUrl}`;
|
|
27
|
+
if (!isResource && meta.locale !== meta.defaultLocale) {
|
|
28
|
+
// append locale if it's not the default one
|
|
29
|
+
// to avoid additional middleware redirection
|
|
30
|
+
// NB: don't add locale to resource urls
|
|
21
31
|
result = `/${meta.locale}${result}`;
|
|
22
32
|
}
|
|
23
33
|
if (!serverSide) {
|
|
@@ -26,7 +36,7 @@ export class UrlProcessor {
|
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
else {
|
|
29
|
-
result = `${baseUrl}/${
|
|
39
|
+
result = `${baseUrl}/${normalUrl}`;
|
|
30
40
|
}
|
|
31
41
|
return fixDoubleSlashes(result);
|
|
32
42
|
}
|
|
@@ -92,6 +102,11 @@ UrlProcessor.endSlashPattern = /\/+$/;
|
|
|
92
102
|
UrlProcessor.startSlashPattern = /^\/+/;
|
|
93
103
|
UrlProcessor.localhostPattern = /localhost/;
|
|
94
104
|
// Actual site is going to be set in fetchContent method, but set a default fallback here too
|
|
95
|
-
const defaultConfig =
|
|
105
|
+
const defaultConfig = getProjectLocaleConfigById();
|
|
96
106
|
UrlProcessor.setSiteKey(defaultConfig.site);
|
|
97
|
-
export
|
|
107
|
+
export function getUrl(url, meta) {
|
|
108
|
+
return UrlProcessor.process(url, meta, false, false);
|
|
109
|
+
}
|
|
110
|
+
export function getAsset(url, meta) {
|
|
111
|
+
return UrlProcessor.process(url, meta, false, true);
|
|
112
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MetaData, PageComponent } from './getMetaData';
|
|
2
|
-
import { ContentPathItem, Context,
|
|
3
|
-
import { GetServerSidePropsResult, GetStaticPropsResult } from 'next';
|
|
2
|
+
import { ContentPathItem, Context, ProjectLocaleConfig } from '../utils';
|
|
4
3
|
type Result = {
|
|
5
4
|
error?: {
|
|
6
5
|
code: string;
|
|
@@ -21,7 +20,7 @@ export type FetchContentResult = Result & {
|
|
|
21
20
|
* @param contentPath string or string array: pre-split or slash-delimited _path to a content available on the API
|
|
22
21
|
* @returns FetchContentResult object: {data?: T, error?: {code, message}}
|
|
23
22
|
*/
|
|
24
|
-
export type ContentFetcher = (
|
|
23
|
+
export type ContentFetcher = (context: Context) => Promise<FetchContentResult>;
|
|
25
24
|
export type ContentApiBaseBody = {
|
|
26
25
|
query?: string;
|
|
27
26
|
variables?: {
|
|
@@ -30,9 +29,9 @@ export type ContentApiBaseBody = {
|
|
|
30
29
|
};
|
|
31
30
|
};
|
|
32
31
|
/** Generic fetch */
|
|
33
|
-
export declare const fetchFromApi: (apiUrl: string, body: ContentApiBaseBody, projectConfig:
|
|
32
|
+
export declare const fetchFromApi: (apiUrl: string, body: ContentApiBaseBody, projectConfig: ProjectLocaleConfig, headers?: {}, method?: string) => Promise<any>;
|
|
34
33
|
/** Guillotine-specialized fetch, using the generic fetch above */
|
|
35
|
-
export declare const fetchGuillotine: (contentApiUrl: string, body: ContentApiBaseBody, projectConfig:
|
|
34
|
+
export declare const fetchGuillotine: (contentApiUrl: string, body: ContentApiBaseBody, projectConfig: ProjectLocaleConfig, headers?: {}) => Promise<GuillotineResult>;
|
|
36
35
|
/**
|
|
37
36
|
* Default fetchContent function, built with params from imports.
|
|
38
37
|
* It runs custom content-type-specific guillotine calls against an XP guillotine endpoint, returns content data, error and some meta data
|
|
@@ -42,8 +41,7 @@ export declare const fetchGuillotine: (contentApiUrl: string, body: ContentApiBa
|
|
|
42
41
|
* @returns FetchContentResult object: {data?: T, error?: {code, message}}
|
|
43
42
|
*/
|
|
44
43
|
export declare const fetchContent: ContentFetcher;
|
|
45
|
-
export declare function
|
|
46
|
-
export declare function createResponse(props: FetchContentResult, context?: Context, isStatic?: false): GetServerSidePropsResult<FetchContentResult>;
|
|
44
|
+
export declare function validateData(props: FetchContentResult): void;
|
|
47
45
|
export declare function fetchContentPathsForAllLocales(path: string, query?: string, countPerLocale?: number): Promise<ContentPathItem[]>;
|
|
48
|
-
export declare function fetchContentPathsForLocale(path: string, config:
|
|
46
|
+
export declare function fetchContentPathsForLocale(path: string, config: ProjectLocaleConfig, query?: string, count?: number): Promise<ContentPathItem[]>;
|
|
49
47
|
export {};
|
|
@@ -8,9 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { getMetaQuery, pageFragmentQuery } from './getMetaData';
|
|
11
|
-
import adapterConstants, { addJsessionHeaders, addLocaleHeaders, APP_NAME, APP_NAME_DASHED, FRAGMENT_CONTENTTYPE_NAME, FRAGMENT_DEFAULT_REGION_NAME, GET_STATIC_PATHS_QUERY, getContentApiUrl,
|
|
11
|
+
import adapterConstants, { addJsessionHeaders, addLocaleHeaders, APP_NAME, APP_NAME_DASHED, FRAGMENT_CONTENTTYPE_NAME, FRAGMENT_DEFAULT_REGION_NAME, GET_STATIC_PATHS_QUERY, getContentApiUrl, getProjectLocaleConfig, getProjectLocaleConfigs, getRenderMode, getRequestLocaleInfo, getXpBaseUrl, IS_DEV_MODE, 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 {
|
|
13
|
+
import { UrlProcessor } from '../UrlProcessor';
|
|
14
|
+
import { notFound, redirect, RedirectType } from 'next/navigation';
|
|
15
|
+
import { headers } from 'next/headers';
|
|
14
16
|
const NO_PROPS_PROCESSOR = (props) => __awaiter(void 0, void 0, void 0, function* () { var _a; return (_a = yield props) !== null && _a !== void 0 ? _a : {}; });
|
|
15
17
|
const ALIAS_PREFIX = 'request';
|
|
16
18
|
const GUILLOTINE_QUERY_REGEXP = /^\s*query\s*(?:\((.*)*\))?\s*{\s*guillotine\s*{((?:.|\s)+)}\s*}\s*$/;
|
|
@@ -85,8 +87,7 @@ export const fetchGuillotine = (contentApiUrl, body, projectConfig, headers) =>
|
|
|
85
87
|
errors = [errors];
|
|
86
88
|
}
|
|
87
89
|
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)}`);
|
|
90
|
+
console.error(`headers: ${JSON.stringify(headers, null, 2)} \nvariables: ${JSON.stringify(body.variables, null, 2)}`);
|
|
90
91
|
errors.forEach(error => {
|
|
91
92
|
console.error(error);
|
|
92
93
|
});
|
|
@@ -381,7 +382,7 @@ function collectComponentDescriptors(components, componentRegistry, xpContentPat
|
|
|
381
382
|
if (cmpDef) {
|
|
382
383
|
// const partPath = `${xpContentPath}/_component${cmp.path}`;
|
|
383
384
|
const config = getComponentConfig(cmp);
|
|
384
|
-
const queryAndVariables = getQueryAndVariables(cmp.type, xpContentPath, cmpDef.query,
|
|
385
|
+
const queryAndVariables = getQueryAndVariables(cmp.type, xpContentPath, context, cmpDef.query, config);
|
|
385
386
|
if (queryAndVariables) {
|
|
386
387
|
descriptors.push({
|
|
387
388
|
component: cmp,
|
|
@@ -429,7 +430,7 @@ function processComponentConfig(myAppName, myAppNameDashed, cmp) {
|
|
|
429
430
|
}
|
|
430
431
|
}
|
|
431
432
|
}
|
|
432
|
-
function getQueryAndVariables(type, path,
|
|
433
|
+
function getQueryAndVariables(type, path, context, selectedQuery, config) {
|
|
433
434
|
let query, getVariables;
|
|
434
435
|
if (typeof selectedQuery === 'string' || typeof selectedQuery === 'function') {
|
|
435
436
|
query = selectedQuery;
|
|
@@ -508,27 +509,29 @@ function createMetaData(contentType, contentPath, requestType, renderMode, apiUr
|
|
|
508
509
|
}
|
|
509
510
|
return meta;
|
|
510
511
|
}
|
|
511
|
-
function
|
|
512
|
-
return {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
512
|
+
function buildErrorResponse(requestType, renderMode, apiUrl, baseUrl, locale, defaultLocale, contentPath) {
|
|
513
|
+
return function (code = '500', message = 'Unknown error') {
|
|
514
|
+
return {
|
|
515
|
+
error: {
|
|
516
|
+
code,
|
|
517
|
+
message,
|
|
518
|
+
},
|
|
519
|
+
page: null,
|
|
520
|
+
common: null,
|
|
521
|
+
data: null,
|
|
522
|
+
meta: {
|
|
523
|
+
type: '',
|
|
524
|
+
requestType: requestType,
|
|
525
|
+
renderMode: renderMode,
|
|
526
|
+
path: contentPath || '',
|
|
527
|
+
canRender: false,
|
|
528
|
+
catchAll: false,
|
|
529
|
+
apiUrl,
|
|
530
|
+
baseUrl,
|
|
531
|
+
locale,
|
|
532
|
+
defaultLocale,
|
|
533
|
+
},
|
|
534
|
+
};
|
|
532
535
|
};
|
|
533
536
|
}
|
|
534
537
|
function restrictComponentsToPath(contentType, components, componentPath) {
|
|
@@ -564,7 +567,7 @@ function restrictComponentsToPath(contentType, components, componentPath) {
|
|
|
564
567
|
return result;
|
|
565
568
|
}
|
|
566
569
|
const COMPONENT_PATH_KEY = /\/?_\/component/; // first slash is optional for root components
|
|
567
|
-
const getContentAndComponentPaths = (requestPath
|
|
570
|
+
const getContentAndComponentPaths = (requestPath) => {
|
|
568
571
|
let contentPath, componentPath;
|
|
569
572
|
if (COMPONENT_PATH_KEY.test(requestPath)) {
|
|
570
573
|
[contentPath, componentPath] = requestPath.split(COMPONENT_PATH_KEY);
|
|
@@ -582,56 +585,61 @@ const getContentAndComponentPaths = (requestPath, context) => {
|
|
|
582
585
|
*/
|
|
583
586
|
const buildContentFetcher = (config) => {
|
|
584
587
|
const { APP_NAME, APP_NAME_DASHED, componentRegistry, } = config;
|
|
585
|
-
return (
|
|
588
|
+
return (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
586
589
|
var _a, _b;
|
|
587
|
-
|
|
588
|
-
|
|
590
|
+
const { locale, locales, defaultLocale } = getRequestLocaleInfo(context);
|
|
591
|
+
// ideally we only want to set headers in draft mode,
|
|
592
|
+
// but draft mode token is not passed in non-static mode
|
|
593
|
+
// so try to add them whenever they are absent
|
|
594
|
+
if (!context.headers) {
|
|
595
|
+
const allHeaders = headers();
|
|
596
|
+
context.headers = new Headers();
|
|
597
|
+
allHeaders.forEach((value, key) => {
|
|
598
|
+
context.headers.set(key, value);
|
|
599
|
+
});
|
|
589
600
|
}
|
|
590
|
-
const
|
|
591
|
-
addJsessionHeaders(
|
|
592
|
-
addLocaleHeaders(
|
|
601
|
+
const outHeaders = {};
|
|
602
|
+
addJsessionHeaders(outHeaders, context);
|
|
603
|
+
addLocaleHeaders(outHeaders, locale, locales, defaultLocale);
|
|
593
604
|
const xpBaseUrl = getXpBaseUrl(context);
|
|
594
605
|
const contentApiUrl = getContentApiUrl(context);
|
|
595
|
-
const projectConfig =
|
|
606
|
+
const projectConfig = getProjectLocaleConfig(context);
|
|
596
607
|
const renderMode = getRenderMode(context);
|
|
597
608
|
let requestType = XP_REQUEST_TYPE.TYPE;
|
|
609
|
+
const requestContentPath = getCleanContentPathArrayOrThrow400(context.contentPath);
|
|
610
|
+
const errorResponse = buildErrorResponse(requestType, renderMode, contentApiUrl, xpBaseUrl, locale, defaultLocale, requestContentPath);
|
|
598
611
|
UrlProcessor.setSiteKey(projectConfig.site);
|
|
599
612
|
try {
|
|
600
|
-
const
|
|
601
|
-
if ('/' + requestContentPath === PURGE_CACHE_URL) {
|
|
602
|
-
// return 404 for /_/cache/enonic/purge because it should've been handled by standalone endpoint
|
|
603
|
-
return errorResponse('404', 'Not found', requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, requestContentPath);
|
|
604
|
-
}
|
|
605
|
-
const [siteRelativeContentPath, componentPath] = getContentAndComponentPaths(requestContentPath, context);
|
|
613
|
+
const [siteRelativeContentPath, componentPath] = getContentAndComponentPaths(requestContentPath);
|
|
606
614
|
if (componentPath) {
|
|
607
615
|
// set component request type because url contains component path
|
|
608
616
|
requestType = XP_REQUEST_TYPE.COMPONENT;
|
|
609
617
|
}
|
|
610
618
|
// ///////////// FIRST GUILLOTINE CALL FOR METADATA /////////////////
|
|
611
|
-
const metaResult = yield fetchMetaData(contentApiUrl, '${site}/' + siteRelativeContentPath, projectConfig,
|
|
619
|
+
const metaResult = yield fetchMetaData(contentApiUrl, '${site}/' + siteRelativeContentPath, projectConfig, outHeaders);
|
|
612
620
|
// ///////////////////////////////////////////////////////////////////////
|
|
613
621
|
const { _path, type } = metaResult.meta || {};
|
|
614
622
|
const contentPath = _path || siteRelativeContentPath;
|
|
615
623
|
if (metaResult.error) {
|
|
616
624
|
console.error(metaResult.error);
|
|
617
|
-
return errorResponse(metaResult.error.code, metaResult.error.message
|
|
625
|
+
return errorResponse(metaResult.error.code, metaResult.error.message);
|
|
618
626
|
}
|
|
619
627
|
if (!metaResult.meta) {
|
|
620
|
-
return errorResponse('404', 'No meta data found for content, most likely content does not exist'
|
|
628
|
+
return errorResponse('404', 'No meta data found for content, most likely content does not exist');
|
|
621
629
|
}
|
|
622
630
|
else if (!type) {
|
|
623
|
-
return errorResponse('500', "Server responded with incomplete meta data: missing content 'type' attribute."
|
|
631
|
+
return errorResponse('500', "Server responded with incomplete meta data: missing content 'type' attribute.");
|
|
624
632
|
}
|
|
625
633
|
else if (renderMode === RENDER_MODE.NEXT && !IS_DEV_MODE &&
|
|
626
634
|
(type === FRAGMENT_CONTENTTYPE_NAME ||
|
|
627
635
|
type === PAGE_TEMPLATE_CONTENTTYPE_NAME ||
|
|
628
636
|
type === PAGE_TEMPLATE_FOLDER)) {
|
|
629
|
-
return errorResponse('404', `Content type [${type}] is not accessible in ${renderMode} mode
|
|
637
|
+
return errorResponse('404', `Content type [${type}] is not accessible in ${renderMode} mode`);
|
|
630
638
|
}
|
|
631
639
|
const components = restrictComponentsToPath(type, metaResult.meta.components, componentPath);
|
|
632
640
|
if (componentPath && !components.length) {
|
|
633
641
|
// component was not found
|
|
634
|
-
return errorResponse('404', `Component ${componentPath} was not found
|
|
642
|
+
return errorResponse('404', `Component ${componentPath} was not found`);
|
|
635
643
|
}
|
|
636
644
|
if (requestType !== XP_REQUEST_TYPE.COMPONENT && components.length > 0) {
|
|
637
645
|
requestType = XP_REQUEST_TYPE.PAGE;
|
|
@@ -644,14 +652,14 @@ const buildContentFetcher = (config) => {
|
|
|
644
652
|
if (pageCmp) {
|
|
645
653
|
processComponentConfig(APP_NAME, APP_NAME_DASHED, pageCmp);
|
|
646
654
|
}
|
|
647
|
-
const contentQueryAndVars = getQueryAndVariables(type, contentPath, contentTypeDef === null || contentTypeDef === void 0 ? void 0 : contentTypeDef.query,
|
|
655
|
+
const contentQueryAndVars = getQueryAndVariables(type, contentPath, context, contentTypeDef === null || contentTypeDef === void 0 ? void 0 : contentTypeDef.query, (_a = pageCmp === null || pageCmp === void 0 ? void 0 : pageCmp.page) === null || _a === void 0 ? void 0 : _a.config);
|
|
648
656
|
if (contentQueryAndVars) {
|
|
649
657
|
allDescriptors.push({
|
|
650
658
|
type: contentTypeDef,
|
|
651
659
|
queryAndVariables: contentQueryAndVars,
|
|
652
660
|
});
|
|
653
661
|
}
|
|
654
|
-
const commonQueryAndVars = getQueryAndVariables(type, contentPath, componentRegistry.getCommonQuery(),
|
|
662
|
+
const commonQueryAndVars = getQueryAndVariables(type, contentPath, context, componentRegistry.getCommonQuery(), (_b = pageCmp === null || pageCmp === void 0 ? void 0 : pageCmp.page) === null || _b === void 0 ? void 0 : _b.config);
|
|
655
663
|
if (commonQueryAndVars) {
|
|
656
664
|
allDescriptors.push({
|
|
657
665
|
type: contentTypeDef,
|
|
@@ -670,14 +678,14 @@ const buildContentFetcher = (config) => {
|
|
|
670
678
|
}
|
|
671
679
|
const { query, variables } = combineMultipleQueries(allDescriptors);
|
|
672
680
|
if (!query.trim()) {
|
|
673
|
-
return errorResponse('400', `Missing or empty query override for content type ${type}
|
|
681
|
+
return errorResponse('400', `Missing or empty query override for content type ${type}`);
|
|
674
682
|
}
|
|
675
683
|
// /////////////// SECOND GUILLOTINE CALL FOR DATA //////////////////////
|
|
676
|
-
const contentResults = yield fetchContentData(contentApiUrl, contentPath, projectConfig, query, variables,
|
|
684
|
+
const contentResults = yield fetchContentData(contentApiUrl, contentPath, projectConfig, query, variables, outHeaders);
|
|
677
685
|
// ///////////////////////////////////////////////////////////////////////////
|
|
678
686
|
if (contentResults.error) {
|
|
679
687
|
console.error(contentResults.error);
|
|
680
|
-
return errorResponse(contentResults.error.code, contentResults.error.message
|
|
688
|
+
return errorResponse(contentResults.error.code, contentResults.error.message);
|
|
681
689
|
}
|
|
682
690
|
// Apply processors to every component
|
|
683
691
|
const datas = yield applyProcessors(allDescriptors, contentResults, context);
|
|
@@ -713,7 +721,7 @@ const buildContentFetcher = (config) => {
|
|
|
713
721
|
}
|
|
714
722
|
}
|
|
715
723
|
const page = createPageData(type, components);
|
|
716
|
-
const meta = createMetaData(type, siteRelativeContentPath, requestType, renderMode, contentApiUrl, xpBaseUrl,
|
|
724
|
+
const meta = createMetaData(type, siteRelativeContentPath, requestType, renderMode, contentApiUrl, xpBaseUrl, locale, defaultLocale, componentPath, page, components);
|
|
717
725
|
return {
|
|
718
726
|
data: contentData,
|
|
719
727
|
common,
|
|
@@ -733,7 +741,7 @@ const buildContentFetcher = (config) => {
|
|
|
733
741
|
message: e.message,
|
|
734
742
|
};
|
|
735
743
|
}
|
|
736
|
-
return errorResponse(error.code, error.message
|
|
744
|
+
return errorResponse(error.code, error.message);
|
|
737
745
|
}
|
|
738
746
|
});
|
|
739
747
|
};
|
|
@@ -747,55 +755,52 @@ const buildContentFetcher = (config) => {
|
|
|
747
755
|
* @returns FetchContentResult object: {data?: T, error?: {code, message}}
|
|
748
756
|
*/
|
|
749
757
|
export const fetchContent = buildContentFetcher(Object.assign(Object.assign({}, adapterConstants), { componentRegistry: ComponentRegistry }));
|
|
750
|
-
export function
|
|
751
|
-
|
|
758
|
+
export function validateData(props) {
|
|
759
|
+
validateNotFound(props);
|
|
760
|
+
validateShortcut(props);
|
|
761
|
+
}
|
|
762
|
+
function validateNotFound(props) {
|
|
763
|
+
const { error } = props;
|
|
764
|
+
if (error) {
|
|
765
|
+
switch (error.code) {
|
|
766
|
+
case '404':
|
|
767
|
+
console.warn(error.code, error.message);
|
|
768
|
+
notFound();
|
|
769
|
+
break;
|
|
770
|
+
default:
|
|
771
|
+
console.error(error.code, error.message);
|
|
772
|
+
throw new Error(error.message);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function validateShortcut(props) {
|
|
777
|
+
var _a, _b, _c;
|
|
752
778
|
const { data, meta, error } = props;
|
|
753
779
|
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;
|
|
754
780
|
if (meta.type === 'base:shortcut' && pageUrl) {
|
|
755
781
|
if (meta.renderMode !== RENDER_MODE.NEXT) {
|
|
756
782
|
// console.debug(`Returning 404 for shortcut in ${RENDER_MODE.NEXT} mode`);
|
|
757
783
|
// do not show shortcut targets in preview/edit mode
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
};
|
|
784
|
+
console.warn(404, `Shortcuts are not available in ${RENDER_MODE.NEXT} render mode`);
|
|
785
|
+
notFound();
|
|
761
786
|
}
|
|
762
|
-
const destination =
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
redirect: {
|
|
766
|
-
statusCode: 307,
|
|
767
|
-
destination,
|
|
768
|
-
},
|
|
769
|
-
};
|
|
787
|
+
const destination = UrlProcessor.process(pageUrl, meta, true);
|
|
788
|
+
console.debug(`Redirecting shortcut content [${meta.path}] to`, destination);
|
|
789
|
+
redirect(destination, RedirectType.replace);
|
|
770
790
|
}
|
|
771
791
|
// we can not set 418 for static paths,
|
|
772
792
|
// but we can show 404 instead to be handled in CS
|
|
773
793
|
const canNotRender = meta && !meta.canRender && meta.renderMode !== RENDER_MODE.EDIT;
|
|
774
794
|
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);
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
notFound
|
|
779
|
-
props,
|
|
780
|
-
};
|
|
781
|
-
if (isStatic) {
|
|
782
|
-
result.revalidate = 3600; // In seconds, meaning every hour
|
|
795
|
+
const isNotFound = (error && error.code === '404') || canNotRender || catchAllInNextProdMode;
|
|
796
|
+
if (isNotFound) {
|
|
797
|
+
console.warn(404, `Can not render content at [${meta.path}]`);
|
|
798
|
+
notFound();
|
|
783
799
|
}
|
|
784
|
-
return result;
|
|
785
|
-
}
|
|
786
|
-
function populateEnonicHeaders(context) {
|
|
787
|
-
const pd = context.previewData;
|
|
788
|
-
if (!pd) {
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
const req = context.req || {};
|
|
792
|
-
req.headers = Object.assign(req.headers || {}, pd.headers);
|
|
793
|
-
context.params = Object.assign(context.params || {}, pd.params);
|
|
794
|
-
context.req = req;
|
|
795
800
|
}
|
|
796
801
|
export function fetchContentPathsForAllLocales(path, query = GET_STATIC_PATHS_QUERY, countPerLocale = 999) {
|
|
797
802
|
return __awaiter(this, void 0, void 0, function* () {
|
|
798
|
-
const promises = Object.values(
|
|
803
|
+
const promises = Object.values(getProjectLocaleConfigs()).map(config => fetchContentPathsForLocale(path, config, query, countPerLocale));
|
|
799
804
|
return Promise.all(promises).then(results => {
|
|
800
805
|
return results.reduce((all, localePaths) => all.concat(localePaths), []);
|
|
801
806
|
});
|
|
@@ -803,7 +808,10 @@ export function fetchContentPathsForAllLocales(path, query = GET_STATIC_PATHS_QU
|
|
|
803
808
|
}
|
|
804
809
|
export function fetchContentPathsForLocale(path, config, query = GET_STATIC_PATHS_QUERY, count = 999) {
|
|
805
810
|
return __awaiter(this, void 0, void 0, function* () {
|
|
806
|
-
const contentApiUrl = getContentApiUrl({
|
|
811
|
+
const contentApiUrl = getContentApiUrl({
|
|
812
|
+
contentPath: path,
|
|
813
|
+
locale: config.locale,
|
|
814
|
+
});
|
|
807
815
|
const body = {
|
|
808
816
|
query,
|
|
809
817
|
variables: {
|
|
@@ -817,10 +825,8 @@ export function fetchContentPathsForLocale(path, config, query = GET_STATIC_PATH
|
|
|
817
825
|
const regexp = new RegExp(`/${(_a = child.site) === null || _a === void 0 ? void 0 : _a._name}/?`);
|
|
818
826
|
const contentPath = child._path.replace(regexp, '');
|
|
819
827
|
prev.push({
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
},
|
|
823
|
-
locale: normalizeLocale(config.locale),
|
|
828
|
+
contentPath: contentPath.split('/'),
|
|
829
|
+
locale: config.locale,
|
|
824
830
|
});
|
|
825
831
|
return prev;
|
|
826
832
|
}, []);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Dict } from './i18n';
|
|
3
|
+
export type LocaleContextType = {
|
|
4
|
+
setLocale: (locale: string) => void;
|
|
5
|
+
locale: string;
|
|
6
|
+
dictionary: Dict;
|
|
7
|
+
localize: (key: string, ...args: any[]) => string;
|
|
8
|
+
};
|
|
9
|
+
export declare const useLocaleContext: () => LocaleContextType;
|
|
10
|
+
export declare const LocaleContextProvider: ({ children, locale }: {
|
|
11
|
+
children: any;
|
|
12
|
+
locale?: string;
|
|
13
|
+
}) => React.JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
import React, { createContext, useContext, useState } from 'react';
|
|
12
|
+
import { getPhrase, loadPhrases } from './i18n';
|
|
13
|
+
const LocaleContext = createContext({
|
|
14
|
+
setLocale: (locale) => {
|
|
15
|
+
},
|
|
16
|
+
locale: '',
|
|
17
|
+
dictionary: {},
|
|
18
|
+
localize: (key, ...args) => key,
|
|
19
|
+
});
|
|
20
|
+
export const useLocaleContext = () => useContext(LocaleContext);
|
|
21
|
+
export const LocaleContextProvider = ({ children, locale }) => {
|
|
22
|
+
const [currentLocale, setLocaleState] = useState(locale);
|
|
23
|
+
const [dictionary, setPhrasesState] = useState({});
|
|
24
|
+
const setLocale = (locale) => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
+
setLocaleState(locale);
|
|
26
|
+
return loadPhrases(locale).then((phrases) => {
|
|
27
|
+
setPhrasesState(phrases);
|
|
28
|
+
console.info(`Loaded client-side phrases for locale "${locale}"`);
|
|
29
|
+
return phrases;
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
if (locale && currentLocale !== locale) {
|
|
33
|
+
// async/await is not supported in client components yet
|
|
34
|
+
void setLocale(locale);
|
|
35
|
+
}
|
|
36
|
+
const localize = (key, ...args) => getPhrase(currentLocale, dictionary, key, ...args);
|
|
37
|
+
return (
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
39
|
+
<LocaleContext.Provider value={{ setLocale, locale: currentLocale, dictionary, localize }}>
|
|
40
|
+
{children}
|
|
41
|
+
</LocaleContext.Provider>);
|
|
42
|
+
};
|
package/i18n/i18n.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type Dict = {
|
|
2
|
+
[key: string]: string;
|
|
3
|
+
};
|
|
4
|
+
export declare class I18n {
|
|
5
|
+
private static locale;
|
|
6
|
+
private static dictionary;
|
|
7
|
+
static setLocale(locale: string): Promise<Dict>;
|
|
8
|
+
static localize(key: string, ...args: any[]): string;
|
|
9
|
+
static getLocale(): string;
|
|
10
|
+
static getDictionary(): Dict;
|
|
11
|
+
}
|
|
12
|
+
export declare const loadPhrases: (locale: string) => Promise<Dict>;
|
|
13
|
+
export declare const getPhrase: (locale: string, dict: Dict, key: string, ...args: any[]) => string;
|
package/i18n/i18n.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export class I18n {
|
|
11
|
+
static setLocale(locale) {
|
|
12
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
13
|
+
this.locale = locale;
|
|
14
|
+
return loadPhrases(locale).then((phrases) => {
|
|
15
|
+
this.dictionary = phrases;
|
|
16
|
+
console.info(`Loaded server-side phrases for locale "${locale}"`);
|
|
17
|
+
return phrases;
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
static localize(key, ...args) {
|
|
22
|
+
return getPhrase(this.locale, this.dictionary, key, ...args);
|
|
23
|
+
}
|
|
24
|
+
static getLocale() {
|
|
25
|
+
return this.locale;
|
|
26
|
+
}
|
|
27
|
+
static getDictionary() {
|
|
28
|
+
return this.dictionary;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
I18n.locale = '';
|
|
32
|
+
I18n.dictionary = {};
|
|
33
|
+
export const loadPhrases = (locale) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
return import(`@phrases/${locale}.json`)
|
|
35
|
+
.then((module) => module.default)
|
|
36
|
+
.catch((e) => {
|
|
37
|
+
console.error(`Failed to load phrases for locale "${locale}"`, e);
|
|
38
|
+
return {};
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
export const getPhrase = (locale, dict, key, ...args) => {
|
|
42
|
+
const template = dict[key];
|
|
43
|
+
if (!template) {
|
|
44
|
+
console.warn(`Missing localization phrase for locale "${locale}": <${key}>`);
|
|
45
|
+
return `<${key}>`;
|
|
46
|
+
}
|
|
47
|
+
if (args === null || args === void 0 ? void 0 : args.length) {
|
|
48
|
+
let index = -1;
|
|
49
|
+
return template.replace(/{(\d+)?}/g, (match, digit) => {
|
|
50
|
+
var _a;
|
|
51
|
+
index++;
|
|
52
|
+
return (_a = args[digit || index]) !== null && _a !== void 0 ? _a : match;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return template;
|
|
56
|
+
};
|
package/index.d.ts
CHANGED