@sitecore-content-sdk/core 0.1.0-beta.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 (160) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +11 -0
  3. package/dist/cjs/cache-client.js +54 -0
  4. package/dist/cjs/constants.js +13 -0
  5. package/dist/cjs/data-fetcher.js +33 -0
  6. package/dist/cjs/debug.js +43 -0
  7. package/dist/cjs/editing/component-library.js +104 -0
  8. package/dist/cjs/editing/graphql-editing-service.js +186 -0
  9. package/dist/cjs/editing/index.js +23 -0
  10. package/dist/cjs/editing/models.js +23 -0
  11. package/dist/cjs/editing/rest-component-layout-service.js +76 -0
  12. package/dist/cjs/editing/utils.js +86 -0
  13. package/dist/cjs/graphql/app-root-query.js +73 -0
  14. package/dist/cjs/graphql/graphql-edge-proxy.js +21 -0
  15. package/dist/cjs/graphql/index.js +13 -0
  16. package/dist/cjs/graphql/search-service.js +61 -0
  17. package/dist/cjs/graphql-request-client.js +152 -0
  18. package/dist/cjs/i18n/dictionary-service.js +45 -0
  19. package/dist/cjs/i18n/graphql-dictionary-service.js +136 -0
  20. package/dist/cjs/i18n/index.js +7 -0
  21. package/dist/cjs/index.js +55 -0
  22. package/dist/cjs/layout/content-styles.js +70 -0
  23. package/dist/cjs/layout/graphql-layout-service.js +86 -0
  24. package/dist/cjs/layout/index.js +24 -0
  25. package/dist/cjs/layout/layout-service.js +9 -0
  26. package/dist/cjs/layout/models.js +35 -0
  27. package/dist/cjs/layout/themes.js +74 -0
  28. package/dist/cjs/layout/utils.js +110 -0
  29. package/dist/cjs/media/index.js +38 -0
  30. package/dist/cjs/media/media-api.js +96 -0
  31. package/dist/cjs/models.js +2 -0
  32. package/dist/cjs/native-fetcher.js +200 -0
  33. package/dist/cjs/personalize/graphql-personalize-service.js +114 -0
  34. package/dist/cjs/personalize/index.js +14 -0
  35. package/dist/cjs/personalize/layout-personalizer.js +97 -0
  36. package/dist/cjs/personalize/utils.js +136 -0
  37. package/dist/cjs/site/graphql-error-pages-service.js +89 -0
  38. package/dist/cjs/site/graphql-redirects-service.js +105 -0
  39. package/dist/cjs/site/graphql-robots-service.js +83 -0
  40. package/dist/cjs/site/graphql-siteinfo-service.js +107 -0
  41. package/dist/cjs/site/graphql-sitemap-service.js +93 -0
  42. package/dist/cjs/site/index.js +22 -0
  43. package/dist/cjs/site/site-resolver.js +79 -0
  44. package/dist/cjs/site/utils.js +43 -0
  45. package/dist/cjs/utils/env.js +26 -0
  46. package/dist/cjs/utils/index.js +20 -0
  47. package/dist/cjs/utils/is-server.js +10 -0
  48. package/dist/cjs/utils/timeout-promise.js +31 -0
  49. package/dist/cjs/utils/utils.js +222 -0
  50. package/dist/esm/cache-client.js +50 -0
  51. package/dist/esm/constants.js +10 -0
  52. package/dist/esm/data-fetcher.js +28 -0
  53. package/dist/esm/debug.js +36 -0
  54. package/dist/esm/editing/component-library.js +98 -0
  55. package/dist/esm/editing/graphql-editing-service.js +179 -0
  56. package/dist/esm/editing/index.js +5 -0
  57. package/dist/esm/editing/models.js +20 -0
  58. package/dist/esm/editing/rest-component-layout-service.js +72 -0
  59. package/dist/esm/editing/utils.js +76 -0
  60. package/dist/esm/graphql/app-root-query.js +69 -0
  61. package/dist/esm/graphql/graphql-edge-proxy.js +16 -0
  62. package/dist/esm/graphql/index.js +4 -0
  63. package/dist/esm/graphql/search-service.js +57 -0
  64. package/dist/esm/graphql-request-client.js +144 -0
  65. package/dist/esm/i18n/dictionary-service.js +41 -0
  66. package/dist/esm/i18n/graphql-dictionary-service.js +129 -0
  67. package/dist/esm/i18n/index.js +2 -0
  68. package/dist/esm/index.js +9 -0
  69. package/dist/esm/layout/content-styles.js +62 -0
  70. package/dist/esm/layout/graphql-layout-service.js +79 -0
  71. package/dist/esm/layout/index.js +6 -0
  72. package/dist/esm/layout/layout-service.js +5 -0
  73. package/dist/esm/layout/models.js +32 -0
  74. package/dist/esm/layout/themes.js +69 -0
  75. package/dist/esm/layout/utils.js +102 -0
  76. package/dist/esm/media/index.js +2 -0
  77. package/dist/esm/media/media-api.js +86 -0
  78. package/dist/esm/models.js +1 -0
  79. package/dist/esm/native-fetcher.js +193 -0
  80. package/dist/esm/personalize/graphql-personalize-service.js +107 -0
  81. package/dist/esm/personalize/index.js +3 -0
  82. package/dist/esm/personalize/layout-personalizer.js +92 -0
  83. package/dist/esm/personalize/utils.js +128 -0
  84. package/dist/esm/site/graphql-error-pages-service.js +82 -0
  85. package/dist/esm/site/graphql-redirects-service.js +98 -0
  86. package/dist/esm/site/graphql-robots-service.js +76 -0
  87. package/dist/esm/site/graphql-siteinfo-service.js +100 -0
  88. package/dist/esm/site/graphql-sitemap-service.js +86 -0
  89. package/dist/esm/site/index.js +7 -0
  90. package/dist/esm/site/site-resolver.js +75 -0
  91. package/dist/esm/site/utils.js +37 -0
  92. package/dist/esm/utils/env.js +22 -0
  93. package/dist/esm/utils/index.js +3 -0
  94. package/dist/esm/utils/is-server.js +8 -0
  95. package/dist/esm/utils/timeout-promise.js +28 -0
  96. package/dist/esm/utils/utils.js +207 -0
  97. package/editing.d.ts +1 -0
  98. package/editing.js +1 -0
  99. package/graphql.d.ts +1 -0
  100. package/graphql.js +1 -0
  101. package/i18n.d.ts +1 -0
  102. package/i18n.js +1 -0
  103. package/layout.d.ts +1 -0
  104. package/layout.js +1 -0
  105. package/media.d.ts +1 -0
  106. package/media.js +1 -0
  107. package/package.json +76 -0
  108. package/personalize.d.ts +1 -0
  109. package/personalize.js +1 -0
  110. package/site.d.ts +1 -0
  111. package/site.js +1 -0
  112. package/types/cache-client.d.ts +64 -0
  113. package/types/constants.d.ts +7 -0
  114. package/types/data-fetcher.d.ts +34 -0
  115. package/types/debug.d.ts +26 -0
  116. package/types/editing/component-library.d.ts +48 -0
  117. package/types/editing/graphql-editing-service.d.ts +90 -0
  118. package/types/editing/index.d.ts +6 -0
  119. package/types/editing/models.d.ts +52 -0
  120. package/types/editing/rest-component-layout-service.d.ts +100 -0
  121. package/types/editing/utils.d.ts +70 -0
  122. package/types/graphql/app-root-query.d.ts +32 -0
  123. package/types/graphql/graphql-edge-proxy.d.ts +15 -0
  124. package/types/graphql/index.d.ts +4 -0
  125. package/types/graphql/search-service.d.ts +95 -0
  126. package/types/graphql-request-client.d.ts +159 -0
  127. package/types/i18n/dictionary-service.d.ts +56 -0
  128. package/types/i18n/graphql-dictionary-service.d.ts +94 -0
  129. package/types/i18n/index.d.ts +2 -0
  130. package/types/index.d.ts +8 -0
  131. package/types/layout/content-styles.d.ts +18 -0
  132. package/types/layout/graphql-layout-service.d.ts +58 -0
  133. package/types/layout/index.d.ts +6 -0
  134. package/types/layout/layout-service.d.ts +19 -0
  135. package/types/layout/models.d.ts +145 -0
  136. package/types/layout/themes.d.ts +11 -0
  137. package/types/layout/utils.d.ts +40 -0
  138. package/types/media/index.d.ts +2 -0
  139. package/types/media/media-api.d.ts +55 -0
  140. package/types/models.d.ts +6 -0
  141. package/types/native-fetcher.d.ts +121 -0
  142. package/types/personalize/graphql-personalize-service.d.ts +80 -0
  143. package/types/personalize/index.d.ts +3 -0
  144. package/types/personalize/layout-personalizer.d.ts +27 -0
  145. package/types/personalize/utils.d.ts +69 -0
  146. package/types/site/graphql-error-pages-service.d.ts +57 -0
  147. package/types/site/graphql-redirects-service.d.ts +68 -0
  148. package/types/site/graphql-robots-service.d.ts +49 -0
  149. package/types/site/graphql-siteinfo-service.d.ts +70 -0
  150. package/types/site/graphql-sitemap-service.d.ts +55 -0
  151. package/types/site/index.d.ts +7 -0
  152. package/types/site/site-resolver.d.ts +27 -0
  153. package/types/site/utils.d.ts +24 -0
  154. package/types/utils/env.d.ts +7 -0
  155. package/types/utils/index.d.ts +3 -0
  156. package/types/utils/is-server.d.ts +6 -0
  157. package/types/utils/timeout-promise.d.ts +17 -0
  158. package/types/utils/utils.d.ts +71 -0
  159. package/utils.d.ts +1 -0
  160. package/utils.js +1 -0
@@ -0,0 +1,62 @@
1
+ import { SITECORE_EDGE_URL_DEFAULT } from '../constants';
2
+ /**
3
+ * Regular expression to check if the content styles are used in the field value
4
+ */
5
+ const CLASS_REGEXP = /class=".*(\bck-content\b).*"/g;
6
+ /**
7
+ * Get the content styles link to be loaded from the Sitecore Edge Platform
8
+ * @param {LayoutServiceData} layoutData Layout service data
9
+ * @param {string} sitecoreEdgeContextId Sitecore Edge Context ID
10
+ * @param {string} [sitecoreEdgeUrl] Sitecore Edge Platform URL. Default is https://edge-platform.sitecorecloud.io
11
+ * @returns {HTMLLink | null} content styles link, null if no styles are used in layout
12
+ */
13
+ export const getContentStylesheetLink = (layoutData, sitecoreEdgeContextId, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) => {
14
+ if (!layoutData.sitecore.route)
15
+ return null;
16
+ const config = { loadStyles: false };
17
+ traverseComponent(layoutData.sitecore.route, config);
18
+ if (!config.loadStyles)
19
+ return null;
20
+ return {
21
+ href: getContentStylesheetUrl(sitecoreEdgeContextId, sitecoreEdgeUrl),
22
+ rel: 'stylesheet',
23
+ };
24
+ };
25
+ export const getContentStylesheetUrl = (sitecoreEdgeContextId, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) => `${sitecoreEdgeUrl}/v1/files/pages/styles/content-styles.css?sitecoreContextId=${sitecoreEdgeContextId}`;
26
+ export const traversePlaceholder = (components, config) => {
27
+ if (config.loadStyles)
28
+ return;
29
+ components.forEach((component) => {
30
+ traverseComponent(component, config);
31
+ });
32
+ };
33
+ export const traverseField = (field, config) => {
34
+ if (!field || config.loadStyles)
35
+ return;
36
+ if ('value' in field && typeof field.value === 'string') {
37
+ config.loadStyles = CLASS_REGEXP.test(field.value);
38
+ }
39
+ else if ('fields' in field) {
40
+ Object.values(field.fields).forEach((field) => {
41
+ traverseField(field, config);
42
+ });
43
+ }
44
+ else if (Array.isArray(field)) {
45
+ field.forEach((field) => {
46
+ traverseField(field, config);
47
+ });
48
+ }
49
+ };
50
+ export const traverseComponent = (component, config) => {
51
+ if (config.loadStyles)
52
+ return;
53
+ if ('fields' in component && component.fields) {
54
+ Object.values(component.fields).forEach((field) => {
55
+ traverseField(field, config);
56
+ });
57
+ }
58
+ const placeholders = component.placeholders || {};
59
+ Object.keys(placeholders).forEach((placeholder) => {
60
+ traversePlaceholder(placeholders[placeholder], config);
61
+ });
62
+ };
@@ -0,0 +1,79 @@
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
+ import { LayoutServiceBase } from './layout-service';
11
+ import debug from '../debug';
12
+ export const GRAPHQL_LAYOUT_QUERY_NAME = 'JssLayoutQuery';
13
+ /**
14
+ * Service that fetch layout data using Sitecore's GraphQL API.
15
+ * @augments LayoutServiceBase
16
+ * @mixes GraphQLRequestClient
17
+ */
18
+ export class GraphQLLayoutService extends LayoutServiceBase {
19
+ /**
20
+ * Fetch layout data using the Sitecore GraphQL endpoint.
21
+ * @param {GraphQLLayoutServiceConfig} serviceConfig configuration
22
+ */
23
+ constructor(serviceConfig) {
24
+ super();
25
+ this.serviceConfig = serviceConfig;
26
+ this.graphQLClient = this.getGraphQLClient();
27
+ }
28
+ /**
29
+ * Fetch layout data for an item.
30
+ * @param {string} itemPath item path to fetch layout data for.
31
+ * @param {string} [language] the language to fetch layout data for.
32
+ * @returns {Promise<LayoutServiceData>} layout service data
33
+ */
34
+ fetchLayoutData(itemPath, language) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ var _a, _b;
37
+ const query = this.getLayoutQuery(itemPath, language);
38
+ debug.layout('fetching layout data for %s %s %s', itemPath, language, this.serviceConfig.siteName);
39
+ const data = yield this.graphQLClient.request(query);
40
+ // If `rendered` is empty -> not found
41
+ return (((_b = (_a = data === null || data === void 0 ? void 0 : data.layout) === null || _a === void 0 ? void 0 : _a.item) === null || _b === void 0 ? void 0 : _b.rendered) || {
42
+ sitecore: { context: { pageEditing: false, language }, route: null },
43
+ });
44
+ });
45
+ }
46
+ /**
47
+ * Gets a GraphQL client that can make requests to the API.
48
+ * @returns {GraphQLClient} implementation
49
+ */
50
+ getGraphQLClient() {
51
+ if (!this.serviceConfig.clientFactory) {
52
+ throw new Error('clientFactory needs to be provided when initializing GraphQL client.');
53
+ }
54
+ return this.serviceConfig.clientFactory({
55
+ debugger: debug.layout,
56
+ retries: this.serviceConfig.retries,
57
+ retryStrategy: this.serviceConfig.retryStrategy,
58
+ });
59
+ }
60
+ /**
61
+ * Returns GraphQL Layout query
62
+ * @param {string} itemPath page route
63
+ * @param {string} [language] language
64
+ * @returns {string} GraphQL query
65
+ */
66
+ getLayoutQuery(itemPath, language) {
67
+ const languageVariable = language ? `, language:"${language}"` : '';
68
+ const layoutQuery = this.serviceConfig.formatLayoutQuery
69
+ ? this.serviceConfig.formatLayoutQuery(this.serviceConfig.siteName, itemPath, language)
70
+ : `layout(site:"${this.serviceConfig.siteName}", routePath:"${itemPath}"${languageVariable})`;
71
+ return `query ${GRAPHQL_LAYOUT_QUERY_NAME} {
72
+ ${layoutQuery}{
73
+ item {
74
+ rendered
75
+ }
76
+ }
77
+ }`;
78
+ }
79
+ }
@@ -0,0 +1,6 @@
1
+ // layout
2
+ export { LayoutServicePageState, EditMode, RenderingType, EDITING_COMPONENT_PLACEHOLDER, EDITING_COMPONENT_ID, } from './models';
3
+ export { getFieldValue, getChildPlaceholder, isFieldValueEmpty, isDynamicPlaceholder, getDynamicPlaceholderPattern, EMPTY_DATE_FIELD_VALUE, } from './utils';
4
+ export { getContentStylesheetLink } from './content-styles';
5
+ export { GraphQLLayoutService, GRAPHQL_LAYOUT_QUERY_NAME, } from './graphql-layout-service';
6
+ export { getComponentLibraryStylesheetLinks } from './themes';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Base abstraction to implement custom layout service
3
+ */
4
+ export class LayoutServiceBase {
5
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Layout Service page state enum
3
+ * library mode would render a single component
4
+ */
5
+ export var LayoutServicePageState;
6
+ (function (LayoutServicePageState) {
7
+ LayoutServicePageState["Preview"] = "preview";
8
+ LayoutServicePageState["Edit"] = "edit";
9
+ LayoutServicePageState["Normal"] = "normal";
10
+ })(LayoutServicePageState || (LayoutServicePageState = {}));
11
+ /**
12
+ * Represents the edit mode for rendering content in Sitecore Editors
13
+ */
14
+ export var EditMode;
15
+ (function (EditMode) {
16
+ EditMode["Metadata"] = "metadata";
17
+ })(EditMode || (EditMode = {}));
18
+ /**
19
+ * Editing rendering type
20
+ */
21
+ export var RenderingType;
22
+ (function (RenderingType) {
23
+ RenderingType["Component"] = "component";
24
+ })(RenderingType || (RenderingType = {}));
25
+ /**
26
+ * Static placeholder name used for component rendering
27
+ */
28
+ export const EDITING_COMPONENT_PLACEHOLDER = 'editing-componentmode-placeholder';
29
+ /**
30
+ * Id of wrapper for component rendering
31
+ */
32
+ export const EDITING_COMPONENT_ID = 'editing-component';
@@ -0,0 +1,69 @@
1
+ import { getFieldValue } from '.';
2
+ import { SITECORE_EDGE_URL_DEFAULT } from '../constants';
3
+ /**
4
+ * Pattern for library ids
5
+ * @example -library--foo
6
+ */
7
+ const STYLES_LIBRARY_ID_REGEX = /-library--([^\s]+)/;
8
+ /**
9
+ * Walks through rendering tree and returns list of links of all FEAAS, BYOC or SXA Component Library Stylesheets that are used
10
+ * @param {LayoutServiceData} layoutData Layout service data
11
+ * @param {string} sitecoreEdgeContextId Sitecore Edge Context ID
12
+ * @param {string} [sitecoreEdgeUrl] Sitecore Edge Platform URL. Default is https://edge-platform.sitecorecloud.io
13
+ * @returns {HTMLLink[]} library stylesheet links
14
+ */
15
+ export function getComponentLibraryStylesheetLinks(layoutData, sitecoreEdgeContextId, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) {
16
+ const ids = new Set();
17
+ if (!layoutData.sitecore.route)
18
+ return [];
19
+ traverseComponent(layoutData.sitecore.route, ids);
20
+ return [...ids].map((id) => ({
21
+ href: getStylesheetUrl(id, sitecoreEdgeContextId, sitecoreEdgeUrl),
22
+ rel: 'stylesheet',
23
+ }));
24
+ }
25
+ export const getStylesheetUrl = (id, sitecoreEdgeContextId, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) => {
26
+ return `${sitecoreEdgeUrl}/v1/files/components/styles/${id}.css?sitecoreContextId=${sitecoreEdgeContextId}`;
27
+ };
28
+ /**
29
+ * Traverse placeholder and components to add library ids
30
+ * @param {ComponentRendering[]} components
31
+ * @param {Set<string>} ids library ids
32
+ */
33
+ const traversePlaceholder = (components, ids) => {
34
+ components.map((component) => {
35
+ return traverseComponent(component, ids);
36
+ });
37
+ };
38
+ /**
39
+ * Traverse component and children to add library ids
40
+ * @param {RouteData | ComponentRendering | HtmlElementRendering} component component data
41
+ * @param {Set<string>} ids library ids
42
+ */
43
+ const traverseComponent = (component, ids) => {
44
+ var _a, _b, _c, _d, _e, _f;
45
+ let libraryId = undefined;
46
+ if ('params' in component && component.params) {
47
+ // LibraryID in css class name takes precedence over LibraryId attribute
48
+ libraryId =
49
+ ((_b = (_a = component.params.CSSStyles) === null || _a === void 0 ? void 0 : _a.match(STYLES_LIBRARY_ID_REGEX)) === null || _b === void 0 ? void 0 : _b[1]) ||
50
+ ((_d = (_c = component.params.Styles) === null || _c === void 0 ? void 0 : _c.match(STYLES_LIBRARY_ID_REGEX)) === null || _d === void 0 ? void 0 : _d[1]) ||
51
+ component.params.LibraryId ||
52
+ undefined;
53
+ }
54
+ // if params are empty we try to fall back to data source
55
+ if (!libraryId && 'fields' in component && component.fields) {
56
+ libraryId =
57
+ ((_e = getFieldValue(component.fields, 'CSSStyles', '').match(STYLES_LIBRARY_ID_REGEX)) === null || _e === void 0 ? void 0 : _e[1]) ||
58
+ ((_f = getFieldValue(component.fields, 'Styles', '').match(STYLES_LIBRARY_ID_REGEX)) === null || _f === void 0 ? void 0 : _f[1]) ||
59
+ getFieldValue(component.fields, 'LibraryId', '') ||
60
+ undefined;
61
+ }
62
+ if (libraryId) {
63
+ ids.add(libraryId);
64
+ }
65
+ const placeholders = component.placeholders || {};
66
+ Object.keys(placeholders).forEach((placeholder) => {
67
+ traversePlaceholder(placeholders[placeholder], ids);
68
+ });
69
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @param {ComponentRendering | Fields} renderingOrFields the rendering or fields object to extract the field from
3
+ * @param {string} fieldName the name of the field to extract
4
+ * @param {T} [defaultValue] the default value to return if the field is not defined
5
+ * @returns {Field | T} the field value or the default value if the field is not defined
6
+ */
7
+ // eslint-disable-next-line no-redeclare
8
+ export function getFieldValue(renderingOrFields, fieldName, defaultValue) {
9
+ if (!renderingOrFields || !fieldName) {
10
+ return defaultValue;
11
+ }
12
+ const fields = renderingOrFields;
13
+ const field = fields[fieldName];
14
+ if (field && typeof field.value !== 'undefined') {
15
+ return field.value;
16
+ }
17
+ const rendering = renderingOrFields;
18
+ if (!rendering.fields ||
19
+ !rendering.fields[fieldName] ||
20
+ typeof rendering.fields[fieldName].value === 'undefined') {
21
+ return defaultValue;
22
+ }
23
+ return rendering.fields[fieldName].value;
24
+ }
25
+ /**
26
+ * Gets rendering definitions in a given child placeholder under a current rendering.
27
+ * @param {ComponentRendering} rendering
28
+ * @param {string} placeholderName
29
+ * @returns {ComponentRendering[]} child placeholder
30
+ */
31
+ export function getChildPlaceholder(rendering, placeholderName) {
32
+ if (!rendering ||
33
+ !placeholderName ||
34
+ !rendering.placeholders ||
35
+ !rendering.placeholders[placeholderName]) {
36
+ return [];
37
+ }
38
+ return rendering.placeholders[placeholderName];
39
+ }
40
+ /**
41
+ * Returns a regular expression pattern for a dynamic placeholder name.
42
+ * @param {string} placeholder Placeholder name with a dynamic segment (e.g. 'main-{*}')
43
+ * @returns Regular expression pattern for the dynamic segment
44
+ */
45
+ export const getDynamicPlaceholderPattern = (placeholder) => {
46
+ return new RegExp(`^${placeholder.replace(/\{\*\}+/i, '\\d+')}$`);
47
+ };
48
+ /**
49
+ * Checks if the placeholder name is dynamic.
50
+ * @param {string} placeholder Placeholder name
51
+ * @returns True if the placeholder name is dynamic
52
+ */
53
+ export const isDynamicPlaceholder = (placeholder) => placeholder.indexOf('{*}') !== -1;
54
+ /**
55
+ * The default value for an empty Date field.
56
+ * This value is defined as a default one by .NET
57
+ */
58
+ export const EMPTY_DATE_FIELD_VALUE = '0001-01-01T00:00:00Z';
59
+ /**
60
+ * Determines if the passed in field object's value is empty.
61
+ * @param {GenericFieldValue | Partial<Field>} field the field object.
62
+ * Partial<T> type is used here because _field.value_ could be required or optional for the different field types
63
+ */
64
+ export function isFieldValueEmpty(field) {
65
+ const isImageFieldEmpty = (fieldValue) => !fieldValue.src;
66
+ const isFileFieldEmpty = (fieldValue) => !fieldValue.src;
67
+ const isLinkFieldEmpty = (fieldValue) => !fieldValue.href;
68
+ const isDateFieldEmpty = (fieldValue) => {
69
+ if (typeof fieldValue === 'string') {
70
+ return fieldValue === EMPTY_DATE_FIELD_VALUE;
71
+ }
72
+ else {
73
+ return !(typeof (fieldValue === null || fieldValue === void 0 ? void 0 : fieldValue.getMonth) === 'function' &&
74
+ !isNaN(fieldValue === null || fieldValue === void 0 ? void 0 : fieldValue.getMonth()));
75
+ }
76
+ };
77
+ const isEmpty = (fieldValue) => {
78
+ if (fieldValue === null || fieldValue === undefined) {
79
+ return true;
80
+ }
81
+ if (typeof fieldValue === 'object') {
82
+ return (isImageFieldEmpty(fieldValue) &&
83
+ isFileFieldEmpty(fieldValue) &&
84
+ isLinkFieldEmpty(fieldValue) &&
85
+ isDateFieldEmpty(fieldValue));
86
+ }
87
+ else if (typeof fieldValue === 'number' || typeof fieldValue === 'boolean') {
88
+ // Avoid returning true for 0 and false values
89
+ return false;
90
+ }
91
+ else {
92
+ return !fieldValue || isDateFieldEmpty(fieldValue);
93
+ }
94
+ };
95
+ if (!field)
96
+ return true;
97
+ const dynamicField = field;
98
+ if (dynamicField.value !== undefined) {
99
+ return isEmpty(dynamicField.value);
100
+ }
101
+ return isEmpty(field);
102
+ }
@@ -0,0 +1,2 @@
1
+ import * as mediaApi from './media-api';
2
+ export { mediaApi };
@@ -0,0 +1,86 @@
1
+ import URL from 'url-parse';
2
+ // finds the Sitecore media URL prefix
3
+ const mediaUrlPrefixRegex = /\/([-~]{1})\/media\//i;
4
+ /**
5
+ * Get required query string params which should be merged with user params
6
+ * @param {object} qs layout service parsed query string
7
+ * @returns {object} requiredParams
8
+ */
9
+ export const getRequiredParams = (qs) => {
10
+ const { rev, db, la, vs, ts } = qs;
11
+ return { rev, db, la, vs, ts };
12
+ };
13
+ /**
14
+ * Replace `/~/media` or `/-/media` with `/~/jssmedia` or `/-/jssmedia`, respectively.
15
+ * Can use `mediaUrlPrefix` in order to use a custom prefix.
16
+ * @param {string} url The URL to replace the media URL prefix in
17
+ * @param {RegExp} [mediaUrlPrefix] The regex to match the media URL prefix
18
+ * @returns {string} The URL with the media URL prefix replaced
19
+ */
20
+ export const replaceMediaUrlPrefix = (url, mediaUrlPrefix = mediaUrlPrefixRegex) => {
21
+ const parsed = URL(url, {}, true);
22
+ const match = mediaUrlPrefix.exec(parsed.pathname);
23
+ if (match && match.length > 1) {
24
+ // regex will provide us with /-/ or /~/ type
25
+ parsed.set('pathname', parsed.pathname.replace(mediaUrlPrefix, `/${match[1]}/jssmedia/`));
26
+ }
27
+ return parsed.toString();
28
+ };
29
+ /**
30
+ * Prepares a Sitecore media URL with `params` for use by the JSS media handler.
31
+ * This is done by replacing `/~/media` or `/-/media` with `/~/jssmedia` or `/-/jssmedia`, respectively.
32
+ * Provided `params` are used as the querystring parameters for the media URL.
33
+ * Can use `mediaUrlPrefix` in order to use a custom prefix.
34
+ * If no `params` are sent, the original media URL is returned.
35
+ * @param {string} url The URL to prepare
36
+ * @param {object} [params] The querystring parameters to use
37
+ * @param {RegExp} [mediaUrlPrefix] The regex to match the media URL prefix
38
+ * @returns {string} The prepared URL
39
+ */
40
+ export const updateImageUrl = (url, params, mediaUrlPrefix = mediaUrlPrefixRegex) => {
41
+ if (!params || Object.keys(params).length === 0) {
42
+ // if params aren't supplied, no need to run it through JSS media handler
43
+ return url;
44
+ }
45
+ // polyfill node `global` in browser to workaround https://github.com/unshiftio/url-parse/issues/150
46
+ if (typeof window !== 'undefined' && !window.global) {
47
+ window.global = {};
48
+ }
49
+ const parsed = URL(replaceMediaUrlPrefix(url, mediaUrlPrefix), {}, true);
50
+ const requiredParams = getRequiredParams(parsed.query);
51
+ const query = Object.assign({}, params);
52
+ Object.entries(requiredParams).forEach(([key, param]) => {
53
+ if (param) {
54
+ query[key] = param;
55
+ }
56
+ });
57
+ parsed.set('query', query);
58
+ return parsed.toString();
59
+ };
60
+ /**
61
+ * Receives an array of `srcSet` parameters that are iterated and used as parameters to generate
62
+ * a corresponding set of updated Sitecore media URLs via @see updateImageUrl. The result is a comma-delimited
63
+ * list of media URLs with respective dimension parameters.
64
+ * @example
65
+ * // returns '/ipsum.jpg?h=1000&w=1000 1000w, /ipsum.jpg?mh=250&mw=250 250w'
66
+ * getSrcSet('/ipsum.jpg', [{ h: 1000, w: 1000 }, { mh: 250, mw: 250 } ])
67
+ * More information about `srcSet`: {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img}
68
+ * @param {string} url The URL to prepare
69
+ * @param {Array} srcSet The array of parameters to use
70
+ * @param {object} [imageParams] The querystring parameters to use
71
+ * @param {RegExp} [mediaUrlPrefix] The regex to match the media URL prefix
72
+ * @returns {string} The prepared URL
73
+ */
74
+ export const getSrcSet = (url, srcSet, imageParams, mediaUrlPrefix) => {
75
+ return srcSet
76
+ .map((params) => {
77
+ const newParams = Object.assign(Object.assign({}, imageParams), params);
78
+ const imageWidth = newParams.w || newParams.mw;
79
+ if (!imageWidth) {
80
+ return null;
81
+ }
82
+ return `${updateImageUrl(url, newParams, mediaUrlPrefix)} ${imageWidth}w`;
83
+ })
84
+ .filter((value) => value)
85
+ .join(', ');
86
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,193 @@
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
+ var __rest = (this && this.__rest) || function (s, e) {
11
+ var t = {};
12
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13
+ t[p] = s[p];
14
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
15
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
16
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
17
+ t[p[i]] = s[p[i]];
18
+ }
19
+ return t;
20
+ };
21
+ import debuggers from './debug';
22
+ import TimeoutPromise from './utils/timeout-promise';
23
+ export class NativeDataFetcher {
24
+ constructor(config = {}) {
25
+ this.config = config;
26
+ }
27
+ /**
28
+ * Implements a data fetcher.
29
+ * @param {string} url The URL to request (may include query string)
30
+ * @param {RequestInit} [options] Optional fetch options
31
+ * @returns {Promise<NativeDataFetcherResponse<T>>} response
32
+ */
33
+ fetch(url_1) {
34
+ return __awaiter(this, arguments, void 0, function* (url, options = {}) {
35
+ var _a;
36
+ const _b = this.config, { debugger: debugOverride, fetch: fetchOverride } = _b, init = __rest(_b, ["debugger", "fetch"]);
37
+ const startTimestamp = Date.now();
38
+ const fetchImpl = fetchOverride || fetch;
39
+ const debug = debugOverride || debuggers.http;
40
+ const requestInit = this.getRequestInit(Object.assign(Object.assign({}, init), options));
41
+ const fetchWithOptionalTimeout = [fetchImpl(url, requestInit)];
42
+ if (init.timeout) {
43
+ this.abortTimeout = new TimeoutPromise(init.timeout);
44
+ fetchWithOptionalTimeout.push(this.abortTimeout.start);
45
+ }
46
+ debug('Request initiated: %o', Object.assign({ url, headers: this.extractDebugHeaders(requestInit.headers) }, requestInit));
47
+ try {
48
+ const response = yield Promise.race(fetchWithOptionalTimeout).then((res) => {
49
+ var _a;
50
+ (_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear();
51
+ return res;
52
+ });
53
+ const respData = yield this.parseResponse(response, debug);
54
+ if (!response.ok) {
55
+ const error = this.createError(response, respData);
56
+ debug('Response error: %o', error.response);
57
+ throw error;
58
+ }
59
+ debug('Response in %dms: %o', Date.now() - startTimestamp, {
60
+ status: response.status,
61
+ statusText: response.statusText,
62
+ headers: this.extractDebugHeaders(response.headers),
63
+ url: response.url,
64
+ data: respData,
65
+ });
66
+ return Object.assign(Object.assign({}, response), { data: respData });
67
+ }
68
+ catch (error) {
69
+ (_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear();
70
+ debug('Request failed: %o', error);
71
+ throw error;
72
+ }
73
+ });
74
+ }
75
+ /**
76
+ * Perform a GET request
77
+ * @param {string} url The URL to request (may include query string)
78
+ * @param {RequestInit} [options] Fetch options
79
+ * @returns {Promise<NativeDataFetcherResponse<T>>} response
80
+ */
81
+ get(url_1) {
82
+ return __awaiter(this, arguments, void 0, function* (url, options = {}) {
83
+ return this.fetch(url, Object.assign({ method: 'GET' }, options));
84
+ });
85
+ }
86
+ /**
87
+ * Perform a POST request
88
+ * @param {string} url The URL to request (may include query string)
89
+ * @param {unknown} body The data to send with the request
90
+ * @param {RequestInit} [options] Fetch options
91
+ * @returns {Promise<NativeDataFetcherResponse<T>>} response
92
+ */
93
+ post(url_1, body_1) {
94
+ return __awaiter(this, arguments, void 0, function* (url, body, options = {}) {
95
+ return this.fetch(url, Object.assign({ method: 'POST', body: JSON.stringify(body) }, options));
96
+ });
97
+ }
98
+ /**
99
+ * Perform a DELETE request
100
+ * @param {string} url The URL to request (may include query string)
101
+ * @param {RequestInit} [options] Fetch options
102
+ * @returns {Promise<NativeDataFetcherResponse<T>>} response
103
+ */
104
+ delete(url_1) {
105
+ return __awaiter(this, arguments, void 0, function* (url, options = {}) {
106
+ return this.fetch(url, Object.assign({ method: 'DELETE' }, options));
107
+ });
108
+ }
109
+ /**
110
+ * Perform a PUT request
111
+ * @param {string} url The URL to request (may include query string)
112
+ * @param {unknown} body The data to send with the request
113
+ * @param {RequestInit} [options] Fetch options
114
+ * @returns {Promise<NativeDataFetcherResponse<T>>} response
115
+ */
116
+ put(url_1, body_1) {
117
+ return __awaiter(this, arguments, void 0, function* (url, body, options = {}) {
118
+ return this.fetch(url, Object.assign({ method: 'PUT', body: JSON.stringify(body) }, options));
119
+ });
120
+ }
121
+ /**
122
+ * Perform a HEAD request
123
+ * @param {string} url The URL to request (may include query string)
124
+ * @param {RequestInit} [options] Fetch options
125
+ * @returns {Promise<NativeDataFetcherResponse<T>>} response
126
+ */
127
+ head(url, options = {}) {
128
+ return this.fetch(url, Object.assign({ method: 'HEAD' }, options));
129
+ }
130
+ /**
131
+ * Determines settings for the request
132
+ * @param {RequestInit} init Custom settings for request
133
+ * @returns {RequestInit} The final request settings
134
+ */
135
+ getRequestInit(init = {}) {
136
+ const headers = new Headers(init.headers);
137
+ if (!init.method) {
138
+ init.method = init.body ? 'POST' : 'GET';
139
+ }
140
+ headers.set('Content-Type', 'application/json');
141
+ init.headers = headers;
142
+ return init;
143
+ }
144
+ /**
145
+ * Safely extract all headers for debug logging
146
+ * @param {HeadersInit} incomingHeaders Incoming headers
147
+ * @returns Object with headers as key/value pairs
148
+ */
149
+ extractDebugHeaders(incomingHeaders = {}) {
150
+ const headers = {};
151
+ if (typeof (incomingHeaders === null || incomingHeaders === void 0 ? void 0 : incomingHeaders.forEach) !== 'string' && incomingHeaders.forEach) {
152
+ incomingHeaders === null || incomingHeaders === void 0 ? void 0 : incomingHeaders.forEach((value, key) => {
153
+ headers[key] = value;
154
+ });
155
+ }
156
+ return headers;
157
+ }
158
+ /**
159
+ * Parses the response data.
160
+ * @param {Response} response - The fetch response object.
161
+ * @param {Function} debug - The debug logger function.
162
+ * @returns {Promise<unknown>} - The parsed response data.
163
+ */
164
+ parseResponse(response, debug) {
165
+ return __awaiter(this, void 0, void 0, function* () {
166
+ const contentType = response.headers.get('Content-Type') || '';
167
+ try {
168
+ if (contentType.includes('application/json')) {
169
+ return yield response.json();
170
+ }
171
+ return yield response.text();
172
+ }
173
+ catch (error) {
174
+ debug('Response parsing error: %o', error);
175
+ return undefined;
176
+ }
177
+ });
178
+ }
179
+ /**
180
+ * Creates a custom error for fetch failures.
181
+ * @param {Response} response - The fetch response object.
182
+ * @param {unknown} data - The parsed response data.
183
+ * @returns {NativeDataFetcherError} - The constructed error object.
184
+ */
185
+ createError(response, data) {
186
+ return Object.assign(Object.assign({}, new Error(`HTTP ${response.status} ${response.statusText}`)), { response: {
187
+ status: response.status,
188
+ statusText: response.statusText,
189
+ headers: this.extractDebugHeaders(response.headers),
190
+ data,
191
+ } });
192
+ }
193
+ }