@enonic/nextjs-adapter 2.0.0-B3 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.eslint-childrc.js +17 -0
  2. package/.eslintrc.js +15 -0
  3. package/.github/dependabot.yml +20 -0
  4. package/.github/pull-merger.yml +1 -0
  5. package/.github/workflows/release.yml +39 -0
  6. package/LICENSE +201 -0
  7. package/RELEASE.md +75 -0
  8. package/package.json +8 -7
  9. package/server/.eslintrc.js +11 -0
  10. package/server/ClearableFileCache.ts +410 -0
  11. package/server/cache.ts +50 -0
  12. package/server/index.ts +74 -0
  13. package/server/tsconfig.json +23 -0
  14. package/setversion.js +8 -0
  15. package/src/.eslintrc.js +11 -0
  16. package/src/ComponentRegistry.ts +189 -0
  17. package/{UrlProcessor.js → src/UrlProcessor.ts} +54 -36
  18. package/{baseMappings.js → src/baseMappings.ts} +13 -3
  19. package/{guillotine/fetchContent.js → src/guillotine/fetchContent.ts} +496 -243
  20. package/src/guillotine/getMetaData.ts +246 -0
  21. package/{index.d.ts → src/index.ts} +1 -1
  22. package/src/tsconfig.json +20 -0
  23. package/{utils.js → src/utils.ts} +160 -70
  24. package/{views/BaseComponent.jsx → src/views/BaseComponent.tsx} +73 -39
  25. package/{views/BaseContent.jsx → src/views/BaseContent.tsx} +24 -17
  26. package/src/views/BaseLayout.tsx +45 -0
  27. package/{views/BaseMacro.jsx → src/views/BaseMacro.tsx} +45 -29
  28. package/src/views/BasePage.tsx +56 -0
  29. package/src/views/BasePart.tsx +50 -0
  30. package/{views/Empty.jsx → src/views/Empty.tsx} +2 -0
  31. package/src/views/Fragment.tsx +45 -0
  32. package/src/views/MainView.tsx +21 -0
  33. package/src/views/Region.tsx +89 -0
  34. package/{views/RichTextView.jsx → src/views/RichTextView.tsx} +31 -10
  35. package/{views/SingleComponent.jsx → src/views/SingleComponent.tsx} +8 -2
  36. package/src/views/StaticContent.tsx +40 -0
  37. package/src/views/Text.tsx +15 -0
  38. package/src/views/macros/DefaultMacro.tsx +9 -0
  39. package/src/views/macros/DisableMacro.tsx +8 -0
  40. package/ComponentRegistry.d.ts +0 -64
  41. package/ComponentRegistry.js +0 -121
  42. package/UrlProcessor.d.ts +0 -25
  43. package/baseMappings.d.ts +0 -1
  44. package/guillotine/fetchContent.d.ts +0 -48
  45. package/guillotine/getMetaData.d.ts +0 -94
  46. package/guillotine/getMetaData.js +0 -128
  47. package/index.js +0 -5
  48. package/query/Shortcut.d.ts +0 -1
  49. package/server/ClearableFileCache.d.ts +0 -31
  50. package/server/ClearableFileCache.js +0 -472
  51. package/server/cache.d.ts +0 -2
  52. package/server/cache.js +0 -129
  53. package/server/index.d.ts +0 -5
  54. package/server/index.js +0 -132
  55. package/utils.d.ts +0 -114
  56. package/views/BaseComponent.d.ts +0 -25
  57. package/views/BaseContent.d.ts +0 -4
  58. package/views/BaseLayout.d.ts +0 -17
  59. package/views/BaseLayout.jsx +0 -24
  60. package/views/BaseMacro.d.ts +0 -16
  61. package/views/BasePage.d.ts +0 -19
  62. package/views/BasePage.jsx +0 -34
  63. package/views/BasePart.d.ts +0 -19
  64. package/views/BasePart.jsx +0 -24
  65. package/views/Empty.d.ts +0 -3
  66. package/views/Fragment.d.ts +0 -10
  67. package/views/Fragment.jsx +0 -30
  68. package/views/MainView.d.ts +0 -4
  69. package/views/MainView.jsx +0 -14
  70. package/views/Region.d.ts +0 -20
  71. package/views/Region.jsx +0 -52
  72. package/views/RichTextView.d.ts +0 -16
  73. package/views/SingleComponent.d.ts +0 -5
  74. package/views/StaticContent.d.ts +0 -15
  75. package/views/StaticContent.jsx +0 -36
  76. package/views/Text.d.ts +0 -9
  77. package/views/Text.jsx +0 -4
  78. package/views/macros/DefaultMacro.d.ts +0 -4
  79. package/views/macros/DefaultMacro.jsx +0 -3
  80. package/views/macros/DisableMacro.d.ts +0 -3
  81. package/views/macros/DisableMacro.jsx +0 -2
  82. /package/{query/Shortcut.js → src/query/Shortcut.ts} +0 -0
@@ -0,0 +1,189 @@
1
+ import {PageComponent} from './guillotine/getMetaData';
2
+ import {Context, XP_COMPONENT_TYPE} from './utils';
3
+
4
+
5
+ /**
6
+ * 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:
7
+ * - '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).
8
+ * - 'props' (used in fetchContent.ts) is a function for processing props after fetching them
9
+ * - 'view' (used in BasePage.tsx) is a React component: top-level content-type-specific rendering with the props first fetched from guillotine (and then optionally preprocessed with the function in 'props').
10
+ */
11
+ export interface ComponentDefinition {
12
+ catchAll?: boolean; // will be set automatically depending on the binding
13
+ query?: SelectedQueryMaybeVariablesFunc,
14
+ configQuery?: string,
15
+ processor?: DataProcessor,
16
+ view?: React.FunctionComponent<any>
17
+ }
18
+
19
+ type SelectorName = 'contentType' | 'page' | 'component' | 'part' | 'layout' | 'macro';
20
+
21
+ function toSelectorName(type: XP_COMPONENT_TYPE): SelectorName | undefined {
22
+ switch (type) {
23
+ case XP_COMPONENT_TYPE.PAGE:
24
+ return 'page';
25
+ case XP_COMPONENT_TYPE.LAYOUT:
26
+ return 'layout';
27
+ case XP_COMPONENT_TYPE.PART:
28
+ return 'part';
29
+ }
30
+ }
31
+
32
+ interface ComponentDictionary {
33
+ [type: string]: ComponentDefinition;
34
+ }
35
+
36
+ // NB! Always return null or empty object from processor for next is unable to serialize undefined
37
+ export type DataProcessor = (data: any, context?: Context, config?: any) => Promise<Record<string, any>>;
38
+
39
+ // TODO: also access as arguments: dataAsJson, pageAsJson, configAsJson from the first (meta) call here?
40
+ // Another option could be to let the component or page controller pass those values to NextJS by a header
41
+ export type VariablesGetter = (path: string, context?: Context, config?: any) => VariablesGetterResult;
42
+
43
+ export type QueryGetter = (path: string, context?: Context, config?: any) => string;
44
+
45
+ export type VariablesGetterResult = {
46
+ [variables: string]: any,
47
+ path: string,
48
+ };
49
+
50
+ export type SelectedQueryMaybeVariablesFunc = string | QueryGetter |
51
+ { query: string | QueryGetter, variables: VariablesGetter } |
52
+ [string | QueryGetter, VariablesGetter];
53
+
54
+ export const CATCH_ALL = '*';
55
+
56
+ export class ComponentRegistry {
57
+
58
+ private static contentTypes: ComponentDictionary = {};
59
+ private static pages: ComponentDictionary = {};
60
+ private static components: ComponentDictionary = {};
61
+ private static parts: ComponentDictionary = {};
62
+ private static layouts: ComponentDictionary = {};
63
+ private static macros: ComponentDictionary = {};
64
+ private static commonQuery: SelectedQueryMaybeVariablesFunc;
65
+
66
+ private static getSelector(name: SelectorName): ComponentDictionary {
67
+ switch (name) {
68
+ case 'contentType':
69
+ return this.contentTypes;
70
+ case 'page':
71
+ return this.pages;
72
+ case 'component':
73
+ return this.components;
74
+ case 'layout':
75
+ return this.layouts;
76
+ case 'part':
77
+ return this.parts;
78
+ case 'macro':
79
+ return this.macros;
80
+ }
81
+ }
82
+
83
+ private static getType(selectorName: SelectorName, typeName: string, useCatchAll = true): ComponentDefinition | undefined {
84
+ const selector = ComponentRegistry.getSelector(selectorName);
85
+ let type = selector[typeName];
86
+ if (type) {
87
+ type.catchAll = false;
88
+ } else if (!type && useCatchAll) {
89
+ type = selector[CATCH_ALL];
90
+ if (type) {
91
+ type.catchAll = true;
92
+ }
93
+ }
94
+ return type;
95
+ }
96
+
97
+ private static addType(selectorName: SelectorName, name: string, obj: ComponentDefinition): void {
98
+ const selector = ComponentRegistry.getSelector(selectorName);
99
+ const curr = selector[name];
100
+ selector[name] = curr ? Object.assign(curr, obj) : obj;
101
+ }
102
+
103
+ public static getByComponent(component: PageComponent): ComponentDefinition | undefined {
104
+ const selName = toSelectorName(component.type);
105
+ const cmpData = component[component.type];
106
+ const desc = cmpData && 'descriptor' in cmpData ? cmpData.descriptor : component.path;
107
+ return selName && desc ? this.getType(selName, desc) : undefined;
108
+ }
109
+
110
+ public static setCommonQuery(query: SelectedQueryMaybeVariablesFunc): void {
111
+ this.commonQuery = query;
112
+ }
113
+
114
+ public static getCommonQuery(): SelectedQueryMaybeVariablesFunc {
115
+ return this.commonQuery;
116
+ }
117
+
118
+ public static addMacro(name: string, obj: ComponentDefinition): void {
119
+ return ComponentRegistry.addType('macro', name, obj);
120
+ }
121
+
122
+ public static getMacro(name: string): ComponentDefinition | undefined {
123
+ return ComponentRegistry.getType('macro', name);
124
+ }
125
+
126
+ public static getMacros(): [string, ComponentDefinition][] {
127
+ return Object.entries(this.macros);
128
+ }
129
+
130
+ public static getParts(): [string, ComponentDefinition][] {
131
+ return Object.entries(this.parts);
132
+ }
133
+
134
+ public static getLayouts(): [string, ComponentDefinition][] {
135
+ return Object.entries(this.layouts);
136
+ }
137
+
138
+ public static getPages(): [string, ComponentDefinition][] {
139
+ return Object.entries(this.pages);
140
+ }
141
+
142
+ public static getContentTypes(): [string, ComponentDefinition][] {
143
+ return Object.entries(this.contentTypes);
144
+ }
145
+
146
+ public static getComponents(): [string, ComponentDefinition][] {
147
+ return Object.entries(this.components);
148
+ }
149
+
150
+ public static addContentType(name: string, obj: ComponentDefinition): void {
151
+ return ComponentRegistry.addType('contentType', name, obj);
152
+ }
153
+
154
+ public static getContentType(name: string): ComponentDefinition | undefined {
155
+ return ComponentRegistry.getType('contentType', name);
156
+ }
157
+
158
+ public static addPage(name: string, obj: ComponentDefinition): void {
159
+ return ComponentRegistry.addType('page', name, obj);
160
+ }
161
+
162
+ public static getPage(name: string): ComponentDefinition | undefined {
163
+ return ComponentRegistry.getType('page', name);
164
+ }
165
+
166
+ public static addPart(name: string, obj: ComponentDefinition): void {
167
+ return ComponentRegistry.addType('part', name, obj);
168
+ }
169
+
170
+ public static getPart(name: string): ComponentDefinition | undefined {
171
+ return ComponentRegistry.getType('part', name);
172
+ }
173
+
174
+ public static addLayout(name: string, obj: ComponentDefinition): void {
175
+ return ComponentRegistry.addType('layout', name, obj);
176
+ }
177
+
178
+ public static getLayout(name: string): ComponentDefinition | undefined {
179
+ return ComponentRegistry.getType('layout', name);
180
+ }
181
+
182
+ public static addComponent(name: string, obj: ComponentDefinition): void {
183
+ return ComponentRegistry.addType('component', name, obj);
184
+ }
185
+
186
+ public static getComponent(name: string): ComponentDefinition | undefined {
187
+ return ComponentRegistry.getType('component', name);
188
+ }
189
+ }
@@ -1,7 +1,25 @@
1
- import { commonChars, fixDoubleSlashes, getLocaleProjectConfigs, RENDER_MODE } from './utils';
2
- import { addBasePath } from 'next/dist/client/add-base-path';
1
+ import {commonChars, fixDoubleSlashes, getLocaleProjectConfigs, RENDER_MODE} from './utils';
2
+ import {ImageData, LinkData, MetaData} from './guillotine/getMetaData';
3
+ import {addBasePath} from 'next/dist/client/add-base-path';
4
+
3
5
  export class UrlProcessor {
4
- static process(url, meta, serverSide = false) {
6
+
7
+ public static IMG_TAG = 'img';
8
+ public static LINK_TAG = 'a';
9
+ public static MACRO_TAG = 'editor-macro';
10
+
11
+ public static IMG_ATTR = 'data-image-ref';
12
+ public static LINK_ATTR = 'data-link-ref';
13
+ public static MACRO_ATTR = 'data-macro-ref';
14
+
15
+ private static IMG_ATMT_REGEXP = /_\/image\/|_\/attachment\//;
16
+ private static endSlashPattern = /\/+$/;
17
+ private static startSlashPattern = /^\/+/;
18
+ private static localhostPattern = /localhost/;
19
+
20
+ private static siteKey: string;
21
+
22
+ public static process(url: string, meta: MetaData, serverSide = false): string {
5
23
  if (this.startsWithHash(url) || !meta || (this.isAttachmentUrl(url)) && meta.renderMode === RENDER_MODE.NEXT) {
6
24
  // do not process if:
7
25
  // - url starts with #
@@ -9,10 +27,11 @@ export class UrlProcessor {
9
27
  // - attachment urls in NEXT mode
10
28
  return url;
11
29
  }
12
- let result;
30
+
31
+ let result: string;
13
32
  const apiUrl = this.getApiUrl(meta);
14
33
  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 : '';
34
+ const baseUrl = meta?.baseUrl && meta?.baseUrl !== '/' ? meta.baseUrl : '';
16
35
  if (meta.renderMode === RENDER_MODE.NEXT) {
17
36
  // only add basePath and locale in next mode
18
37
  result = `/${strippedUrl}`;
@@ -24,24 +43,26 @@ export class UrlProcessor {
24
43
  // no need for baseurl and basepath on server
25
44
  result = addBasePath(`${baseUrl}${result}`);
26
45
  }
27
- }
28
- else {
46
+ } else {
29
47
  result = `${baseUrl}/${strippedUrl}`;
30
48
  }
49
+
31
50
  return fixDoubleSlashes(result);
32
51
  }
33
- static setSiteKey(key) {
52
+
53
+ public static setSiteKey(key: string): void {
34
54
  this.siteKey = key;
35
55
  }
36
- static isMediaLink(ref, linkData) {
37
- var _a;
38
- return ((_a = linkData.find(data => data.ref === ref)) === null || _a === void 0 ? void 0 : _a.media) !== null;
56
+
57
+ public static isMediaLink(ref: string, linkData: LinkData[]): boolean {
58
+ return linkData.find(data => data.ref === ref)?.media !== null;
39
59
  }
40
- static isContentImage(ref, linkData) {
41
- var _a;
42
- return ((_a = linkData.find(data => data.ref === ref)) === null || _a === void 0 ? void 0 : _a.image) !== null;
60
+
61
+ public static isContentImage(ref: string, linkData: ImageData[]): boolean {
62
+ return linkData.find(data => data.ref === ref)?.image !== null;
43
63
  }
44
- static processSrcSet(srcset, meta) {
64
+
65
+ public static processSrcSet(srcset: string, meta: MetaData): string {
45
66
  return srcset.split(/, */g).map(src => {
46
67
  const srcParts = src.trim().split(' ');
47
68
  switch (srcParts.length) {
@@ -55,43 +76,40 @@ export class UrlProcessor {
55
76
  }
56
77
  }).join(', ');
57
78
  }
58
- static stripApiUrl(url, apiUrl) {
79
+
80
+ private static stripApiUrl(url: string, apiUrl: string): string {
59
81
  // normalise localhost-127.0.0.1 if present in urls
60
82
  const normalUrl = url.replace(this.localhostPattern, '127.0.0.1');
61
83
  const normalApiUrl = apiUrl.replace(this.localhostPattern, '127.0.0.1');
62
- const common = commonChars(normalUrl, normalApiUrl);
84
+ const common = commonChars(normalUrl, normalApiUrl, '/');
63
85
  const remaining = common.length > 0 ? normalUrl.substring(common.length) : normalUrl;
64
86
  return (remaining.length > 0 && remaining.charAt(0) === '/') ? remaining.substring(1) : remaining;
65
87
  }
66
- static isAttachmentUrl(url) {
88
+
89
+ private static isAttachmentUrl(url: string): boolean {
67
90
  return this.IMG_ATMT_REGEXP.test(url);
68
91
  }
69
- static startsWithHash(url) {
70
- return (url === null || url === void 0 ? void 0 : url.charAt(0)) == '#';
92
+
93
+ private static startsWithHash(url: string): boolean {
94
+ return url?.charAt(0) == '#';
71
95
  }
72
- static isRelative(url) {
96
+
97
+ private static isRelative(url: string): boolean {
73
98
  return !/^(?:ht|f)tps?:\/\/[^ :\r\n\t]+/.test(url);
74
99
  }
75
- static getApiUrl(meta) {
76
- var _a, _b;
77
- let combinedUrl = ((_a = meta.apiUrl) === null || _a === void 0 ? void 0 : _a.replace(this.endSlashPattern, '')) || '';
78
- if ((_b = this.siteKey) === null || _b === void 0 ? void 0 : _b.length) {
100
+
101
+ private static getApiUrl(meta: MetaData) {
102
+ let combinedUrl = meta.apiUrl?.replace(this.endSlashPattern, '') || '';
103
+ if (this.siteKey?.length) {
79
104
  combinedUrl += '/' + this.siteKey.replace(this.startSlashPattern, '');
80
105
  }
81
106
  return combinedUrl;
82
107
  }
83
108
  }
84
- UrlProcessor.IMG_TAG = 'img';
85
- UrlProcessor.LINK_TAG = 'a';
86
- UrlProcessor.MACRO_TAG = 'editor-macro';
87
- UrlProcessor.IMG_ATTR = 'data-image-ref';
88
- UrlProcessor.LINK_ATTR = 'data-link-ref';
89
- UrlProcessor.MACRO_ATTR = 'data-macro-ref';
90
- UrlProcessor.IMG_ATMT_REGEXP = /_\/image\/|_\/attachment\//;
91
- UrlProcessor.endSlashPattern = /\/+$/;
92
- UrlProcessor.startSlashPattern = /^\/+/;
93
- UrlProcessor.localhostPattern = /localhost/;
109
+
94
110
  // Actual site is going to be set in fetchContent method, but set a default fallback here too
95
111
  const defaultConfig = getLocaleProjectConfigs().default;
96
112
  UrlProcessor.setSiteKey(defaultConfig.site);
97
- export const getUrl = UrlProcessor.process.bind(UrlProcessor);
113
+
114
+ export const getUrl: (url: string, meta: MetaData, serverSide?: boolean) => string = UrlProcessor.process.bind(UrlProcessor);
115
+
@@ -1,5 +1,5 @@
1
- import { ComponentRegistry } from './ComponentRegistry';
2
- import { FRAGMENT_CONTENTTYPE_NAME, XP_COMPONENT_TYPE } from './utils';
1
+ import {ComponentRegistry} from './ComponentRegistry';
2
+ import {FRAGMENT_CONTENTTYPE_NAME, XP_COMPONENT_TYPE} from './utils';
3
3
  import FragmentView from './views/Fragment';
4
4
  import BasePage from './views/BasePage';
5
5
  import BasePart from './views/BasePart';
@@ -7,30 +7,40 @@ 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
+ import {getShortcutQuery} from './query/Shortcut';
11
+
11
12
  // Base Content Types
13
+
12
14
  ComponentRegistry.addContentType(FRAGMENT_CONTENTTYPE_NAME, {
13
15
  view: FragmentView,
14
16
  });
17
+
15
18
  ComponentRegistry.addContentType('base:shortcut', {
16
19
  query: getShortcutQuery,
17
20
  });
21
+
18
22
  // Base Components
23
+
19
24
  ComponentRegistry.addComponent(XP_COMPONENT_TYPE.PAGE, {
20
25
  view: BasePage,
21
26
  });
27
+
22
28
  ComponentRegistry.addComponent(XP_COMPONENT_TYPE.PART, {
23
29
  view: BasePart,
24
30
  });
31
+
25
32
  ComponentRegistry.addComponent(XP_COMPONENT_TYPE.LAYOUT, {
26
33
  view: BaseLayout,
27
34
  });
35
+
28
36
  ComponentRegistry.addComponent(XP_COMPONENT_TYPE.FRAGMENT, {
29
37
  view: FragmentView,
30
38
  });
39
+
31
40
  ComponentRegistry.addComponent(XP_COMPONENT_TYPE.TEXT, {
32
41
  view: TextView,
33
42
  });
43
+
34
44
  // Macro mappings
35
45
  ComponentRegistry.addMacro('system:embed', {
36
46
  view: DefaultMacro,