@exdst-sitecore-content-sdk/astro 0.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 (87) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +3 -0
  3. package/package.json +101 -0
  4. package/src/client/index.ts +12 -0
  5. package/src/client/sitecore-astro-client.test.ts +271 -0
  6. package/src/client/sitecore-astro-client.ts +137 -0
  7. package/src/components/AstroImage.astro +114 -0
  8. package/src/components/Date.astro +76 -0
  9. package/src/components/DefaultEmptyFieldEditingComponentImage.astro +24 -0
  10. package/src/components/DefaultEmptyFieldEditingComponentText.astro +12 -0
  11. package/src/components/EditingScripts.astro +49 -0
  12. package/src/components/EmptyRendering.astro +3 -0
  13. package/src/components/ErrorBoundary.astro +77 -0
  14. package/src/components/FieldMetadata.astro +30 -0
  15. package/src/components/File.astro +46 -0
  16. package/src/components/HiddenRendering.astro +22 -0
  17. package/src/components/Image.astro +155 -0
  18. package/src/components/Link.astro +105 -0
  19. package/src/components/MissingComponent.astro +39 -0
  20. package/src/components/Placeholder/EmptyPlaceholder.astro +9 -0
  21. package/src/components/Placeholder/Placeholder.astro +100 -0
  22. package/src/components/Placeholder/PlaceholderMetadata.astro +102 -0
  23. package/src/components/Placeholder/PlaceholderUtils.astro +153 -0
  24. package/src/components/Placeholder/index.ts +5 -0
  25. package/src/components/Placeholder/models.ts +82 -0
  26. package/src/components/Placeholder/placeholder-utils.test.ts +162 -0
  27. package/src/components/Placeholder/placeholder-utils.ts +80 -0
  28. package/src/components/RenderWrapper.astro +31 -0
  29. package/src/components/RichText.astro +59 -0
  30. package/src/components/Text.astro +97 -0
  31. package/src/components/sharedTypes/index.ts +1 -0
  32. package/src/components/sharedTypes/props.ts +17 -0
  33. package/src/config/define-config.test.ts +526 -0
  34. package/src/config/define-config.ts +99 -0
  35. package/src/config/index.ts +1 -0
  36. package/src/config-cli/define-cli-config.test.ts +95 -0
  37. package/src/config-cli/define-cli-config.ts +50 -0
  38. package/src/config-cli/index.ts +1 -0
  39. package/src/context.ts +68 -0
  40. package/src/editing/constants.ts +8 -0
  41. package/src/editing/editing-config-middleware.test.ts +166 -0
  42. package/src/editing/editing-config-middleware.ts +111 -0
  43. package/src/editing/editing-render-middleware.test.ts +801 -0
  44. package/src/editing/editing-render-middleware.ts +288 -0
  45. package/src/editing/index.ts +16 -0
  46. package/src/editing/render-middleware.test.ts +57 -0
  47. package/src/editing/render-middleware.ts +51 -0
  48. package/src/editing/utils.test.ts +852 -0
  49. package/src/editing/utils.ts +308 -0
  50. package/src/enhancers/WithEmptyFieldEditingComponent.astro +56 -0
  51. package/src/enhancers/WithFieldMetadata.astro +31 -0
  52. package/src/env.d.ts +12 -0
  53. package/src/index.ts +16 -0
  54. package/src/middleware/index.ts +24 -0
  55. package/src/middleware/middleware.test.ts +507 -0
  56. package/src/middleware/middleware.ts +167 -0
  57. package/src/middleware/multisite-middleware.test.ts +672 -0
  58. package/src/middleware/multisite-middleware.ts +147 -0
  59. package/src/middleware/robots-middleware.test.ts +113 -0
  60. package/src/middleware/robots-middleware.ts +47 -0
  61. package/src/middleware/sitemap-middleware.test.ts +152 -0
  62. package/src/middleware/sitemap-middleware.ts +65 -0
  63. package/src/services/component-props-service.ts +182 -0
  64. package/src/sharedTypes/component-props.ts +17 -0
  65. package/src/site/index.ts +1 -0
  66. package/src/test-data/components/Bar.astro +0 -0
  67. package/src/test-data/components/Baz.astro +0 -0
  68. package/src/test-data/components/Foo.astro +0 -0
  69. package/src/test-data/components/Hero.variant.astro +0 -0
  70. package/src/test-data/components/NotComponent.bsx +0 -0
  71. package/src/test-data/components/Qux.astro +0 -0
  72. package/src/test-data/components/folded/Folded.astro +0 -0
  73. package/src/test-data/components/folded/random-file-2.docx +0 -0
  74. package/src/test-data/components/random-file.txt +0 -0
  75. package/src/test-data/helpers.ts +46 -0
  76. package/src/test-data/personalizeData.ts +63 -0
  77. package/src/tools/generate-map.ts +83 -0
  78. package/src/tools/index.ts +8 -0
  79. package/src/tools/templating/components.test.ts +305 -0
  80. package/src/tools/templating/components.ts +49 -0
  81. package/src/tools/templating/constants.ts +4 -0
  82. package/src/tools/templating/default-component.test.ts +31 -0
  83. package/src/tools/templating/default-component.ts +63 -0
  84. package/src/tools/templating/index.ts +2 -0
  85. package/src/utils/index.ts +1 -0
  86. package/src/utils/utils.test.ts +48 -0
  87. package/src/utils/utils.ts +52 -0
@@ -0,0 +1,82 @@
1
+ import { Page } from '@sitecore-content-sdk/core/client';
2
+ import { AstroContentSdkComponent } from '../../sharedTypes/component-props';
3
+ import { ComponentRendering, Field, Item, RouteData } from '@sitecore-content-sdk/core/layout';
4
+
5
+ /** Provided for the component which represents rendering data */
6
+ export type ComponentProps = {
7
+ [key: string]: unknown;
8
+ rendering: ComponentRendering;
9
+ };
10
+
11
+ export interface PlaceholderProps {
12
+ [key: string]: unknown;
13
+ /** Name of the placeholder to render. */
14
+ name: string;
15
+ /** Rendering data to be used when rendering the placeholder. */
16
+ rendering: ComponentRendering | RouteData;
17
+ /**
18
+ * An object of field names/values that are aggregated and propagated through the component tree created by a placeholder.
19
+ * Any component or placeholder rendered by a placeholder will have access to this data via `props.fields`.
20
+ */
21
+ fields?: {
22
+ [name: string]: Field | Item | Item[];
23
+ };
24
+ /**
25
+ * An object of rendering parameter names/values that are aggregated and propagated through the component tree created by a placeholder.
26
+ * Any component or placeholder rendered by a placeholder will have access to this data via `props.params`.
27
+ */
28
+ params?: {
29
+ [name: string]: string;
30
+ };
31
+ /**
32
+ * Modify final props of component (before render) provided by rendering data.
33
+ * Can be used in case when you need to insert additional data into the component.
34
+ * @param {ComponentProps} componentProps component props to be modified
35
+ * @returns {ComponentProps} modified or initial props
36
+ */
37
+ modifyComponentProps?: (componentProps: ComponentProps) => ComponentProps;
38
+ /**
39
+ * A component that is rendered in place of any components that are in this placeholder,
40
+ * but do not have a definition in the componentMap (i.e. don't have an implementation)
41
+ */
42
+ missingComponentComponent?: AstroContentSdkComponent;
43
+
44
+ /**
45
+ * A component that is rendered in place of any components that are hidden
46
+ */
47
+ hiddenRenderingComponent?: AstroContentSdkComponent;
48
+
49
+ /**
50
+ * A component that is rendered in place of the placeholder when an error occurs rendering
51
+ * the placeholder
52
+ */
53
+ errorComponent?: AstroContentSdkComponent;
54
+ /**
55
+ * Page data.
56
+ * This data is passed by the SitecoreProvider.
57
+ */
58
+ page: Page;
59
+
60
+ /**
61
+ * Render HTML or an Astro component when the placeholder contains no content components.
62
+ */
63
+ renderEmpty?: string | AstroContentSdkComponent;
64
+
65
+ /**
66
+ * Render HTML or an Astro component wrapped around the placeholder and components.
67
+ * For HTML wrapper use ${component} string placeholder to set where the placeholder and components should be rendered.
68
+ */
69
+ render?: string | AstroContentSdkComponent;
70
+
71
+ /**
72
+ * Render HTML or an Astro component wrapped around each non-system component added to the placeholder.
73
+ * For HTML wrapper use ${component} string placeholder to set where the component should be rendered.
74
+ * Mutually exclusive with `render`.
75
+ */
76
+ renderEach?: string | AstroContentSdkComponent;
77
+ }
78
+
79
+ export interface ComponentForRendering {
80
+ component: AstroContentSdkComponent;
81
+ isEmpty: boolean;
82
+ }
@@ -0,0 +1,162 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable no-unused-expressions */
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ import { expect } from 'chai';
5
+ import { createSandbox } from 'sinon';
6
+ import { getPlaceholderRenderings, getSXAParams } from './placeholder-utils';
7
+ import { ComponentRendering } from '@sitecore-content-sdk/core/layout';
8
+
9
+ describe('placeholder-utils', () => {
10
+ const sandbox = createSandbox();
11
+ let consoleWarnStub: any;
12
+
13
+ beforeEach(() => {
14
+ consoleWarnStub = sandbox.stub(console, 'warn');
15
+ });
16
+
17
+ afterEach(() => {
18
+ sandbox.restore();
19
+ });
20
+
21
+ describe('getPlaceholderRenderings', () => {
22
+ it('should return null if rendering does not have placeholders', () => {
23
+ const rendering: ComponentRendering = {
24
+ componentName: 'TestComponent',
25
+ uid: 'test-uid',
26
+ };
27
+
28
+ const result = getPlaceholderRenderings(rendering, 'test-placeholder', false);
29
+
30
+ expect(result).to.deep.equal([]);
31
+ expect(consoleWarnStub.calledOnce).to.be.true;
32
+ expect(consoleWarnStub.firstCall.args[0]).to.include('test-placeholder');
33
+ });
34
+
35
+ it('should return renderings from placeholder by name', () => {
36
+ const expectedRenderings = [
37
+ {
38
+ componentName: 'Component1',
39
+ uid: 'comp1-uid',
40
+ },
41
+ {
42
+ componentName: 'Component2',
43
+ uid: 'comp2-uid',
44
+ },
45
+ ];
46
+
47
+ const rendering: ComponentRendering = {
48
+ componentName: 'TestComponent',
49
+ uid: 'test-uid',
50
+ placeholders: {
51
+ 'test-placeholder': expectedRenderings,
52
+ 'other-placeholder': [{ componentName: 'OtherComponent', uid: 'other-uid' }],
53
+ },
54
+ };
55
+
56
+ const result = getPlaceholderRenderings(rendering, 'test-placeholder', false);
57
+
58
+ expect(result).to.deep.equal(expectedRenderings);
59
+ expect(consoleWarnStub.called).to.be.false;
60
+ });
61
+
62
+ it('should parse dynamic SXA placeholder names correctly', () => {
63
+ const expectedRenderings = [
64
+ {
65
+ componentName: 'DynamicComponent',
66
+ uid: 'dynamic-uid',
67
+ },
68
+ ];
69
+
70
+ const rendering: ComponentRendering = {
71
+ componentName: 'TestComponent',
72
+ uid: 'test-uid',
73
+ placeholders: {
74
+ 'container-{*}': expectedRenderings,
75
+ 'other-placeholder': [{ componentName: 'OtherComponent', uid: 'other-uid' }],
76
+ },
77
+ };
78
+
79
+ // Test non-editing mode - should replace dynamic placeholder
80
+ const result = getPlaceholderRenderings(rendering, 'container-1', false);
81
+ expect(result).to.deep.equal(expectedRenderings);
82
+ expect(rendering.placeholders['container-1']).to.deep.equal(expectedRenderings);
83
+ expect(rendering.placeholders['container-{*}']).to.be.undefined;
84
+
85
+ // Reset rendering for editing mode test
86
+ rendering.placeholders = {
87
+ 'container-{*}': expectedRenderings,
88
+ 'other-placeholder': [{ componentName: 'OtherComponent', uid: 'other-uid' }],
89
+ };
90
+
91
+ // Test editing mode - should keep original placeholder name
92
+ const editResult = getPlaceholderRenderings(rendering, 'container-1', true);
93
+ expect(editResult).to.deep.equal(expectedRenderings);
94
+ expect(rendering.placeholders['container-{*}']).to.deep.equal(expectedRenderings);
95
+ });
96
+ });
97
+
98
+ describe('getSXAParams', () => {
99
+ it('should return GridParameters and Styles when present', () => {
100
+ const rendering: ComponentRendering = {
101
+ componentName: 'TestComponent',
102
+ uid: 'test-uid',
103
+ params: {
104
+ GridParameters: 'col-lg-6',
105
+ Styles: 'custom-class',
106
+ OtherParam: 'other-value',
107
+ },
108
+ };
109
+
110
+ const result = getSXAParams(rendering);
111
+
112
+ expect(result).to.deep.equal({
113
+ styles: 'col-lg-6 custom-class',
114
+ });
115
+ });
116
+
117
+ it('should return only GridParameters when Styles not present', () => {
118
+ const rendering: ComponentRendering = {
119
+ componentName: 'TestComponent',
120
+ uid: 'test-uid',
121
+ params: {
122
+ GridParameters: 'col-lg-8',
123
+ OtherParam: 'other-value',
124
+ },
125
+ };
126
+
127
+ const result = getSXAParams(rendering);
128
+
129
+ expect(result).to.deep.equal({
130
+ styles: 'col-lg-8 ',
131
+ });
132
+ });
133
+
134
+ it('should return only Styles when GridParameters not present', () => {
135
+ const rendering: ComponentRendering = {
136
+ componentName: 'TestComponent',
137
+ uid: 'test-uid',
138
+ params: {
139
+ Styles: 'custom-styles',
140
+ OtherParam: 'other-value',
141
+ },
142
+ };
143
+
144
+ const result = getSXAParams(rendering);
145
+
146
+ expect(result).to.deep.equal({
147
+ styles: ' custom-styles',
148
+ });
149
+ });
150
+
151
+ it('should return empty object when no params', () => {
152
+ const rendering: ComponentRendering = {
153
+ componentName: 'TestComponent',
154
+ uid: 'test-uid',
155
+ };
156
+
157
+ const result = getSXAParams(rendering);
158
+
159
+ expect(result).to.deep.equal({});
160
+ });
161
+ });
162
+ });
@@ -0,0 +1,80 @@
1
+ import {
2
+ ComponentRendering,
3
+ RouteData,
4
+ isDynamicPlaceholder,
5
+ getDynamicPlaceholderPattern,
6
+ } from '@sitecore-content-sdk/core/layout';
7
+
8
+ /**
9
+ * Get the renderings for the specified placeholder from the rendering data.
10
+ * @param {ComponentRendering | RouteData } rendering rendering data
11
+ * @param {string} name placeholder name
12
+ * @param {boolean} isEditing whether components should be rendered in editing mode
13
+ * @returns {ComponentRendering[]} array of component renderings
14
+ */
15
+ export const getPlaceholderRenderings = (
16
+ rendering: ComponentRendering | RouteData,
17
+ name: string,
18
+ isEditing: boolean
19
+ ) => {
20
+ let result;
21
+ let phName = name.slice();
22
+
23
+ /**
24
+ * Process (SXA) dynamic placeholders
25
+ * Find and replace the matching dynamic placeholder e.g 'nameOfContainer-{*}' with the requested e.g. 'nameOfContainer-1'.
26
+ * For Metadata EditMode, we need to keep the raw placeholder name in place.
27
+ */
28
+ if (rendering?.placeholders) {
29
+ Object.keys(rendering.placeholders).forEach((placeholder) => {
30
+ const patternPlaceholder = isDynamicPlaceholder(placeholder)
31
+ ? getDynamicPlaceholderPattern(placeholder)
32
+ : null;
33
+
34
+ if (patternPlaceholder && patternPlaceholder.test(phName)) {
35
+ if (isEditing) {
36
+ phName = placeholder;
37
+ } else {
38
+ // @ts-ignore
39
+ rendering.placeholders[phName] = rendering.placeholders[placeholder];
40
+ // @ts-ignore
41
+ delete rendering.placeholders[placeholder];
42
+ }
43
+ }
44
+ });
45
+ }
46
+
47
+ if (rendering && rendering.placeholders && Object.keys(rendering.placeholders).length > 0) {
48
+ result = rendering.placeholders[phName];
49
+ } else {
50
+ result = null;
51
+ }
52
+
53
+ if (!result) {
54
+ console.warn(
55
+ `Placeholder '${phName}' was not found in the current rendering data`,
56
+ JSON.stringify(rendering, null, 2)
57
+ );
58
+
59
+ return [];
60
+ }
61
+
62
+ return result;
63
+ };
64
+
65
+ /**
66
+ * Get SXA specific params from Sitecore rendering params
67
+ * @param {ComponentRendering} rendering rendering object
68
+ * @returns {object} converted SXA params
69
+ */
70
+ export const getSXAParams = (rendering: ComponentRendering) => {
71
+ if (!rendering.params) return {};
72
+
73
+ const { GridParameters, Styles } = rendering.params;
74
+
75
+ return (
76
+ (GridParameters || Styles) && {
77
+ styles: `${GridParameters || ''} ${Styles || ''}`,
78
+ }
79
+ );
80
+ };
@@ -0,0 +1,31 @@
1
+ ---
2
+ import { AstroContentSdkComponent } from '../sharedTypes/component-props';
3
+
4
+ interface RenderWrapperProps {
5
+ wrapper?: AstroContentSdkComponent | string;
6
+ }
7
+
8
+ const { wrapper } = Astro.props as RenderWrapperProps;
9
+
10
+ let html;
11
+ let WrapperComponent = wrapper as AstroContentSdkComponent;
12
+
13
+ const isHtmlWrapper = wrapper && typeof wrapper === 'string';
14
+
15
+ if (isHtmlWrapper) {
16
+ const componentHtml = await Astro.slots.render('default');
17
+ html = wrapper.replace('${component}', componentHtml);
18
+ }
19
+ ---
20
+
21
+ <>
22
+ {!wrapper && <slot />}
23
+ {
24
+ wrapper && !isHtmlWrapper && (
25
+ <WrapperComponent>
26
+ <slot />
27
+ </WrapperComponent>
28
+ )
29
+ }
30
+ {isHtmlWrapper && <Fragment set:html={html} />}
31
+ </>
@@ -0,0 +1,59 @@
1
+ ---
2
+ import { EditableFieldProps } from './sharedTypes';
3
+ import {
4
+ FieldMetadata,
5
+ isFieldValueEmpty,
6
+ } from '@sitecore-content-sdk/core/layout';
7
+ import WithEmptyFieldEditingComponent from '../enhancers/WithEmptyFieldEditingComponent.astro';
8
+ import WithFieldMetadata from '../enhancers/WithFieldMetadata.astro';
9
+ import DefaultEmptyFieldEditingComponentText from './DefaultEmptyFieldEditingComponentText.astro';
10
+
11
+ export interface RichTextField extends FieldMetadata {
12
+ value?: string;
13
+ }
14
+
15
+ export interface RichTextProps extends EditableFieldProps {
16
+ [htmlAttributes: string]: unknown;
17
+ /** The text field data. */
18
+ field: RichTextField;
19
+ /**
20
+ * The HTML element that will wrap the contents of the field.
21
+ */
22
+ tag?: string;
23
+ }
24
+
25
+ const {
26
+ field,
27
+ tag = 'div',
28
+ editable = true,
29
+ emptyFieldEditingComponent,
30
+ } = Astro.props as RichTextProps;
31
+
32
+ const isEmptyField = isFieldValueEmpty(field);
33
+
34
+ const Tag = tag;
35
+
36
+ const attrs = (function () {
37
+ const { field, ...attrs } = Astro.props;
38
+ const { tag, ...finalAttrs } = attrs;
39
+ return finalAttrs;
40
+ })();
41
+ ---
42
+
43
+ {
44
+ (
45
+ <WithFieldMetadata field={field} editable={editable}>
46
+ <WithEmptyFieldEditingComponent
47
+ field={field}
48
+ editable={editable}
49
+ defaultEmptyFieldEditingComponent={
50
+ DefaultEmptyFieldEditingComponentText
51
+ }
52
+ emptyFieldEditingComponent={emptyFieldEditingComponent}
53
+ {...attrs}
54
+ >
55
+ {!isEmptyField && <Tag {...attrs} set:html={field.value} />}
56
+ </WithEmptyFieldEditingComponent>
57
+ </WithFieldMetadata>
58
+ )
59
+ }
@@ -0,0 +1,97 @@
1
+ ---
2
+ import {
3
+ FieldMetadata,
4
+ isFieldValueEmpty,
5
+ } from '@sitecore-content-sdk/core/layout';
6
+ import { EditableFieldProps } from './sharedTypes';
7
+ import WithFieldMetadata from '../enhancers/WithFieldMetadata.astro';
8
+ import WithEmptyFieldEditingComponent from '../enhancers/WithEmptyFieldEditingComponent.astro';
9
+ import DefaultEmptyFieldEditingComponentText from './DefaultEmptyFieldEditingComponentText.astro';
10
+
11
+ export interface TextField extends FieldMetadata {
12
+ value?: string | number;
13
+ }
14
+
15
+ export interface TextProps extends EditableFieldProps {
16
+ [htmlAttributes: string]: unknown;
17
+ /** The text field data. */
18
+ field: TextField;
19
+ /**
20
+ * The HTML element that will wrap the contents of the field.
21
+ */
22
+ tag?: string;
23
+ /**
24
+ * If false, HTML-encoding of the field value is disabled and the value is rendered as-is.
25
+ */
26
+ encode?: boolean;
27
+ }
28
+
29
+ const {
30
+ field,
31
+ tag,
32
+ editable = true,
33
+ encode = true,
34
+ emptyFieldEditingComponent,
35
+ ...otherProps
36
+ } = Astro.props as TextProps;
37
+
38
+ const isEmptyField = isFieldValueEmpty(field);
39
+
40
+ // can't use editable value if we want to output unencoded
41
+ const isEditable = !encode ? false : editable;
42
+
43
+ let output: string | number | string[] = '';
44
+ if (!isEmptyField) {
45
+ output = field.value === undefined ? '' : field.value;
46
+
47
+ // when string value isn't formatted, we should format line breaks
48
+ const splitted = String(output).split('\n');
49
+
50
+ if (splitted.length) {
51
+ const formatted: string[] = [];
52
+
53
+ splitted.forEach((str, i) => {
54
+ const isLast = i === splitted.length - 1;
55
+
56
+ formatted.push(str);
57
+
58
+ if (!isLast) {
59
+ formatted.push('<br />');
60
+ }
61
+ });
62
+
63
+ output = formatted;
64
+ }
65
+ }
66
+
67
+ const Tag = tag || 'span';
68
+
69
+ const attrs: {
70
+ [htmlAttributes: string]: unknown;
71
+ } = {
72
+ ...otherProps,
73
+ };
74
+ ---
75
+
76
+ {
77
+ (
78
+ <WithFieldMetadata field={field} editable={isEditable}>
79
+ <WithEmptyFieldEditingComponent
80
+ field={field}
81
+ editable={isEditable}
82
+ defaultEmptyFieldEditingComponent={
83
+ DefaultEmptyFieldEditingComponentText
84
+ }
85
+ emptyFieldEditingComponent={emptyFieldEditingComponent}
86
+ {...attrs}
87
+ >
88
+ {!isEmptyField && (
89
+ <>
90
+ {(tag || !encode) && <Tag {...attrs} set:html={output} />}
91
+ {(!tag || encode) && <Fragment {...attrs} set:html={output} />}
92
+ </>
93
+ )}
94
+ </WithEmptyFieldEditingComponent>
95
+ </WithFieldMetadata>
96
+ )
97
+ }
@@ -0,0 +1 @@
1
+ export * from './props';
@@ -0,0 +1,17 @@
1
+ import { AstroContentSdkComponent } from "../../sharedTypes/component-props";
2
+
3
+ /**
4
+ * Shared editing field props
5
+ */
6
+ export interface EditableFieldProps {
7
+ /**
8
+ * Can be used to explicitly disable inline editing.
9
+ * @default true
10
+ */
11
+ editable?: boolean;
12
+ /**
13
+ * Custom element to render in Pages in edit mode if field value is empty
14
+ */
15
+ emptyFieldEditingComponent?: AstroContentSdkComponent;
16
+ }
17
+