@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 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 const getUrl: (url: string, meta: MetaData, serverSide?: boolean) => string;
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, getLocaleProjectConfigs, RENDER_MODE } from './utils';
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
- const apiUrl = this.getApiUrl(meta);
14
- const strippedUrl = this.stripApiUrl(url, apiUrl);
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 = `/${strippedUrl}`;
19
- if (!this.isRelative(url) && meta.locale !== meta.defaultLocale) {
20
- // do not append locale to local assets (having relative urls)
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}/${strippedUrl}`;
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 = getLocaleProjectConfigs().default;
105
+ const defaultConfig = getProjectLocaleConfigById();
96
106
  UrlProcessor.setSiteKey(defaultConfig.site);
97
- export const getUrl = UrlProcessor.process.bind(UrlProcessor);
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, LocaleProjectConfig } from '../utils';
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 = (contentPath: string | string[], context?: Context) => Promise<FetchContentResult>;
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: LocaleProjectConfig, headers?: {}, method?: string) => Promise<any>;
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: LocaleProjectConfig, headers?: {}) => Promise<GuillotineResult>;
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 createResponse(props: FetchContentResult, context?: Context, isStatic?: true): GetStaticPropsResult<FetchContentResult>;
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: LocaleProjectConfig, query?: string, count?: number): Promise<ContentPathItem[]>;
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, getLocaleProjectConfig, getLocaleProjectConfigs, getRenderMode, getXpBaseUrl, IS_DEV_MODE, normalizeLocale, PAGE_TEMPLATE_CONTENTTYPE_NAME, PAGE_TEMPLATE_FOLDER, PURGE_CACHE_URL, RENDER_MODE, sanitizeGraphqlName, 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, 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 { getUrl, UrlProcessor } from '../UrlProcessor';
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, context, config);
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, selectedQuery, context, config) {
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 errorResponse(code = '500', message = 'Unknown error', requestType, renderMode, apiUrl, baseUrl, locale, defaultLocale, contentPath) {
512
- return {
513
- error: {
514
- code,
515
- message,
516
- },
517
- page: null,
518
- common: null,
519
- data: null,
520
- meta: {
521
- type: '',
522
- requestType: requestType,
523
- renderMode: renderMode,
524
- path: contentPath || '',
525
- canRender: false,
526
- catchAll: false,
527
- apiUrl,
528
- baseUrl,
529
- locale,
530
- defaultLocale,
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, context) => {
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 (contentPathOrArray, context) => __awaiter(void 0, void 0, void 0, function* () {
588
+ return (context) => __awaiter(void 0, void 0, void 0, function* () {
586
589
  var _a, _b;
587
- if (context.preview) {
588
- populateEnonicHeaders(context);
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 headers = {};
591
- addJsessionHeaders(headers, context);
592
- addLocaleHeaders(headers, context);
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 = getLocaleProjectConfig(context);
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 requestContentPath = getCleanContentPathArrayOrThrow400(contentPathOrArray);
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, headers);
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, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
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', requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
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.", requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
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`, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
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`, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
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, context, (_a = pageCmp === null || pageCmp === void 0 ? void 0 : pageCmp.page) === null || _a === void 0 ? void 0 : _a.config);
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(), context, (_b = pageCmp === null || pageCmp === void 0 ? void 0 : pageCmp.page) === null || _b === void 0 ? void 0 : _b.config);
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}`, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
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, headers);
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, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPath);
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, context.locale, context.defaultLocale, componentPath, page, components);
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, requestType, renderMode, contentApiUrl, xpBaseUrl, context.locale, context.defaultLocale, contentPathOrArray.toString());
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 createResponse(props, context, isStatic = true) {
751
- var _a, _b, _c, _d;
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
- return {
759
- notFound: true,
760
- };
784
+ console.warn(404, `Shortcuts are not available in ${RENDER_MODE.NEXT} render mode`);
785
+ notFound();
761
786
  }
762
- const destination = getUrl(pageUrl, meta, true);
763
- // console.debug('Redirecting to', destination);
764
- return {
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 notFound = (error && error.code === '404') || ((_d = context.res) === null || _d === void 0 ? void 0 : _d.statusCode) === 404 || canNotRender || catchAllInNextProdMode || undefined;
776
- // console.debug('Returning view ' + (notFound ? '(not found)' : ''));
777
- const result = {
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(getLocaleProjectConfigs()).map(config => fetchContentPathsForLocale(path, config, query, countPerLocale));
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({ locale: config.locale });
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
- params: {
821
- contentPath: contentPath.split('/'),
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
@@ -3,3 +3,5 @@ export * from './guillotine/fetchContent';
3
3
  export * from './guillotine/getMetaData';
4
4
  export * from './ComponentRegistry';
5
5
  export * from './UrlProcessor';
6
+ export * from './i18n/i18n';
7
+ export * from './i18n/LocaleContext';
package/index.js CHANGED
@@ -3,3 +3,5 @@ export * from './guillotine/fetchContent';
3
3
  export * from './guillotine/getMetaData';
4
4
  export * from './ComponentRegistry';
5
5
  export * from './UrlProcessor';
6
+ export * from './i18n/i18n';
7
+ export * from './i18n/LocaleContext';