@sitecore-jss/sitecore-jss 0.1.0-beta.2

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 (143) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +7 -0
  3. package/dist/cjs/cache-client.js +54 -0
  4. package/dist/cjs/constants.js +12 -0
  5. package/dist/cjs/debug.js +43 -0
  6. package/dist/cjs/graphql/app-root-query.js +73 -0
  7. package/dist/cjs/graphql/graphql-edge-proxy.js +12 -0
  8. package/dist/cjs/graphql/index.js +11 -0
  9. package/dist/cjs/graphql/search-service.js +60 -0
  10. package/dist/cjs/graphql-request-client.js +106 -0
  11. package/dist/cjs/i18n/dictionary-service.js +45 -0
  12. package/dist/cjs/i18n/graphql-dictionary-service.js +125 -0
  13. package/dist/cjs/i18n/index.js +7 -0
  14. package/dist/cjs/index.js +36 -0
  15. package/dist/cjs/layout/content-styles.js +73 -0
  16. package/dist/cjs/layout/graphql-layout-service.js +84 -0
  17. package/dist/cjs/layout/index.js +18 -0
  18. package/dist/cjs/layout/layout-service.js +9 -0
  19. package/dist/cjs/layout/models.js +27 -0
  20. package/dist/cjs/layout/themes.js +79 -0
  21. package/dist/cjs/layout/utils.js +44 -0
  22. package/dist/cjs/media/index.js +24 -0
  23. package/dist/cjs/media/media-api.js +128 -0
  24. package/dist/cjs/models.js +2 -0
  25. package/dist/cjs/native-fetcher.js +183 -0
  26. package/dist/cjs/personalize/graphql-personalize-service.js +114 -0
  27. package/dist/cjs/personalize/index.js +12 -0
  28. package/dist/cjs/personalize/layout-personalizer.js +75 -0
  29. package/dist/cjs/personalize/utils.js +92 -0
  30. package/dist/cjs/site/graphql-error-pages-service.js +86 -0
  31. package/dist/cjs/site/graphql-redirects-service.js +103 -0
  32. package/dist/cjs/site/graphql-robots-service.js +81 -0
  33. package/dist/cjs/site/graphql-siteinfo-service.js +128 -0
  34. package/dist/cjs/site/graphql-sitemap-service.js +91 -0
  35. package/dist/cjs/site/index.js +22 -0
  36. package/dist/cjs/site/site-resolver.js +79 -0
  37. package/dist/cjs/site/utils.js +43 -0
  38. package/dist/cjs/utils/edit-frame.js +138 -0
  39. package/dist/cjs/utils/editing.js +122 -0
  40. package/dist/cjs/utils/env.js +26 -0
  41. package/dist/cjs/utils/index.js +25 -0
  42. package/dist/cjs/utils/is-server.js +10 -0
  43. package/dist/cjs/utils/timeout-promise.js +31 -0
  44. package/dist/cjs/utils/utils.js +70 -0
  45. package/dist/esm/cache-client.js +50 -0
  46. package/dist/esm/constants.js +9 -0
  47. package/dist/esm/debug.js +36 -0
  48. package/dist/esm/graphql/app-root-query.js +69 -0
  49. package/dist/esm/graphql/graphql-edge-proxy.js +8 -0
  50. package/dist/esm/graphql/index.js +4 -0
  51. package/dist/esm/graphql/search-service.js +56 -0
  52. package/dist/esm/graphql-request-client.js +99 -0
  53. package/dist/esm/i18n/dictionary-service.js +41 -0
  54. package/dist/esm/i18n/graphql-dictionary-service.js +118 -0
  55. package/dist/esm/i18n/index.js +2 -0
  56. package/dist/esm/index.js +7 -0
  57. package/dist/esm/layout/content-styles.js +65 -0
  58. package/dist/esm/layout/graphql-layout-service.js +77 -0
  59. package/dist/esm/layout/index.js +6 -0
  60. package/dist/esm/layout/layout-service.js +5 -0
  61. package/dist/esm/layout/models.js +24 -0
  62. package/dist/esm/layout/themes.js +74 -0
  63. package/dist/esm/layout/utils.js +39 -0
  64. package/dist/esm/media/index.js +2 -0
  65. package/dist/esm/media/media-api.js +117 -0
  66. package/dist/esm/models.js +1 -0
  67. package/dist/esm/native-fetcher.js +176 -0
  68. package/dist/esm/personalize/graphql-personalize-service.js +107 -0
  69. package/dist/esm/personalize/index.js +3 -0
  70. package/dist/esm/personalize/layout-personalizer.js +69 -0
  71. package/dist/esm/personalize/utils.js +85 -0
  72. package/dist/esm/site/graphql-error-pages-service.js +79 -0
  73. package/dist/esm/site/graphql-redirects-service.js +96 -0
  74. package/dist/esm/site/graphql-robots-service.js +74 -0
  75. package/dist/esm/site/graphql-siteinfo-service.js +121 -0
  76. package/dist/esm/site/graphql-sitemap-service.js +84 -0
  77. package/dist/esm/site/index.js +7 -0
  78. package/dist/esm/site/site-resolver.js +75 -0
  79. package/dist/esm/site/utils.js +37 -0
  80. package/dist/esm/utils/edit-frame.js +133 -0
  81. package/dist/esm/utils/editing.js +111 -0
  82. package/dist/esm/utils/env.js +22 -0
  83. package/dist/esm/utils/index.js +5 -0
  84. package/dist/esm/utils/is-server.js +8 -0
  85. package/dist/esm/utils/timeout-promise.js +28 -0
  86. package/dist/esm/utils/utils.js +61 -0
  87. package/graphql.d.ts +1 -0
  88. package/graphql.js +1 -0
  89. package/i18n.d.ts +1 -0
  90. package/i18n.js +1 -0
  91. package/layout.d.ts +1 -0
  92. package/layout.js +1 -0
  93. package/media.d.ts +1 -0
  94. package/media.js +1 -0
  95. package/package.json +71 -0
  96. package/personalize.d.ts +1 -0
  97. package/personalize.js +1 -0
  98. package/site.d.ts +1 -0
  99. package/site.js +1 -0
  100. package/types/cache-client.d.ts +64 -0
  101. package/types/constants.d.ts +6 -0
  102. package/types/debug.d.ts +26 -0
  103. package/types/graphql/app-root-query.d.ts +32 -0
  104. package/types/graphql/graphql-edge-proxy.d.ts +7 -0
  105. package/types/graphql/index.d.ts +4 -0
  106. package/types/graphql/search-service.d.ts +92 -0
  107. package/types/graphql-request-client.d.ts +88 -0
  108. package/types/i18n/dictionary-service.d.ts +56 -0
  109. package/types/i18n/graphql-dictionary-service.d.ts +65 -0
  110. package/types/i18n/index.d.ts +2 -0
  111. package/types/index.d.ts +6 -0
  112. package/types/layout/content-styles.d.ts +18 -0
  113. package/types/layout/graphql-layout-service.d.ts +59 -0
  114. package/types/layout/index.d.ts +6 -0
  115. package/types/layout/layout-service.d.ts +20 -0
  116. package/types/layout/models.d.ts +140 -0
  117. package/types/layout/themes.d.ts +11 -0
  118. package/types/layout/utils.d.ts +17 -0
  119. package/types/media/index.d.ts +2 -0
  120. package/types/media/media-api.d.ts +69 -0
  121. package/types/models.d.ts +6 -0
  122. package/types/native-fetcher.d.ts +92 -0
  123. package/types/personalize/graphql-personalize-service.d.ts +77 -0
  124. package/types/personalize/index.d.ts +3 -0
  125. package/types/personalize/layout-personalizer.d.ts +25 -0
  126. package/types/personalize/utils.d.ts +53 -0
  127. package/types/site/graphql-error-pages-service.d.ts +55 -0
  128. package/types/site/graphql-redirects-service.d.ts +66 -0
  129. package/types/site/graphql-robots-service.d.ts +47 -0
  130. package/types/site/graphql-siteinfo-service.d.ts +69 -0
  131. package/types/site/graphql-sitemap-service.d.ts +53 -0
  132. package/types/site/index.d.ts +7 -0
  133. package/types/site/site-resolver.d.ts +27 -0
  134. package/types/site/utils.d.ts +24 -0
  135. package/types/utils/edit-frame.d.ts +76 -0
  136. package/types/utils/editing.d.ts +58 -0
  137. package/types/utils/env.d.ts +7 -0
  138. package/types/utils/index.d.ts +5 -0
  139. package/types/utils/is-server.d.ts +6 -0
  140. package/types/utils/timeout-promise.d.ts +18 -0
  141. package/types/utils/utils.d.ts +18 -0
  142. package/utils.d.ts +1 -0
  143. package/utils.js +1 -0
@@ -0,0 +1,74 @@
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 { siteNameError } from '../constants';
11
+ import debug from '../debug';
12
+ // The default query for request robots.txt
13
+ const defaultQuery = /* GraphQL */ `
14
+ query RobotsQuery($siteName: String!) {
15
+ site {
16
+ siteInfo(site: $siteName) {
17
+ robots
18
+ }
19
+ }
20
+ }
21
+ `;
22
+ /**
23
+ * Service that fetch the robots.txt data using Sitecore's GraphQL API.
24
+ */
25
+ export class GraphQLRobotsService {
26
+ /**
27
+ * Creates an instance of graphQL robots.txt service with the provided options
28
+ * @param {GraphQLRobotsServiceConfig} options instance
29
+ */
30
+ constructor(options) {
31
+ this.options = options;
32
+ this.graphQLClient = this.getGraphQLClient();
33
+ }
34
+ get query() {
35
+ return defaultQuery;
36
+ }
37
+ /**
38
+ * Fetch a data of robots.txt from API
39
+ * @returns text of robots.txt
40
+ * @throws {Error} if the siteName is empty.
41
+ */
42
+ fetchRobots() {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ const siteName = this.options.siteName;
45
+ if (!siteName) {
46
+ throw new Error(siteNameError);
47
+ }
48
+ const robotsResult = this.graphQLClient.request(this.query, {
49
+ siteName,
50
+ });
51
+ try {
52
+ return robotsResult.then((result) => {
53
+ var _a, _b;
54
+ return (_b = (_a = result === null || result === void 0 ? void 0 : result.site) === null || _a === void 0 ? void 0 : _a.siteInfo) === null || _b === void 0 ? void 0 : _b.robots;
55
+ });
56
+ }
57
+ catch (e) {
58
+ return Promise.reject(e);
59
+ }
60
+ });
61
+ }
62
+ /**
63
+ * Gets a GraphQL client that can make requests to the API.
64
+ * @returns {GraphQLClient} implementation
65
+ */
66
+ getGraphQLClient() {
67
+ if (!this.options.clientFactory) {
68
+ throw new Error('You should provide a clientFactory.');
69
+ }
70
+ return this.options.clientFactory({
71
+ debugger: debug.robots,
72
+ });
73
+ }
74
+ }
@@ -0,0 +1,121 @@
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 debug from '../debug';
11
+ import { MemoryCacheClient } from '../cache-client';
12
+ const headlessSiteGroupingTemplate = 'E46F3AF2-39FA-4866-A157-7017C4B2A40C';
13
+ const sitecoreContentRootItem = '0DE95AE4-41AB-4D01-9EB0-67441B7C2450';
14
+ const defaultQuery = /* GraphQL */ `
15
+ query($pageSize: Int = 10, $after: String) {
16
+ search(
17
+ where: {
18
+ AND: [
19
+ { name: "_templates", value: "${headlessSiteGroupingTemplate}", operator: CONTAINS }
20
+ { name: "_path", value: "${sitecoreContentRootItem}", operator: CONTAINS }
21
+ ]
22
+ }
23
+ first: $pageSize
24
+ after: $after
25
+ ) {
26
+ pageInfo {
27
+ endCursor
28
+ hasNext
29
+ }
30
+ results {
31
+ ... on Item {
32
+ name: field(name: "SiteName") {
33
+ value
34
+ }
35
+ hostName: field(name: "Hostname") {
36
+ value
37
+ }
38
+ language: field(name: "Language") {
39
+ value
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ `;
46
+ export class GraphQLSiteInfoService {
47
+ /**
48
+ * Creates an instance of graphQL service to retrieve site configuration list from Sitecore
49
+ * @param {GraphQLSiteInfoServiceConfig} config instance
50
+ */
51
+ constructor(config) {
52
+ this.config = config;
53
+ this.graphQLClient = this.getGraphQLClient();
54
+ this.cache = this.getCacheClient();
55
+ }
56
+ get query() {
57
+ return defaultQuery;
58
+ }
59
+ fetchSiteInfo() {
60
+ var _a, _b;
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ const cachedResult = this.cache.getCacheValue(this.getCacheKey());
63
+ if (cachedResult) {
64
+ return cachedResult;
65
+ }
66
+ if (process.env.SITECORE) {
67
+ debug.multisite('Skipping site information fetch (building on XM Cloud)');
68
+ return [];
69
+ }
70
+ const results = [];
71
+ let hasNext = true;
72
+ let after = '';
73
+ while (hasNext) {
74
+ const response = yield this.graphQLClient.request(this.query, {
75
+ pageSize: this.config.pageSize,
76
+ after,
77
+ });
78
+ const result = (_b = (_a = response === null || response === void 0 ? void 0 : response.search) === null || _a === void 0 ? void 0 : _a.results) === null || _b === void 0 ? void 0 : _b.reduce((result, current) => {
79
+ result.push({
80
+ name: current.name.value,
81
+ hostName: current.hostName.value,
82
+ language: current.language.value,
83
+ });
84
+ return result;
85
+ }, []);
86
+ results.push(...result);
87
+ hasNext = response.search.pageInfo.hasNext;
88
+ after = response.search.pageInfo.endCursor;
89
+ }
90
+ this.cache.setCacheValue(this.getCacheKey(), results);
91
+ return results;
92
+ });
93
+ }
94
+ /**
95
+ * Gets cache client implementation
96
+ * Override this method if custom cache needs to be used
97
+ * @returns CacheClient instance
98
+ */
99
+ getCacheClient() {
100
+ var _a, _b;
101
+ return new MemoryCacheClient({
102
+ cacheEnabled: (_a = this.config.cacheEnabled) !== null && _a !== void 0 ? _a : true,
103
+ cacheTimeout: (_b = this.config.cacheTimeout) !== null && _b !== void 0 ? _b : 10,
104
+ });
105
+ }
106
+ /**
107
+ * Gets a GraphQL client that can make requests to the API.
108
+ * @returns {GraphQLClient} implementation
109
+ */
110
+ getGraphQLClient() {
111
+ if (!this.config.clientFactory) {
112
+ throw new Error('You should provide a clientFactory.');
113
+ }
114
+ return this.config.clientFactory({
115
+ debugger: debug.multisite,
116
+ });
117
+ }
118
+ getCacheKey() {
119
+ return 'siteinfo-service-cache';
120
+ }
121
+ }
@@ -0,0 +1,84 @@
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 { siteNameError } from '../constants';
11
+ import debug from '../debug';
12
+ const PREFIX_NAME_SITEMAP = 'sitemap';
13
+ // The default query for request sitemaps
14
+ const defaultQuery = /* GraphQL */ `
15
+ query SitemapQuery($siteName: String!) {
16
+ site {
17
+ siteInfo(site: $siteName) {
18
+ sitemap
19
+ }
20
+ }
21
+ }
22
+ `;
23
+ /**
24
+ * Service that fetch the sitemaps data using Sitecore's GraphQL API.
25
+ */
26
+ export class GraphQLSitemapXmlService {
27
+ /**
28
+ * Creates an instance of graphQL sitemaps service with the provided options
29
+ * @param {GraphQLSitemapXmlServiceConfig} options instance
30
+ */
31
+ constructor(options) {
32
+ this.options = options;
33
+ this.graphQLClient = this.getGraphQLClient();
34
+ }
35
+ get query() {
36
+ return defaultQuery;
37
+ }
38
+ /**
39
+ * Fetch list of sitemaps for the site
40
+ * @returns {string[]} list of sitemap paths
41
+ * @throws {Error} if the siteName is empty.
42
+ */
43
+ fetchSitemaps() {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ const siteName = this.options.siteName;
46
+ if (!siteName) {
47
+ throw new Error(siteNameError);
48
+ }
49
+ const sitemapResult = this.graphQLClient.request(this.query, {
50
+ siteName,
51
+ });
52
+ try {
53
+ return sitemapResult.then((result) => result.site.siteInfo.sitemap);
54
+ }
55
+ catch (e) {
56
+ return Promise.reject(e);
57
+ }
58
+ });
59
+ }
60
+ /**
61
+ * Get sitemap file path for sitemap id
62
+ * @param {string} id the sitemap id (can be empty for default 'sitemap.xml' file)
63
+ * @returns {string | undefined} the sitemap file path or undefined if one doesn't exist
64
+ */
65
+ getSitemap(id) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ const searchSitemap = `${PREFIX_NAME_SITEMAP}${id}.xml`;
68
+ const sitemaps = yield this.fetchSitemaps();
69
+ return sitemaps.find((sitemap) => sitemap.includes(searchSitemap));
70
+ });
71
+ }
72
+ /**
73
+ * Gets a GraphQL client that can make requests to the API.
74
+ * @returns {GraphQLClient} implementation
75
+ */
76
+ getGraphQLClient() {
77
+ if (!this.options.clientFactory) {
78
+ throw new Error('You should provide a clientFactory.');
79
+ }
80
+ return this.options.clientFactory({
81
+ debugger: debug.sitemap,
82
+ });
83
+ }
84
+ }
@@ -0,0 +1,7 @@
1
+ export { GraphQLRobotsService, } from './graphql-robots-service';
2
+ export { REDIRECT_TYPE_301, REDIRECT_TYPE_302, REDIRECT_TYPE_SERVER_TRANSFER, GraphQLRedirectsService, } from './graphql-redirects-service';
3
+ export { GraphQLSitemapXmlService, } from './graphql-sitemap-service';
4
+ export { GraphQLErrorPagesService, } from './graphql-error-pages-service';
5
+ export { GraphQLSiteInfoService, } from './graphql-siteinfo-service';
6
+ export { getSiteRewrite, getSiteRewriteData, normalizeSiteRewrite } from './utils';
7
+ export { SiteResolver } from './site-resolver';
@@ -0,0 +1,75 @@
1
+ // Delimiters for multi-value hostnames
2
+ const DELIMITERS = /\||,|;/g;
3
+ /**
4
+ * Resolves site based on the provided host or site name
5
+ */
6
+ export class SiteResolver {
7
+ /**
8
+ * @param {SiteInfo[]} sites Array of sites to be used in resolution
9
+ */
10
+ constructor(sites) {
11
+ this.sites = sites;
12
+ /**
13
+ * Resolve site by host name
14
+ * @param {string} hostName the host name
15
+ * @returns {SiteInfo} the resolved site
16
+ * @throws {Error} if a matching site is not found
17
+ */
18
+ this.getByHost = (hostName) => {
19
+ for (const [hostname, site] of this.getHostMap()) {
20
+ if (this.matchesPattern(hostName, hostname)) {
21
+ return site;
22
+ }
23
+ }
24
+ throw new Error(`Could not resolve site for host ${hostName}`);
25
+ };
26
+ /**
27
+ * Resolve site by site name
28
+ * @param {string} siteName the site name
29
+ * @returns {SiteInfo} the resolved site
30
+ * @throws {Error} if a matching site is not found
31
+ */
32
+ this.getByName = (siteName) => {
33
+ const siteInfo = this.sites.find((info) => info.name.toLocaleLowerCase() === siteName.toLocaleLowerCase());
34
+ if (!siteInfo) {
35
+ throw new Error(`Could not resolve site for name ${siteName}`);
36
+ }
37
+ return siteInfo;
38
+ };
39
+ this.getHostMap = () => {
40
+ const map = new Map();
41
+ // First collect unique hostnames.
42
+ // For sites with same hostname defined, priority is given to the first encountered.
43
+ this.sites.forEach((site) => {
44
+ const hostnames = site.hostName
45
+ .replace(/\s/g, '')
46
+ .toLocaleLowerCase()
47
+ .split(DELIMITERS);
48
+ hostnames.forEach((hostname) => {
49
+ if (!map.has(hostname)) {
50
+ map.set(hostname, site);
51
+ }
52
+ });
53
+ });
54
+ // Now order by specificity.
55
+ // This equivalates to sorting from longest to shortest with the assumption
56
+ // that your match is less specific as wildcards are introduced.
57
+ // E.g. order.eu.site.com → *.eu.site.com → *.site.com → *
58
+ // In case of a tie (e.g. *.site.com vs i.site.com), prefer one with less wildcards.
59
+ return new Map(Array.from(map).sort((a, b) => {
60
+ if (a[0].length === b[0].length) {
61
+ return (a[0].match(/\*/g) || []).length - (b[0].match(/\*/g) || []).length;
62
+ }
63
+ return b[0].length - a[0].length;
64
+ }));
65
+ };
66
+ }
67
+ // b[0].match(/\*/g) || []).length
68
+ matchesPattern(hostname, pattern) {
69
+ // dots should be treated as chars
70
+ // stars should be treated as wildcards
71
+ const regExpPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
72
+ const regExp = new RegExp(`^${regExpPattern}$`, 'gi');
73
+ return !!hostname.match(regExp);
74
+ }
75
+ }
@@ -0,0 +1,37 @@
1
+ export const SITE_PREFIX = '_site_';
2
+ /**
3
+ * Get a site rewrite path for given pathname
4
+ * @param {string} pathname the pathname
5
+ * @param {SiteRewriteData} data the site data to include in the rewrite
6
+ * @returns {string} the rewrite path
7
+ */
8
+ export function getSiteRewrite(pathname, data) {
9
+ const path = pathname.startsWith('/') ? pathname : '/' + pathname;
10
+ return `/${SITE_PREFIX}${data.siteName}${path}`;
11
+ }
12
+ /**
13
+ * Get site data from the rewrite path
14
+ * @param {string} pathname the pathname
15
+ * @param {string} defaultSiteName the default site name
16
+ * @returns {SiteRewriteData} the site data from the rewrite
17
+ */
18
+ export function getSiteRewriteData(pathname, defaultSiteName) {
19
+ const data = {
20
+ siteName: defaultSiteName,
21
+ };
22
+ const path = pathname.endsWith('/') ? pathname : pathname + '/';
23
+ const result = path.match(`${SITE_PREFIX}(.*?)\\/`);
24
+ if (result && result[1] !== '') {
25
+ data.siteName = result[1];
26
+ }
27
+ return data;
28
+ }
29
+ /**
30
+ * Normalize a site rewrite path (remove site data)
31
+ * @param {string} pathname the pathname
32
+ * @returns {string} the pathname with site data removed
33
+ */
34
+ export function normalizeSiteRewrite(pathname) {
35
+ const result = pathname.match(`${SITE_PREFIX}.*?(?:\\/|$)`);
36
+ return result === null ? pathname : pathname.replace(result[0], '');
37
+ }
@@ -0,0 +1,133 @@
1
+ export const DefaultEditFrameButtonIds = {
2
+ edit: '{70C4EED5-D4CD-4D7D-9763-80C42504F5E7}',
3
+ };
4
+ export const DefaultEditFrameButton = {
5
+ insert: {
6
+ header: 'Insert New',
7
+ icon: '/~/icon/Office/16x16/insert_from_template.png',
8
+ click: 'webedit:new',
9
+ tooltip: 'Insert a new item',
10
+ },
11
+ editRelatedItem: {
12
+ header: 'Edit the related item',
13
+ icon: '/~/icon/Office/16x16/cubes.png',
14
+ click: 'webedit:open',
15
+ tooltip: 'Edit the related item in the Content Editor.',
16
+ },
17
+ edit: {
18
+ header: 'Edit Item',
19
+ icon: '/~/icon/people/16x16/cubes_blue.png',
20
+ fields: ['Title', 'Text'],
21
+ tooltip: 'Edit the item fields.',
22
+ },
23
+ };
24
+ export const DefaultEditFrameButtons = [
25
+ DefaultEditFrameButton.editRelatedItem,
26
+ DefaultEditFrameButton.insert,
27
+ DefaultEditFrameButton.edit,
28
+ ];
29
+ /**
30
+ * @param {WebEditButton | FieldEditButton} button the button to determine the type of
31
+ */
32
+ function isWebEditButton(button) {
33
+ return button.click !== undefined;
34
+ }
35
+ /**
36
+ * Map the edit button types to chrome data
37
+ * @param {EditButtonTypes } button the edit button to build a ChromeCommand for
38
+ * @param {string} itemId the ID of the item the EditFrame is associated with
39
+ * @param {Record<string, string | number | boolean | undefined | null>} frameParameters additional parameters passed to the EditFrame
40
+ */
41
+ export function mapButtonToCommand(button, itemId, frameParameters) {
42
+ if (button === '|' || button.isDivider) {
43
+ return {
44
+ click: 'chrome:dummy',
45
+ header: 'Separator',
46
+ icon: '',
47
+ isDivider: true,
48
+ tooltip: null,
49
+ type: 'separator',
50
+ };
51
+ }
52
+ else if (isWebEditButton(button)) {
53
+ return commandBuilder(button, itemId, frameParameters);
54
+ }
55
+ else {
56
+ const fieldsString = button.fields.join('|');
57
+ const editButton = Object.assign({ click: `webedit:fieldeditor(command=${DefaultEditFrameButtonIds.edit},fields=${fieldsString})` }, button);
58
+ return commandBuilder(editButton, itemId, frameParameters);
59
+ }
60
+ }
61
+ /**
62
+ * Build a ChromeCommand from a web edit button. Merging the parameters from the button, frame and id
63
+ * @param {WebEditButton } button the web edit button to build a ChromeCommand for
64
+ * @param {string} itemId the ID of the item the EditFrame is associated with
65
+ * @param {Record<string, string>} frameParameters additional parameters passed to the EditFrame
66
+ */
67
+ export function commandBuilder(button, itemId, frameParameters) {
68
+ if (!button.click) {
69
+ return Object.assign({ isDivider: false, type: button.type || null, header: button.header || '', icon: button.icon || '', tooltip: button.tooltip || '' }, button);
70
+ }
71
+ else if (button.click.startsWith('javascript:') || button.click.startsWith('chrome:')) {
72
+ return Object.assign({ isDivider: false, type: button.type || null, header: button.header || '', icon: button.icon || '', tooltip: button.tooltip || '' }, button);
73
+ }
74
+ else {
75
+ if (!itemId) {
76
+ return Object.assign({ isDivider: false, type: button.type || null, header: button.header || '', icon: button.icon || '', tooltip: button.tooltip || '' }, button);
77
+ }
78
+ else {
79
+ let message = button.click;
80
+ let parameters = {};
81
+ // Extract any parameters already in the command
82
+ const length = button.click.indexOf('(');
83
+ if (length >= 0) {
84
+ const end = button.click.indexOf(')');
85
+ if (end < 0) {
86
+ throw new Error('Message with arguments must end with ")".');
87
+ }
88
+ parameters = button.click
89
+ .substring(length + 1, end)
90
+ .split(',')
91
+ .map((_) => _.trim())
92
+ .reduce((previous, current) => {
93
+ const parts = current.split('=');
94
+ if (parts.length < 2) {
95
+ previous[parts[0]] = '';
96
+ }
97
+ else {
98
+ previous[parts[0]] = parts[1];
99
+ }
100
+ return previous;
101
+ }, {});
102
+ message = button.click.substring(0, length);
103
+ }
104
+ parameters.id = itemId;
105
+ if (button.parameters) {
106
+ Object.keys(button.parameters).forEach((_) => {
107
+ var _a;
108
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
109
+ parameters[_] = ((_a = button.parameters[_]) === null || _a === void 0 ? void 0 : _a.toString()) || '';
110
+ });
111
+ }
112
+ if (frameParameters) {
113
+ Object.keys(frameParameters).forEach((_) => {
114
+ var _a;
115
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
116
+ parameters[_] = ((_a = frameParameters[_]) === null || _a === void 0 ? void 0 : _a.toString()) || '';
117
+ });
118
+ }
119
+ const parameterString = Object.keys(parameters)
120
+ .map((_) => `${_}=${parameters[_]}`)
121
+ .join(', ');
122
+ const click = `${message}(${parameterString})`;
123
+ return {
124
+ isDivider: false,
125
+ click: `javascript:Sitecore.PageModes.PageEditor.postRequest('${click}',null,false)`,
126
+ header: button.header || '',
127
+ icon: button.icon || '',
128
+ tooltip: button.tooltip || '',
129
+ type: button.type || null,
130
+ };
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,111 @@
1
+ import isServer from './is-server';
2
+ /**
3
+ * Static utility class for Sitecore Experience Editor
4
+ */
5
+ export class ExperienceEditor {
6
+ /**
7
+ * Determines whether the current execution context is within a Experience Editor.
8
+ * Experience Editor environment can be identified only in the browser
9
+ * @returns true if executing within a Experience Editor
10
+ */
11
+ static isActive() {
12
+ if (isServer()) {
13
+ return false;
14
+ }
15
+ // eslint-disable-next-line
16
+ const sc = window.Sitecore;
17
+ return Boolean(sc && sc.PageModes && sc.PageModes.ChromeManager);
18
+ }
19
+ static resetChromes() {
20
+ if (isServer()) {
21
+ return;
22
+ }
23
+ window.Sitecore.PageModes.ChromeManager.resetChromes();
24
+ }
25
+ }
26
+ /**
27
+ * Copy of chrome rediscovery contract from Horizon (chrome-rediscovery.contract.ts)
28
+ */
29
+ export const ChromeRediscoveryGlobalFunctionName = {
30
+ name: 'Sitecore.Horizon.ResetChromes',
31
+ };
32
+ /**
33
+ * Static utility class for Sitecore Horizon Editor
34
+ */
35
+ export class HorizonEditor {
36
+ /**
37
+ * Determines whether the current execution context is within a Horizon Editor.
38
+ * Horizon Editor environment can be identified only in the browser
39
+ * @returns true if executing within a Horizon Editor
40
+ */
41
+ static isActive() {
42
+ if (isServer()) {
43
+ return false;
44
+ }
45
+ // Horizon will add "sc_horizon=editor" query string parameter for the editor and "sc_horizon=simulator" for the preview
46
+ return window.location.search.indexOf('sc_horizon=editor') > -1;
47
+ }
48
+ static resetChromes() {
49
+ if (isServer()) {
50
+ return;
51
+ }
52
+ // Reset chromes in Horizon
53
+ window[ChromeRediscoveryGlobalFunctionName.name] &&
54
+ window[ChromeRediscoveryGlobalFunctionName.name]();
55
+ }
56
+ }
57
+ /**
58
+ * Determines whether the current execution context is within a Sitecore editor.
59
+ * Sitecore Editor environment can be identified only in the browser
60
+ * @returns true if executing within a Sitecore editor
61
+ */
62
+ export const isEditorActive = () => {
63
+ return ExperienceEditor.isActive() || HorizonEditor.isActive();
64
+ };
65
+ /**
66
+ * Resets Sitecore editor "chromes"
67
+ */
68
+ export const resetEditorChromes = () => {
69
+ if (ExperienceEditor.isActive()) {
70
+ ExperienceEditor.resetChromes();
71
+ }
72
+ else if (HorizonEditor.isActive()) {
73
+ HorizonEditor.resetChromes();
74
+ }
75
+ };
76
+ /**
77
+ * @description in Experience Editor, anchor tags
78
+ * with both onclick and href attributes will use the href, blocking the onclick from firing.
79
+ * This function makes it so the anchor tags function as intended in the sample when using Experience Editor
80
+ *
81
+ * The Mutation Observer API is used to observe changes to the body, then select all elements with href="#" and an onclick,
82
+ * and replaces the # value with javascript:void(0); which prevents the anchor tag from blocking the onclick event handler.
83
+ * @see Mutation Observer API: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver
84
+ */
85
+ export const handleEditorAnchors = () => {
86
+ // The sample gives the href attribute priority over the onclick attribute if both are present, so we must replace
87
+ // the href attribute to avoid overriding the onclick in Experience Editor
88
+ if (!window || !ExperienceEditor.isActive()) {
89
+ return;
90
+ }
91
+ const targetNode = document.querySelector('body');
92
+ const callback = (mutationList) => {
93
+ mutationList.forEach((mutation) => {
94
+ const btns = document.querySelectorAll('.scChromeDropDown > a[href="#"], .scChromeDropDown > a[href="#!"], a[onclick]');
95
+ if (mutation.type === 'childList') {
96
+ btns.forEach((link) => {
97
+ link.href = 'javascript:void(0);';
98
+ });
99
+ }
100
+ return;
101
+ });
102
+ };
103
+ const observer = new MutationObserver(callback);
104
+ const observerOptions = {
105
+ childList: true,
106
+ subtree: true,
107
+ };
108
+ if (targetNode) {
109
+ observer.observe(targetNode, observerOptions);
110
+ }
111
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Method to parse JSON-formatted environment variables
3
+ * @param {string} envValue - can be undefined when providing values via process.env
4
+ * @param {T} defaultValue - default value
5
+ * @returns {T | string} parsed value
6
+ */
7
+ export const tryParseEnvValue = (envValue, defaultValue) => {
8
+ if (!envValue) {
9
+ return defaultValue;
10
+ }
11
+ if (envValue.startsWith('{') && envValue.endsWith('}')) {
12
+ try {
13
+ return JSON.parse(envValue);
14
+ }
15
+ catch (error) {
16
+ console.warn('Parsing of env variable failed');
17
+ console.warn(`Attempted to parse ${envValue}`);
18
+ return defaultValue;
19
+ }
20
+ }
21
+ return defaultValue;
22
+ };
@@ -0,0 +1,5 @@
1
+ export { default as isServer } from './is-server';
2
+ export { resolveUrl, isAbsoluteUrl, isTimeoutError } from './utils';
3
+ export { tryParseEnvValue } from './env';
4
+ export { ExperienceEditor, HorizonEditor, isEditorActive, resetEditorChromes, handleEditorAnchors, } from './editing';
5
+ export { DefaultEditFrameButton, DefaultEditFrameButtons, DefaultEditFrameButtonIds, mapButtonToCommand, } from './edit-frame';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Determines whether the current execution context is server-side
3
+ * @returns true if executing server-side
4
+ */
5
+ function isServer() {
6
+ return !(typeof window !== 'undefined' && window.document);
7
+ }
8
+ export default isServer;