@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.
- package/LICENSE.txt +202 -0
- package/README.md +7 -0
- package/dist/cjs/cache-client.js +54 -0
- package/dist/cjs/constants.js +12 -0
- package/dist/cjs/debug.js +43 -0
- package/dist/cjs/graphql/app-root-query.js +73 -0
- package/dist/cjs/graphql/graphql-edge-proxy.js +12 -0
- package/dist/cjs/graphql/index.js +11 -0
- package/dist/cjs/graphql/search-service.js +60 -0
- package/dist/cjs/graphql-request-client.js +106 -0
- package/dist/cjs/i18n/dictionary-service.js +45 -0
- package/dist/cjs/i18n/graphql-dictionary-service.js +125 -0
- package/dist/cjs/i18n/index.js +7 -0
- package/dist/cjs/index.js +36 -0
- package/dist/cjs/layout/content-styles.js +73 -0
- package/dist/cjs/layout/graphql-layout-service.js +84 -0
- package/dist/cjs/layout/index.js +18 -0
- package/dist/cjs/layout/layout-service.js +9 -0
- package/dist/cjs/layout/models.js +27 -0
- package/dist/cjs/layout/themes.js +79 -0
- package/dist/cjs/layout/utils.js +44 -0
- package/dist/cjs/media/index.js +24 -0
- package/dist/cjs/media/media-api.js +128 -0
- package/dist/cjs/models.js +2 -0
- package/dist/cjs/native-fetcher.js +183 -0
- package/dist/cjs/personalize/graphql-personalize-service.js +114 -0
- package/dist/cjs/personalize/index.js +12 -0
- package/dist/cjs/personalize/layout-personalizer.js +75 -0
- package/dist/cjs/personalize/utils.js +92 -0
- package/dist/cjs/site/graphql-error-pages-service.js +86 -0
- package/dist/cjs/site/graphql-redirects-service.js +103 -0
- package/dist/cjs/site/graphql-robots-service.js +81 -0
- package/dist/cjs/site/graphql-siteinfo-service.js +128 -0
- package/dist/cjs/site/graphql-sitemap-service.js +91 -0
- package/dist/cjs/site/index.js +22 -0
- package/dist/cjs/site/site-resolver.js +79 -0
- package/dist/cjs/site/utils.js +43 -0
- package/dist/cjs/utils/edit-frame.js +138 -0
- package/dist/cjs/utils/editing.js +122 -0
- package/dist/cjs/utils/env.js +26 -0
- package/dist/cjs/utils/index.js +25 -0
- package/dist/cjs/utils/is-server.js +10 -0
- package/dist/cjs/utils/timeout-promise.js +31 -0
- package/dist/cjs/utils/utils.js +70 -0
- package/dist/esm/cache-client.js +50 -0
- package/dist/esm/constants.js +9 -0
- package/dist/esm/debug.js +36 -0
- package/dist/esm/graphql/app-root-query.js +69 -0
- package/dist/esm/graphql/graphql-edge-proxy.js +8 -0
- package/dist/esm/graphql/index.js +4 -0
- package/dist/esm/graphql/search-service.js +56 -0
- package/dist/esm/graphql-request-client.js +99 -0
- package/dist/esm/i18n/dictionary-service.js +41 -0
- package/dist/esm/i18n/graphql-dictionary-service.js +118 -0
- package/dist/esm/i18n/index.js +2 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/layout/content-styles.js +65 -0
- package/dist/esm/layout/graphql-layout-service.js +77 -0
- package/dist/esm/layout/index.js +6 -0
- package/dist/esm/layout/layout-service.js +5 -0
- package/dist/esm/layout/models.js +24 -0
- package/dist/esm/layout/themes.js +74 -0
- package/dist/esm/layout/utils.js +39 -0
- package/dist/esm/media/index.js +2 -0
- package/dist/esm/media/media-api.js +117 -0
- package/dist/esm/models.js +1 -0
- package/dist/esm/native-fetcher.js +176 -0
- package/dist/esm/personalize/graphql-personalize-service.js +107 -0
- package/dist/esm/personalize/index.js +3 -0
- package/dist/esm/personalize/layout-personalizer.js +69 -0
- package/dist/esm/personalize/utils.js +85 -0
- package/dist/esm/site/graphql-error-pages-service.js +79 -0
- package/dist/esm/site/graphql-redirects-service.js +96 -0
- package/dist/esm/site/graphql-robots-service.js +74 -0
- package/dist/esm/site/graphql-siteinfo-service.js +121 -0
- package/dist/esm/site/graphql-sitemap-service.js +84 -0
- package/dist/esm/site/index.js +7 -0
- package/dist/esm/site/site-resolver.js +75 -0
- package/dist/esm/site/utils.js +37 -0
- package/dist/esm/utils/edit-frame.js +133 -0
- package/dist/esm/utils/editing.js +111 -0
- package/dist/esm/utils/env.js +22 -0
- package/dist/esm/utils/index.js +5 -0
- package/dist/esm/utils/is-server.js +8 -0
- package/dist/esm/utils/timeout-promise.js +28 -0
- package/dist/esm/utils/utils.js +61 -0
- package/graphql.d.ts +1 -0
- package/graphql.js +1 -0
- package/i18n.d.ts +1 -0
- package/i18n.js +1 -0
- package/layout.d.ts +1 -0
- package/layout.js +1 -0
- package/media.d.ts +1 -0
- package/media.js +1 -0
- package/package.json +71 -0
- package/personalize.d.ts +1 -0
- package/personalize.js +1 -0
- package/site.d.ts +1 -0
- package/site.js +1 -0
- package/types/cache-client.d.ts +64 -0
- package/types/constants.d.ts +6 -0
- package/types/debug.d.ts +26 -0
- package/types/graphql/app-root-query.d.ts +32 -0
- package/types/graphql/graphql-edge-proxy.d.ts +7 -0
- package/types/graphql/index.d.ts +4 -0
- package/types/graphql/search-service.d.ts +92 -0
- package/types/graphql-request-client.d.ts +88 -0
- package/types/i18n/dictionary-service.d.ts +56 -0
- package/types/i18n/graphql-dictionary-service.d.ts +65 -0
- package/types/i18n/index.d.ts +2 -0
- package/types/index.d.ts +6 -0
- package/types/layout/content-styles.d.ts +18 -0
- package/types/layout/graphql-layout-service.d.ts +59 -0
- package/types/layout/index.d.ts +6 -0
- package/types/layout/layout-service.d.ts +20 -0
- package/types/layout/models.d.ts +140 -0
- package/types/layout/themes.d.ts +11 -0
- package/types/layout/utils.d.ts +17 -0
- package/types/media/index.d.ts +2 -0
- package/types/media/media-api.d.ts +69 -0
- package/types/models.d.ts +6 -0
- package/types/native-fetcher.d.ts +92 -0
- package/types/personalize/graphql-personalize-service.d.ts +77 -0
- package/types/personalize/index.d.ts +3 -0
- package/types/personalize/layout-personalizer.d.ts +25 -0
- package/types/personalize/utils.d.ts +53 -0
- package/types/site/graphql-error-pages-service.d.ts +55 -0
- package/types/site/graphql-redirects-service.d.ts +66 -0
- package/types/site/graphql-robots-service.d.ts +47 -0
- package/types/site/graphql-siteinfo-service.d.ts +69 -0
- package/types/site/graphql-sitemap-service.d.ts +53 -0
- package/types/site/index.d.ts +7 -0
- package/types/site/site-resolver.d.ts +27 -0
- package/types/site/utils.d.ts +24 -0
- package/types/utils/edit-frame.d.ts +76 -0
- package/types/utils/editing.d.ts +58 -0
- package/types/utils/env.d.ts +7 -0
- package/types/utils/index.d.ts +5 -0
- package/types/utils/is-server.d.ts +6 -0
- package/types/utils/timeout-promise.d.ts +18 -0
- package/types/utils/utils.d.ts +18 -0
- package/utils.d.ts +1 -0
- 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';
|